Skip to content

Conversation

@nigrosimone
Copy link
Contributor

This PR replaces the internal _queryQueue array in Client with a custom Deque data structure. Previously, the client relied on Array.shift() and Array.splice() to manage queued queries, which are O(n) operations and become costly under heavy load.

With the new Deque:

  • push, shift, and remove are O(1), reducing overhead and GC pressure.
  • _errorAllQueries and query timeout handling now use Deque’s forEach, clear, and remove methods.
  • The overall behavior of the client remains unchanged, but performance scales better with many concurrent queries.

Benefits:

  • More efficient query queue management.
  • Lower latency and improved throughput under high concurrency.
  • Cleaner, more predictable queue operations.

@nigrosimone nigrosimone changed the title refactor(client): Improve query queue performance by introducing Deque perf(client): Improve query queue performance by introducing Deque Dec 14, 2025
@nigrosimone nigrosimone changed the title perf(client): Improve query queue performance by introducing Deque perf(client): improve query queue performance by introducing Deque Dec 14, 2025
@nigrosimone nigrosimone marked this pull request as draft December 14, 2025 18:40
@nigrosimone nigrosimone marked this pull request as ready for review December 14, 2025 18:52
Copy link
Collaborator

@charmander charmander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a question about removing the query queue entirely at one point; I’ve always discouraged its use, but I can’t remember if it has any bad behaviours remaining beyond this performance issue and what was addressed with the introduction of _errorAllQueries. I guess there’s the complexity of managing timeouts and client state as it leaves and returns to the pool…. Is it how we want to implement pipelining in the future?

Note: The pool-level queue is separate, and is the queue that most people will be using in practice. (Surprisingly, it’s still on an array + shift implementation too.)

push, shift, and remove are O(1), reducing overhead and GC pressure.

This remove doesn’t look O(1). It also looks like the queue leaks memory in the form of undefined-valued properties as long as it’s never empty.

Reusing a preexisting implementation like double-ended-queue would be nice unless we’re going to implement a faster remove. In the case when the timeout is the same for all queries, removes will always be at the beginning of the queue anyway… but supporting random cancellation and timeouts (with AbortSignal) at both the pool and client level is nice.

@nigrosimone nigrosimone marked this pull request as draft December 15, 2025 20:19
@nigrosimone
Copy link
Contributor Author

I’ve implemented a deque backed by a doubly‑linked list with WeakMap index, so push, shift, and remove are all O(1). This avoids the memory leaks from array holes (undefined) and reduces GC pressure.

If the goal is to eventually remove the client queue entirely, this can still serve as a drop‑in improvement in the meantime.

I agree that reusing a proven library like double-ended-queue is an option, but since we only need a small subset of operations, a lightweight internal implementation may be easier to maintain.

Also, I want to thank you for all the work you put into this project — I really appreciate the effort and dedication you’ve invested in maintaining it. Please also feel free to close the PR if you don’t think it’s interesting or worth pursuing further.

@nigrosimone nigrosimone marked this pull request as ready for review December 15, 2025 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants