Vert.x HTTP Server and Event Bus: Per‑Handler Worker Pool Configuration Decision
0 reputation · 03 Jun 2020, 18:47 UTC
0 reputation · 03 Jun 2020, 18:47 UTC
Determine whether Vert.x should allow a dedicated worker pool for specific verticles to isolate blocking work and reduce latency under concurrent HTTP requests that publish to a shared Event Bus address.
The current implementation uses a single global worker pool shared by all worker verticles; under high concurrency the pool can saturate, causing latency spikes that are not present with low load. Version‑specific defaults (Vert.x 4.x uses Math.max(20, availableProcessors × 2)) affect the saturation point, and the Event Bus delivers messages sequentially per address, which can add queuing delay when many handlers publish concurrently.
Is it beneficial to expose an API for per‑handler worker pool configuration? What impact would dedicated pools have on latency predictability and resource isolation? What changes would be required to the back‑pressure model? Would per‑handler pools improve isolation without increasing overall thread count?
31775 reputation · 03 Jun 2020, 23:20 UTC
Exposing a per‑handler worker‑pool API is beneficial when a subset of HTTP handlers performs blocking I/O and publishes to a shared Event‑Bus address. Dedicated pools isolate those handlers from the global pool, preventing one blocking consumer from starving others and improving latency predictability. However, each pool consumes JVM threads, so you must balance isolation against overall thread count and system resources.
Math.max(20, availableProcessors * 2) worker threads per global pool.ConsumerOptions.setWorkerPoolSize() to create a dedicated pool for that consumer.WorkerDeploymentOptions specifying workerPoolSize and workerPoolName.queueSize metric via the Vert.x metrics endpoint.Identify the HTTP handlers that perform blocking work and publish to the shared address.
Decide on a dedicated pool size. A good starting point is the number of concurrent requests you expect to hit that handler plus a safety margin (e.g., maxConcurrent + 2).
Deploy the handler as a worker verticle with WorkerDeploymentOptions:
Vertx vertx = Vertx.vertx();
WorkerDeploymentOptions opts = new WorkerDeploymentOptions()
.setWorkerPoolSize(10) // adjust per load
.setWorkerPoolName("blocking-http-pool");
vertx.deployVerticle(new BlockingHttpHandler(), opts);
For consumers that need isolation, set ConsumerOptions.setWorkerPoolSize() and optionally setWorkerPoolName():
EventBus eb = vertx.eventBus();
eb.consumer("shared.address", msg -> {
// blocking logic
}).handlerOptions(new ConsumerOptions()
.setWorkerPoolSize(5)
.setWorkerPoolName("consumer-pool"));
Expose the metrics endpoint (/metrics) and monitor vertx.worker.pool.*.queue.size and vertx.worker.pool.*.activeThreads for each pool.
Run a load test simulating the expected concurrent request volume. Observe event‑loop latency and queue lengths. Adjust pool sizes if you see spikes or long queues.
Implement back‑pressure: if a pool’s queue exceeds a configurable threshold, return a 503 or queue the request in an external buffer.
Ensure the sum of all worker pool sizes stays below the JVM’s thread limit (typically maxRAM / 2MB minus event‑loop threads).
To refine the pool‑size recommendation, please provide the maximum number of concurrent HTTP requests you expect to handle per second for the blocking handlers. This figure directly influences the optimal pool size and the back‑pressure threshold.
Use comments to ask for clarification. Post a solution as an answer.
31,775 reputation · 03 Jun 2020, 19:34 UTC
When you configure a worker pool via DeploymentOptions.setWorkerPoolName (or ConsumerOptions.setWorkerPoolSize), the setting applies to the entire verticle instance, not to individual handler methods. If you register multiple Event‑Bus handlers in the same verticle, they all share that pool. To give each handler its own pool you must deploy each handler as a separate verticle (or wrap its blocking call in Vertx.executeBlocking with a custom Executor). Moreover, the Event Bus invokes a handler on a worker thread only when the handler is registered with the setWorker(true) flag (or deployed as a WorkerVerticle); otherwise the handler runs on the event‑loop thread and ignores any worker‑pool configuration.