From 4bd71f795fdba2f560913a982e562335584baf59 Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Sun, 14 Dec 2025 19:30:43 +0100 Subject: [PATCH 1/3] refactor(client): replace query queue array with Deque for O(1) operations --- packages/pg/lib/client.js | 10 +++----- packages/pg/lib/deque.js | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 packages/pg/lib/deque.js diff --git a/packages/pg/lib/client.js b/packages/pg/lib/client.js index 903db6c66..54a9213d5 100644 --- a/packages/pg/lib/client.js +++ b/packages/pg/lib/client.js @@ -11,6 +11,7 @@ const Query = require('./query') const defaults = require('./defaults') const Connection = require('./connection') const crypto = require('./crypto/utils') +const Deque = require('./deque') const activeQueryDeprecationNotice = nodeUtils.deprecate( () => {}, @@ -79,7 +80,7 @@ class Client extends EventEmitter { keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0, encoding: this.connectionParameters.client_encoding || 'utf8', }) - this._queryQueue = [] + this._queryQueue = new Deque() this.binary = c.binary || defaults.binary this.processID = null this.secretKey = null @@ -124,7 +125,7 @@ class Client extends EventEmitter { } this._queryQueue.forEach(enqueueError) - this._queryQueue.length = 0 + this._queryQueue.clear() } _connect(callback) { @@ -608,10 +609,7 @@ class Client extends EventEmitter { query.callback = () => {} // Remove from queue - const index = this._queryQueue.indexOf(query) - if (index > -1) { - this._queryQueue.splice(index, 1) - } + this._queryQueue.remove(query) this._pulseQueryQueue() }, readTimeout) diff --git a/packages/pg/lib/deque.js b/packages/pg/lib/deque.js new file mode 100644 index 000000000..3d5050851 --- /dev/null +++ b/packages/pg/lib/deque.js @@ -0,0 +1,51 @@ +class Deque { + constructor() { + this._store = Object.create(null) + this._head = 0 + this._tail = 0 + } + + push(item) { + this._store[this._tail++] = item + } + + shift() { + if (this._head === this._tail) return undefined + const item = this._store[this._head] + delete this._store[this._head++] + return item + } + + get length() { + return this._tail - this._head + } + + clear() { + this._store = Object.create(null) + this._head = 0 + this._tail = 0 + } + + remove(item) { + const newStore = Object.create(null) + const newHead = 0 + let newTail = 0 + for (let i = this._head; i < this._tail; i++) { + const current = this._store[i] + if (current !== item) { + newStore[newTail++] = current + } + } + this._store = newStore + this._head = newHead + this._tail = newTail + } + + forEach(fn) { + for (let i = this._head; i < this._tail; i++) { + fn(this._store[i], i) + } + } +} + +module.exports = Deque From d40e18c82d0d66ec7c1678c5db97c6f1580963d9 Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Sun, 14 Dec 2025 19:51:23 +0100 Subject: [PATCH 2/3] perf: make stable object shapes --- packages/pg/lib/deque.js | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/pg/lib/deque.js b/packages/pg/lib/deque.js index 3d5050851..bca141c04 100644 --- a/packages/pg/lib/deque.js +++ b/packages/pg/lib/deque.js @@ -12,7 +12,14 @@ class Deque { shift() { if (this._head === this._tail) return undefined const item = this._store[this._head] - delete this._store[this._head++] + this._store[this._head] = undefined + this._head++ + + if (this._head === this._tail) { + this._head = 0 + this._tail = 0 + } + return item } @@ -27,23 +34,33 @@ class Deque { } remove(item) { - const newStore = Object.create(null) - const newHead = 0 - let newTail = 0 - for (let i = this._head; i < this._tail; i++) { - const current = this._store[i] + if (this._head === this._tail) return + + const store = this._store + let write = this._head + + for (let read = this._head; read < this._tail; read++) { + const current = store[read] if (current !== item) { - newStore[newTail++] = current + store[write++] = current } } - this._store = newStore - this._head = newHead - this._tail = newTail + + for (let i = write; i < this._tail; i++) { + store[i] = undefined + } + + this._tail = write + + if (this._head === this._tail) { + this._head = 0 + this._tail = 0 + } } forEach(fn) { for (let i = this._head; i < this._tail; i++) { - fn(this._store[i], i) + fn(this._store[i]) } } } From 17158e6b8afd17fe2e3897a0a3f4c043104fb26f Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Mon, 15 Dec 2025 21:51:02 +0100 Subject: [PATCH 3/3] chore: make remove O(1) --- packages/pg/lib/deque.js | 87 +++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/packages/pg/lib/deque.js b/packages/pg/lib/deque.js index bca141c04..dfb5db1e0 100644 --- a/packages/pg/lib/deque.js +++ b/packages/pg/lib/deque.js @@ -1,66 +1,81 @@ class Deque { constructor() { - this._store = Object.create(null) - this._head = 0 - this._tail = 0 + this._head = null + this._tail = null + this._size = 0 + this._index = new WeakMap() } push(item) { - this._store[this._tail++] = item + const node = { value: item, prev: this._tail, next: null } + + if (this._tail) { + node.prev = this._tail + this._tail.next = node + this._tail = node + } else { + this._head = this._tail = node + } + + this._index.set(item, node) + this._size++ } shift() { - if (this._head === this._tail) return undefined - const item = this._store[this._head] - this._store[this._head] = undefined - this._head++ - - if (this._head === this._tail) { - this._head = 0 - this._tail = 0 + if (!this._head) return undefined + + const node = this._head + const value = node.value + + this._head = node.next + if (this._head) { + this._head.prev = null + } else { + this._tail = null } - return item + this._index.delete(value) + this._size-- + + node.prev = node.next = null + + return value } get length() { - return this._tail - this._head + return this._size } clear() { - this._store = Object.create(null) - this._head = 0 - this._tail = 0 + this._head = null + this._tail = null + this._size = 0 + this._index = new WeakMap() } remove(item) { - if (this._head === this._tail) return + const node = this._index.get(item) + if (!node) return false - const store = this._store - let write = this._head + if (node.prev) node.prev.next = node.next + else this._head = node.next - for (let read = this._head; read < this._tail; read++) { - const current = store[read] - if (current !== item) { - store[write++] = current - } - } + if (node.next) node.next.prev = node.prev + else this._tail = node.prev - for (let i = write; i < this._tail; i++) { - store[i] = undefined - } + this._index.delete(item) + this._size-- - this._tail = write + node.prev = node.next = null - if (this._head === this._tail) { - this._head = 0 - this._tail = 0 - } + return true } forEach(fn) { - for (let i = this._head; i < this._tail; i++) { - fn(this._store[i]) + let curr = this._head + while (curr) { + fn(curr.value) + curr = curr.next } } }