-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Broker] Increase default numHttpServerThreads value to 50 to prevent Admin API unavailability #14320
base: master
Are you sure you want to change the base?
Conversation
…t Broker Admin API unavailability - numHttpServerThreads is the maximum number of threads. Initial number of threads is 8 when numHttpServerThreads >= 8 - Jetty defaults to 200 maximum threads, to prevent thread pool starvation. - this is the reason to use the value 200 as a default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch
@gaoran10 @michaeljmarshall please consider including this fix in your upcoming RCs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change the default configuration need to start with a proposal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason of setting the default value to 200? If the node just have one core, what will happen? Please send email to dev mail list to discuss.
These are threads. Jetty defaults to 200 maximum threads, to prevent thread pool starvation. This is recommended when using blocking Servlet API. The problem is that Pulsar uses the blocking servlet API and doesn't have a sufficient amount of threads which are needed and recommended. The value 200 doesn't mean that there will be 200 threads to start with. This is the maximum size for the thread pool. When the value is more than 8, Jetty will start with 8 initial threads and add more threads to the pool when all threads are occupied. I have already started an email discussion to discuss this topic. Please reply to https://lists.apache.org/thread/byg1g081o6mfj0xn8ntryvb5qplmrjyl . There is useful background information in https://lists.apache.org/thread/hso8qwsv40ccrk116fj5ggdpt3b9d4g4 . I wrote that reply before I noticed Penghui's response. It contains a link to Jetty's documenation about asynchronous servlets: https://wiki.eclipse.org/Jetty/Feature/Continuations#Why_Asynchronous_Servlets_.3F . |
@lhotari If this is jetty defaults. Can we just leave it blank? |
This PR is a proposal. I have also made this proposal on the dev mailing list in the discussion. https://lists.apache.org/thread/byg1g081o6mfj0xn8ntryvb5qplmrjyl . What else is needed? In this case, the previous default for numHttpServerThreads is simply too small and invalid when blocking servlet API is used. There is no breaking change in increasing the default value to 200. It's just an improvement and fixes "the problem" where Admin API goes unresponsive when all threads are occupied. We might end up setting the default value to something lower than 200. A value like 50 or 100 might be fine. I just think that 200 is a good default since Jetty also uses that as the default value. The main overhead of a thread is the amount of memory that the stack of each thread consumes. It's 1MB by default. 200 threads will consume 200MB of RSS memory in the thread stacks. |
@Jason918 no. Pulsar overrides the default with the value set in numHttpServerThreads in the configuration. Pulsar code locations: pulsar/pulsar-broker/src/main/java/org/apache/pulsar/broker/web/WebService.java Lines 79 to 81 in b540523
pulsar/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/web/WebExecutorThreadPool.java Lines 33 to 36 in adcbe0f
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to solve the root cause of the issue.
This PR is a proposal. I have also made this proposal on the dev mailing list in the discussion. https://lists.apache.org/thread/byg1g081o6mfj0xn8ntryvb5qplmrjyl . What else is needed?
I think we need a PIP to get this change approved.
@lhotari Do we need to take the number of availableProcessors into consideration for the maximum threads of the thread pool? |
Please tell me what is "the issue" that you are referring to?
The Pulsar community makes major decisions on the dev mailing list according to the Apache Way. The mailing list is the place to decide whether this change needs a PIP or not. Please participate in the existing dev mailing list discussion: https://lists.apache.org/thread/byg1g081o6mfj0xn8ntryvb5qplmrjyl |
@lhotari @eolivelli I suggest that we should consider the system cpu cores, it may be hurtful change for people who run pulsar in a low machine, like one cpu core. |
Sure |
I think I need to explain more for the important term I used. IMO, a PR that could block a release during the release phase must match following rules:
It's only my opinion. I think our release document for release manager missed something like this. Let's look back to this PR. First, I don't think a change to the default configuration value can be treated as a bug fix. It's more like an enhancement. Because the previous stable releases all should have the same problem. Then, we can see it's not a regression. Third, it's not something serious like Log4j2 Vulnerability (CVE-2021-44228). It just make some certain cases not work for Admin API and can be fixed by configuration tuning. In short, IMO, after a release started, we must be very careful and strict on the new PRs. |
The unavailability of the Admin API is not caused by the HTTP server thread, the root cause is that the ZK callback thread is blocked. When an admin API calls the ZK metadatastore API, it gets the ZK data by call the How to solve this problem?
How to reproduce the ZK callback thread is blocked:
public class Main {
private static final long CACHE_REFRESH_TIME_MILLIS = TimeUnit.MINUTES.toMillis(5);
public static void printThread(String name) {
System.out.println(name + " thread name -> " + Thread.currentThread().getName());
}
public static void main(String[] args) throws Exception {
ZooKeeper zkc = new ZooKeeper("localhost:2181", 60_000, null);
System.out.println("Check the zk connect");
CountDownLatch zkLatch = new CountDownLatch(1);
new Thread(() -> {
while (true) {
if (zkc.getState().isConnected()) {
zkLatch.countDown();
break;
}
}
}).start();
if (!zkLatch.await(5, TimeUnit.SECONDS)) {
throw new Exception("zk connect failed");
}
AsyncLoadingCache<String, byte[]> objCache = Caffeine.newBuilder()
.refreshAfterWrite(CACHE_REFRESH_TIME_MILLIS, TimeUnit.MILLISECONDS)
.buildAsync((key, executor) -> {
CompletableFuture<byte[]> future = new CompletableFuture<>();
zkc.multi(Lists.newArrayList(Op.getData("/")), (rc, path, ctx, opResults) -> {
printThread("zk callback");
future.complete(null);
}, null);
return future;
});
CountDownLatch countDownLatch = new CountDownLatch(1);
// Reproduce the ZK callback is blocked
System.out.println("async get start");
objCache.get("/").whenComplete((unused, ignored) -> {
printThread("async get done");
try {
System.out.println("zk thread will blocked after sync get");
System.out.println("sync get start");
objCache.get("/1").get(5, TimeUnit.SECONDS);
// Unreachable
printThread("sync get done");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
});
countDownLatch.await();
}
} |
The blocked thread in #13666 is a HTTP server thread.
I'll clarify what I have been referring to as "sync -> async" changes: changes where the use of the blocking Servlet API is migrated to use Asynchronous Servlet API. |
I agree. Have I been rushing this? Instead, you can say that a lot of PRs with "make async" have been pushed and merged recently. My valid questions were never answered by the contributors of the "make async" changes. I'm expecting that there are issues or a PIP which is referred to.
I'll clarify: what I have been referring to as "sync -> async" changes: changes where the use of the blocking Servlet API is migrated to use Asynchronous Servlet API. That won't solve any problems on it's own. Any problems that it might solve would be solved also by configuring Jetty as it is recommended to be configured when there are blocking calls involved. I'd assume that the reason for deadlocks when thread pool size is properly configured are caused by locks. I like to see an example of a deadlock which couldn't be resolved by continuing to use the blocking servlet api. I'm not against the changes from blocking API to async API, but I think changes need proper justification, especially when the "make async" changes have been initiated without referring any reported issues or a PIP. |
I haven't requested to block the release.
The Jetty documentation recommends values 50-500 for the maximum thread pool size. That is a fact, so there cannot be different opinions on this. Since the default configuration value doesn't fall in the recommended value range, my opinion is that this is a bug. For some people a bug is a feature. :) Does it really matter whether we call this a bug or an improvement? |
Yeah, I noticed these PRs recently as well. But are these PRs blockers for 2.10.0 release? IMO, they should not be blockers as well. I've thought they are intended to be included in Pulsar 2.11.0.
Sorry, I might missed some context. I just saw this PR in the 2.10.0 release email list. This PR should focus on the fix itself, but the previous discussion might go far for the release issue.
It's a pity to see the lack of communication. AFAIK, @Technoboy- is also preparing for a PIP to make admin APIs async. I think you should have a discussion about:
|
I'll continue the discussion in the PIP-142 discussion email. |
Great point @BewareMyPower . I hope that the problem would first be discussed or reported before a PIP is created. @Technoboy- Would you be able to start some discussion even before the PIP is ready?
That's also a valid question to ask. When we work together, we can learn together. |
This is not something that can block release. |
When the ZK callback thread is blocked in the WEB thread, another admin API request the ZK metadata store is not working, so you see this thread stack. |
The pr had no activity for 30 days, mark with Stale label. |
The pr had no activity for 30 days, mark with Stale label. |
@lhotari - should this old debate on a PR be closed as it was hopefully resolved? |
The Pulsar Admin client doesn't have a limit of how many connections it opens to a single broker. There is issue #22041 for addressing that. |
Motivation
Since Pulsar Admin API uses the blocking servlet API, it is possible that all Jetty threads are occupied and this causes unavailability on the Pulsar Admin API. The default value for the maximum number of threads for Jetty is too low in Pulsar. That is the root cause of many problems where Pulsar Admin API is unavailable when all threads are in use.
Additional context
Mailing list thread about "make async" changes: https://lists.apache.org/thread/tn7rt59cd1k724l4ytfcmzx1w2sbtw7l
Related issues/PRs Avoid call sync method in async rest API for delete subscription #13666 Broker's web threads get stuck #4756 Converted bundle split into an HTTP async operation #10619
PIP-142
Modification
numHttpServerThreads=50
.numHttpServerThreads