Synchronous crypto.pbkdf2Sync vs. asynchronous crypto.pbkdf2 for handling concurrent requests in Node.js
0 reputation · 15 Nov 2022, 21:15 UTC
0 reputation · 15 Nov 2022, 21:15 UTC
Goal: Identify whether using the synchronous crypto.pbkdf2Sync function or its asynchronous counterpart crypto.pbkdf2 yields lower latency spikes when the server handles many concurrent requests.
Constraint: The synchronous call blocks the Node.js event loop, causing all incoming requests to wait until the operation finishes, while the asynchronous call offloads work to the libuv threadpool, which is limited by UV_THREADPOOL_SIZE and can queue tasks under high concurrency.
Uncertainty: It is not evident which trade‑off—event‑loop blocking versus threadpool queuing—produces better 99th‑percentile latency for realistic payload sizes and iteration counts, especially when the number of concurrent connections exceeds the default threadpool size.
Which approach results in lower average latency under a sustained load of 200 concurrent requests?
Does increasing UV_THREADPOOL_SIZE mitigate the latency disadvantage of the asynchronous variant, or does it merely shift the bottleneck elsewhere?
33275 reputation · 16 Nov 2022, 02:04 UTC
Under a sustained load of 200 concurrent requests, the asynchronous crypto.pbkdf2 call generally produces lower average latency and significantly lower 99th‑percentile latency spikes than the synchronous crypto.pbkdf2Sync call.
crypto.pbkdf2Sync blocks the Node.js event loop for the entire duration of each hash, causing all other incoming requests to wait.crypto.pbkdf2 offloads work to libuv’s threadpool (default size 4). When the number of concurrent hash operations exceeds the pool size, tasks queue, but the event loop remains free to handle new connections.UV_THREADPOOL_SIZE raises the pool’s capacity, reducing queueing delay for the asynchronous variant up to the number of available CPU cores; beyond that point the benefit diminishes due to added context‑switch overhead and memory usage.The synchronous variant stalls the entire event loop, so even a single long hash can delay all other requests, inflating tail latency. The asynchronous variant allows the loop to continue accepting and processing other I/O while CPU‑bound hashes run in parallel threads; latency spikes appear only when the threadpool becomes saturated, which typically occurs later than the event‑loop stall caused by the sync version.
crypto.pbkdf2Sync or crypto.pbkdf2 with fixed PBKDF2 parameters (e.g., 100 k iterations, 64‑byte salt, 32‑byte key).UV_THREADPOOL_SIZE values (e.g., 4, 8, 12, 20) via the environment variable, keeping all other factors constant.process._getActiveHandles() or clinic flame) to confirm whether the pool is saturated.To fine‑tune UV_THREADPOOL_SIZE you need to know the number of CPU cores available on the server. If the core count is lower than the threadpool size you intend to set, increasing the pool may add overhead without reducing latency, potentially making the synchronous approach relatively less harmful. Please provide the server’s CPU core count so the advice can be adjusted accordingly.
Use comments to ask for clarification. Post a solution as an answer.
33,275 reputation · 16 Nov 2022, 08:37 UTC
While crypto.pbkdf2Sync blocks the event loop, it can be isolated from the main thread by delegating the call to a WorkerThread. In that setup the main loop remains responsive, and you retain the simplicity of the synchronous API.
Similarly, the cluster module can spawn multiple Node.js processes, each with its own threadpool, effectively multiplying the available UV_THREADPOOL_SIZE capacity. This is often a cheaper alternative to raising the threadpool size beyond the number of CPU cores.
For workloads with very low iteration counts (<10 k), the overhead of queueing in the default pool can outweigh the benefit of offloading; in such cases pbkdf2Sync in a worker thread may actually be faster.