perf(pg-pool): cut per-query allocations in the acquire/release path#3680
Open
kylecannon wants to merge 1 commit into
Open
perf(pg-pool): cut per-query allocations in the acquire/release path#3680kylecannon wants to merge 1 commit into
kylecannon wants to merge 1 commit into
Conversation
Two allocation reductions, no behavior change:
- client.on('error', onError) instead of once: the query callback always removes
the listener and onError self-guards via clientReleased, so once's per-query
onceWrapper allocation was unnecessary.
- Bind _pulseQueue once in the constructor (_pulseQueueBound) and reuse it for the
connect-path nextTick and the three _remove callbacks, instead of allocating a
fresh bound function per acquire/release.
Benchmark (mock-client pool-micro, alternating-sampled vs base, conc=50):
~+2-3% qps (all 3 rounds favored this change). The O(1) pending-queue dequeue
(separate PR) adds more at high concurrency.
Full pg-pool suite passes (85 passing; the one failure is the unrelated
pg-native bindings test, which requires libpq).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
|
I’m not for merging this. Seems like a microoptimization that doesn’t match the rest of the code. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two small allocation reductions in the pool's hot acquire/release path, with no behavior or API change:
client.on('error', onError)instead ofclient.once(...). The query result callback already removes the listener on completion, andonErrorself-guards via theclientReleasedflag, soonce's per-query wrapper allocation was unnecessary._pulseQueueonce in the constructor (this._pulseQueueBound) and reuse that reference for the connect-pathprocess.nextTickand the three_removecallbacks, instead of allocating a fresh bound function on every acquire/release.Why
pool.query()is on the hot path for most applications. These changes remove one closure allocation per query (theoncewrapper) and one bound-function allocation per acquire/release, lowering per-query GC pressure with no change to behavior.Correctness
The
oncetoonswitch cannot double-fire or leak a listener:client.removeListener('error', onError)before resolving.onErrorflipsclientReleasedto true on its first call and returns early on any later call, so it can release and call back at most once.No public API or observable behavior changes.
waitingCountand all emitted events are unchanged.Benchmark
Measured with a mock-client microbenchmark (no network, so it isolates the pool's own per-query overhead), alternating between this branch and base each round to cancel thermal drift. At concurrency 50 this change is about +2 to +3% queries/sec, with all three alternating rounds favoring it.
This is the allocation portion only. A separate change (O(1) pending-queue dequeue) adds more at high concurrency.
Testing
The full
pg-poolsuite passes (85 passing). The one failure in my environment is the unrelatedpg-nativebindings test, which requires a locallibpqbuild.