You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Add opt-in query pipelining support
Allow multiple queries to be sent on the wire before waiting for
responses, reducing round-trip latency. Enabled via client.pipelining = true.
Each query gets its own Sync boundary so errors are isolated.
Tracks in-flight named statements (submittedNamedStatements) to prevent
duplicate Parse messages when pipelining queries with the same prepared
statement name. Handles error/disconnect cleanup for the sent queue.
* Fix pipelining edge cases and add benchmark
- Clean up submittedNamedStatements on error in _handleErrorMessage to
prevent stale entries from blocking future re-preparation of the same
named statement after a parse failure
- Guard _pulsePipelinedQueryQueue against non-queryable connections
- Fix cancel() and readTimeout for sent queries: removing an already-sent
query from _sentQueryQueue corrupts the pipeline response mapping since
the server will still respond to it; no-op the callback instead
- Add bench-pipelining.js comparing serial vs pipelined throughput
* Fix pipelining activation race and add edge-case tests
Gate _sentQueryQueue activation on readyForQuery=true inside
_pulsePipelinedQueryQueue (and remove the redundant promotion block
from _handleReadyForQuery) to eliminate the microtask/macrotask race
where the next query could be activated as _activeQuery before the
error's ReadyForQuery arrived, causing that RFQ to be handled by the
wrong query.
Also adds the error-listener fix for the query_timeout integration test
so the expected stream-destroy doesn't leak as an unhandled 'error'.
* Add pipelining documentation and pool-level integration
- New features/pipelining.mdx documenting the opt-in flag
- Client and Pool API reference updated
- Pool accepts `pipelining: true` and sets it on every client it creates
* Fix prettier formatting
* Add pipelining support to native client via libpq pipeline mode
- pg-native: handle PGRES_PIPELINE_SYNC/PGRES_PIPELINE_ABORTED in
_emitResult; add pipeline() batch method using libpq 14+ pipeline
mode (enterPipelineMode, pipelineSync, exitPipelineMode)
- pg-native: bump libpq dependency to ^1.9.0 (has pipeline bindings)
- native client: add _pulsePipelinedQueryQueue that batches all queued
queries through pg-native pipeline(), delivering results per-query
- native client: suppress queue length deprecation when pipelining
* Fix pipeline mode to use extended query protocol and add JS vs native benchmarks
Pipeline mode requires sendQueryParams (extended query protocol), not sendQuery
(simple query protocol). PostgreSQL rejects PQsendQuery in pipeline mode.
Benchmark script now tests all four combinations: JS serial, JS pipelined,
native serial, and native pipelined.
* Fix native client end() to wait for in-flight pipeline queries
The native client end() was immediately terminating the connection,
causing "Connection terminated" errors for queries still in the pipeline.
Now waits for the drain event before closing when pipelining is active.
Also fix pipeline mode to use sendQueryParams instead of sendQuery,
since PostgreSQL rejects simple query protocol in pipeline mode.
* Fix native pipelining: guard handleError when query.native is unset and skip JS-only tests
handleError in native/query.js crashes when this.native is undefined
(e.g. query_timeout fires before pipeline callback sets it).
Skip named statement cleanup and query_timeout tests for native client
since those features rely on JS-specific internals.
* Make pipelining a constructor option named 'pipeline'
Move from post-construction property (client.pipelining = true) to
constructor option (new Client({ pipeline: true })). Same for the pool:
new Pool({ pipeline: true }).
Renames the property and internal _pipeliningInFlight to _pipelineInFlight.
Tests, docs, and benchmarks updated accordingly.
---------
Co-authored-by: Brian C <brian.m.carlson@gmail.com>
Copy file name to clipboardExpand all lines: docs/pages/apis/client.mdx
+13-1Lines changed: 13 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,7 +29,8 @@ type Config = {
29
29
idle_in_transaction_session_timeout?:number, // number of milliseconds before terminating any session with an open idle transaction, default is no timeout
30
30
client_encoding?:string, // specifies the character set encoding that the database uses for sending data to the client
31
31
fallback_application_name?:string, // provide an application name to use if application_name is not set
32
-
options?:string// command-line options to be sent to the server
32
+
options?:string, // command-line options to be sent to the server
33
+
pipeline?:boolean// when true, enables query pipelining. See /features/pipelining for details. Default false.
33
34
}
34
35
```
35
36
@@ -56,6 +57,17 @@ const client = new Client()
56
57
awaitclient.connect()
57
58
```
58
59
60
+
## client.pipeline
61
+
62
+
`client.pipeline: boolean` (read-only)
63
+
64
+
Whether this client has pipelining enabled. Set via the `pipeline` config option to the `Client` constructor. Defaults to `false`. See [Pipelining](/features/pipelining) for details and examples.
By default node-postgres waits for each query to complete before sending the next one. This means every query pays a full network round-trip of latency. **Query pipelining** sends multiple queries to the server without waiting for responses, and the server processes them in order. Each query still gets its own result (or error), but you avoid the idle time between them.
10
+
11
+
```
12
+
sequential (default) pipelined
13
+
───────────────────── ─────────────────────
14
+
client ──Parse──▶ server client ──Parse──▶ server
15
+
client ◀──Ready── server ──Parse──▶
16
+
client ──Parse──▶ server ──Parse──▶
17
+
client ◀──Ready── server client ◀──Ready── server
18
+
client ──Parse──▶ server client ◀──Ready── server
19
+
client ◀──Ready── server client ◀──Ready── server
20
+
```
21
+
22
+
In benchmarks, pipelining typically delivers **2-3x throughput** for batches of simple queries on a local connection, with larger gains over higher-latency links.
23
+
24
+
## Enabling pipelining
25
+
26
+
Pipelining is opt-in. Pass `pipeline: true` to the `Client` constructor:
All query types work with pipelining: plain text, parameterized, and named prepared statements.
46
+
47
+
## Pipelining with a pool
48
+
49
+
Pass `pipeline: true` in the pool config to enable it on every client the pool creates:
50
+
51
+
```js
52
+
import { Pool } from'pg'
53
+
54
+
constpool=newPool({ pipeline:true })
55
+
56
+
constclient=awaitpool.connect()
57
+
// client.pipeline is already true
58
+
59
+
const [users, orders] =awaitPromise.all([
60
+
client.query('SELECT * FROM users WHERE id = $1', [1]),
61
+
client.query('SELECT * FROM orders WHERE user_id = $1', [1]),
62
+
])
63
+
64
+
client.release()
65
+
```
66
+
67
+
<Alert>
68
+
<div>
69
+
<code>pool.query()</code> checks out a client for a single query and releases it immediately, so pipelining has no effect there. Use <code>pool.connect()</code> to check out a client and send multiple queries on it.
70
+
</div>
71
+
</Alert>
72
+
73
+
## Error isolation
74
+
75
+
Each pipelined query gets its own error boundary. A failing query in the middle of a batch does not break the other queries:
76
+
77
+
```js
78
+
constresults=awaitPromise.allSettled([
79
+
client.query('SELECT 1 AS num'),
80
+
client.query('SELECT INVALID SYNTAX'),
81
+
client.query('SELECT 3 AS num'),
82
+
])
83
+
84
+
console.log(results[0].status) // 'fulfilled'
85
+
console.log(results[1].status) // 'rejected'
86
+
console.log(results[2].status) // 'fulfilled'
87
+
```
88
+
89
+
This works because node-postgres sends a `Sync` message after each query, which is how PostgreSQL delimits error boundaries in the extended query protocol.
90
+
91
+
## Named prepared statements
92
+
93
+
Named prepared statements work with pipelining. When two pipelined queries share the same statement name, node-postgres sends `Parse` only once and reuses the prepared statement for subsequent queries:
Calling `client.end()` while pipelined queries are in flight will wait for all of them to complete before closing the connection:
108
+
109
+
```js
110
+
constclient=newClient({ pipeline:true })
111
+
awaitclient.connect()
112
+
113
+
constp1=client.query('SELECT 1')
114
+
constp2=client.query('SELECT 2')
115
+
constendPromise=client.end()
116
+
117
+
// Both queries will resolve normally
118
+
const [r1, r2] =awaitPromise.all([p1, p2])
119
+
await endPromise
120
+
```
121
+
122
+
## When to use pipelining
123
+
124
+
Pipelining is most useful when you have multiple **independent** queries that don't depend on each other's results. Common use cases:
125
+
126
+
- Fetching data from multiple tables in parallel for a page load
127
+
- Inserting or updating multiple rows simultaneously
128
+
- Running a batch of analytics queries
129
+
130
+
<divclassName="alert alert-warning">
131
+
Do not use pipelining inside a transaction if you need to read the result of one query before issuing the next. Pipelined queries are all sent before any responses arrive, so you cannot branch on intermediate results. For dependent queries within a transaction, use sequential <code>await</code> calls instead.
0 commit comments