# 34.5. Pipeline Mode
34.5.2. Functions Associated with Pipeline Mode
34.5.3. When to Use Pipeline Mode
libpq pipeline mode allows applications to send a query without having to read the result of the previously sent query. Taking advantage of the pipeline mode, a client will wait less for the server, since multiple queries/results can be sent/received in a single network transaction.
While pipeline mode provides a significant performance boost, writing clients using the pipeline mode is more complex because it involves managing a queue of pending queries and finding which result corresponds to which query in the queue.
Pipeline mode also generally consumes more memory on both the client and server, though careful and aggressive management of the send/receive queue can mitigate this. This applies whether or not the connection is in blocking or non-blocking mode.
While the pipeline API was introduced in PostgreSQL 14, it is a client-side feature which doesn't require special server support and works on any server that supports the v3 extended query protocol.
# 34.5.1. Using Pipeline Mode
To issue pipelines, the application must switch the connection into pipeline mode, which is done withPQenterPipelineMode
.PQpipelineStatus
can be used to test whether pipeline mode is active. In pipeline mode, onlyasynchronous operationsare permitted, command strings containing multiple SQL commands are disallowed, and so isCOPY
. Using synchronous command execution functions such asPQfn
,PQexec
,PQexecParams
,PQprepare
,PQexecPrepared
,PQdescribePrepared
,PQdescribePortal
, is an error condition. Once all dispatched commands have had their results processed, and the end pipeline result has been consumed, the application may return to non-pipelined mode withPQexitPipelineMode
.
# Note
It is best to use pipeline mode with libpq innon-blocking mode. If used in blocking mode it is possible for a client/server deadlock to occur.[15]
# 34.5.1.1. Issuing Queries
After entering pipeline mode, the application dispatches requests usingPQsendQuery
,PQsendQueryParams
, or its prepared-query siblingPQsendQueryPrepared
. These requests are queued on the client-side until flushed to the server; this occurs whenPQpipelineSync
is used to establish a synchronization point in the pipeline, or whenPQflush
is called. The functionsPQsendPrepare
,PQsendDescribePrepared
, andPQsendDescribePortal
also work in pipeline mode. Result processing is described below.
The server executes statements, and returns results, in the order the client sends them. The server will begin executing the commands in the pipeline immediately, not waiting for the end of the pipeline. Note that results are buffered on the server side; the server flushes that buffer when a synchronization point is established withPQpipelineSync
, or whenPQsendFlushRequest
is called. If any statement encounters an error, the server aborts the current transaction and does not execute any subsequent command in the queue until the next synchronization point; aPGRES_PIPELINE_ABORTED
result is produced for each such command. (This remains true even if the commands in the pipeline would rollback the transaction.) Query processing resumes after the synchronization point.
It's fine for one operation to depend on the results of a prior one; for example, one query may define a table that the next query in the same pipeline uses. Similarly, an application may create a named prepared statement and execute it with later statements in the same pipeline.
# 34.5.1.2. Processing Results
To process the result of one query in a pipeline, the application callsPQgetResult
repeatedly and handles each result untilPQgetResult
returns null. The result from the next query in the pipeline may then be retrieved usingPQgetResult
again and the cycle repeated. The application handles individual statement results as normal. When the results of all the queries in the pipeline have been returned,PQgetResult
returns a result containing the status valuePGRES_PIPELINE_SYNC
The client may choose to defer result processing until the complete pipeline has been sent, or interleave that with sending further queries in the pipeline; seeSection 34.5.1.4.
To enter single-row mode, callPQsetSingleRowMode
before retrieving results withPQgetResult
. This mode selection is effective only for the query currently being processed. For more information on the use ofPQsetSingleRowMode
, refer toSection 34.6.
PQgetResult
behaves the same as for normal asynchronous processing except that it may contain the newPGresult
typesPGRES_PIPELINE_SYNC
andPGRES_PIPELINE_ABORTED
.PGRES_PIPELINE_SYNC
is reported exactly once for eachPQpipelineSync
at the corresponding point in the pipeline.PGRES_PIPELINE_ABORTED
is emitted in place of a normal query result for the first error and all subsequent results until the nextPGRES_PIPELINE_SYNC
; seeSection 34.5.1.3.
PQisBusy
,PQconsumeInput
, etc operate as normal when processing pipeline results. In particular, a call toPQisBusy
in the middle of a pipeline returns 0 if the results for all the queries issued so far have been consumed.
libpq does not provide any information to the application about the query currently being processed (except thatPQgetResult
returns null to indicate that we start returning the results of next query). The application must keep track of the order in which it sent queries, to associate them with their corresponding results. Applications will typically use a state machine or a FIFO queue for this.
# 34.5.1.3. Error Handling
从客户的角度来看,之后PQresult状态
返回PGRES_FATAL_ERROR
,管道被标记为中止。PQresult状态
将报告一个PGRES_PIPELINE_ABORTED
中止管道中每个剩余排队操作的结果。结果为PQpipelineSync
报告为PGRES_PIPELINE_SYNC
发出终止流水线的信号并恢复正常结果处理。
客户端必须处理结果PQgetResult
在错误恢复期间。
如果管道使用隐式事务,则回滚已执行的操作,并完全跳过排队等待失败操作的操作。如果管道启动并提交单个显式事务(即第一条语句是开始
最后一个是犯罪
) 除了会话在管道结束时仍处于中止事务状态。如果管道包含多个显式事务,在错误之前提交的所有事务保持提交,当前正在进行的事务被中止,并且所有后续操作被完全跳过,包括后续事务。如果管道同步点出现在显式事务块处于中止状态时,下一个管道将立即中止,除非下一个命令将事务置于正常模式回滚
.
# 笔记
客户不得假定工作已提交sendsaCOMMIT
— only when the corresponding result is received to confirm the commit is complete. Because errors arrive asynchronously, the application needs to be able to restart from the lastreceivedcommitted change and resend work done after that point if something goes wrong.
# 34.5.1.4. Interleaving Result Processing and Query Dispatch
To avoid deadlocks on large pipelines the client should be structured around a non-blocking event loop using operating system facilities such asselect
,poll
,WaitForMultipleObjectEx
, etc.
The client application should generally maintain a queue of work remaining to be dispatched and a queue of work that has been dispatched but not yet had its results processed. When the socket is writable it should dispatch more work. When the socket is readable it should read results and process them, matching them up to the next entry in its corresponding results queue. Based on available memory, results from the socket should be read frequently: there's no need to wait until the pipeline end to read the results. Pipelines should be scoped to logical units of work, usually (but not necessarily) one transaction per pipeline. There's no need to exit pipeline mode and re-enter it between pipelines, or to wait for one pipeline to finish before sending the next.
An example usingselect()
and a simple state machine to track sent and received work is insrc/test/modules/libpq_pipeline/libpq_pipeline.c
in the PostgreSQL source distribution.
# 34.5.2. Functions Associated with Pipeline Mode
Returns the current pipeline mode status of the libpq connection.
PGpipelineStatus PQpipelineStatus(const PGconn *conn);
PQpipelineStatus
can return one of the following values:
PQ_PIPELINE_ON
The libpq connection is in pipeline mode.
PQ_PIPELINE_OFF
The libpq connection isnotin pipeline mode.
PQ_PIPELINE_ABORTED
The libpq connection is in pipeline mode and an error occurred while processing the current pipeline. The aborted flag is cleared whenPQgetResult
returns a result of typePGRES_PIPELINE_SYNC
.
Causes a connection to enter pipeline mode if it is currently idle or already in pipeline mode.
int PQenterPipelineMode(PGconn *conn);
Returns 1 for success. Returns 0 and has no effect if the connection is not currently idle, i.e., it has a result ready, or it is waiting for more input from the server, etc. This function does not actually send anything to the server, it just changes the libpq connection state.
Causes a connection to exit pipeline mode if it is currently in pipeline mode with an empty queue and no pending results.
int PQexitPipelineMode(PGconn *conn);
Returns 1 for success. Returns 1 and takes no action if not in pipeline mode. If the current statement isn't finished processing, orPQgetResult
has not been called to collect results from all previously sent query, returns 0 (in which case, usePQerrorMessage
to get more information about the failure).
Marks a synchronization point in a pipeline by sending async messageand flushing the send buffer. This serves as the delimiter of an implicit transaction and an error recovery point; seeSection 34.5.1.3.
int PQpipelineSync(PGconn *conn);
Returns 1 for success. Returns 0 if the connection is not in pipeline mode or sending async messagefailed.
Sends a request for the server to flush its output buffer.
int PQsendFlushRequest(PGconn *conn);
Returns 1 for success. Returns 0 on any failure.
The server flushes its output buffer automatically as a result ofPQpipelineSync
being called, or on any request when not in pipeline mode; this function is useful to cause the server to flush its output buffer in pipeline mode without establishing a synchronization point. Note that the request is not itself flushed to the server automatically; usePQflush
if necessary.
# 34.5.3. When to Use Pipeline Mode
Much like asynchronous query mode, there is no meaningful performance overhead when using pipeline mode. It increases client application complexity, and extra caution is required to prevent client/server deadlocks, but pipeline mode can offer considerable performance improvements, in exchange for increased memory usage from leaving state around longer.
Pipeline mode is most useful when the server is distant, i.e., network latency (“ping time”) is high, and also when many small operations are being performed in rapid succession. There is usually less benefit in using pipelined commands when each query takes many multiples of the client/server round-trip time to execute. A 100-statement operation run on a server 300 ms round-trip-time away would take 30 seconds in network latency alone without pipelining; with pipelining it may spend as little as 0.3 s waiting for results from the server.
Use pipelined commands when your application does lots of smallINSERT
,UPDATE
andDELETE
operations that can't easily be transformed into operations on sets, or into aCOPY
operation.
Pipeline mode is not useful when information from one operation is required by the client to produce the next operation. In such cases, the client would have to introduce a synchronization point and wait for a full client/server round-trip to get the results it needs. However, it's often possible to adjust the client design to exchange the required information server-side. Read-modify-write cycles are especially good candidates; for example:
BEGIN;
SELECT x FROM mytable WHERE id = 42 FOR UPDATE;
-- result: x=2
-- client adds 1 to x:
UPDATE mytable SET x = 3 WHERE id = 42;
COMMIT;
could be much more efficiently done with:
UPDATE mytable SET x = x + 1 WHERE id = 42;
Pipelining is less useful, and more complex, when a single pipeline contains multiple transactions (seeSection 34.5.1.3).