sturdy-bulkhead implements middleware to queue compute-intensive requests within a Ring API.
It prevents CPU starvation by offloading work to dedicated worker pools using core.async.
Add to deps.edn:
{:deps com.sturdystats/sturdy-bulkhead {:mvn/version "VERSION"}}
Initialize a worker pool when your application starts.
(require '[sturdy.bulkhead :as bulkhead])
(def my-pool
(bulkhead/start-pool! {:num-workers 4 ; 4 concurrent queries max
:queue-size 20})) ; queue up to 20 requests
Use the provided middleware to wrap computationally expensive routes.
(def app
(-> heavy-query-handler
(bulkhead/wrap-compute-bound my-pool {:timeout-ms 10000})))
If you use reitit, you can attach the middleware to specific routes using its vector syntax:
(require '[reitit.ring :as ring])
(def app
(ring/ring-handler
(ring/router
["/api"
["/heavy" {:get {:middleware [[bulkhead/wrap-compute-bound my-pool {:timeout-ms 10000}]]
:handler heavy-query-handler}}]
["/light" {:get {:handler light-query-handler}}]])))
When shutting down your application, clean up the resources:
(bulkhead/stop-pool! my-pool)
It is critical to tune the worker pool relative to your web server's thread pool:
The sum of your num-workers and queue-size should be strictly less than your web server's maximum thread pool size.
Because wrap-compute-bound is synchronous middleware, it blocks the incoming Jetty request thread while waiting for the bulkhead worker to finish (or for the timeout to occur).
num-workers): This should be less than your total CPU cores. This ensures the OS and web server always have dedicated resources to process network I/O, health checks, and fast endpoints without CPU starvation.queue-size): Determine how many requests you are willing to hold in memory during a traffic burst. If the queue is full, the middleware immediately returns a 503 Service Unavailable, freeing the jetty thread.num-workers = 30 and queue-size = 250, all of jetty's threads could be sleeping on responses from bulkhead. This would render the server unresponsive until the queue drains.This project uses deps.edn aliases for testing and validation.
# Run unit tests
clj -X:test
# Generate code coverage report (in target/coverage/index.html)
clj -X:coverage
# Scan dependencies for known vulnerabilities (clj-watson)
clj -X:vuln
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |