libpqパイプラインモードを使用すると、アプリケーションは以前に送信された問い合わせの結果を読み込まなくても問い合わせを送信できます。 パイプラインモードを利用すると、1つのネットワークトランザクションで複数の問い合わせ/結果を送受信できるので、クライアントはサーバを待つ時間が少なくなります。
パイプラインモードではパフォーマンスが大幅に向上しますが、パイプラインモードを使用してクライアントを作成すると、保留中の問い合わせのキューを管理し、どの結果がキュー内のどの問い合わせに対応するかを見つける必要があるため、より複雑になります。
パイプラインモードでは、一般にクライアントとサーバの両方でより多くのメモリを消費しますが、送受信キューを注意深く積極的に管理することでこれを軽減できます。 これは、接続がブロックモードか非ブロックモードかに関係なく適用されます。
パイプラインAPIはPostgreSQL14で導入されましたが、これは特別なサーバサポートを必要としないクライアント側の機能であり、v3拡張問い合わせプロトコルをサポートするサーバで動作します。
パイプラインを発行するためには、アプリケーションは接続をパイプラインモードに切り替える必要があります。
これはPQenterPipelineMode
で行われます。
PQpipelineStatus
は、パイプラインモードがアクティブかどうかをテストするために使用できます。
パイプラインモードでは、非同期操作のみが許可され、複数のSQLコマンドを含むコマンド文字列、COPY
は許可されません。
PQfn
、PQexec
、PQexecParams
、PQprepare
、PQexecPrepared
、PQdescribePrepared
、PQdescribePortal
などの同期コマンド実行関数を使用すると、エラー状態になります。
登録されたすべてのコマンドの結果が処理され、パイプラインの終了結果が消費されると、アプリケーションはPQexitPipelineMode
を使用して非パイプラインモードに戻ることができます。
パイプラインモードに入った後、アプリケーションはPQsendQuery
、PQsendQueryParams
、またはその準備された問い合わせの兄弟であるPQsendQueryPrepared
を使用して要求を登録します。
これらの要求は、サーバにフラッシュされるまでクライアント側で待ち行列に入れられます。
これは、PQpipelineSync
がパイプラインに同期ポイントを確立するために使用された場合、またはPQflush
が呼び出された場合に発生します。
PQsendPrepare
、PQsendDescribePrepared
、PQsendDescribePortal
の関数もパイプラインモードで動作します。
結果の処理については後述します。
サーバは文を実行し、クライアントが送信した順に結果を返します。
サーバはパイプラインのコマンドの実行を即座に開始し、パイプラインの終了を待機しません。
結果はサーバ側でバッファされることに注意してください;同期ポイントがPQpipelineSync
で確立されたとき、またはPQsendFlushRequest
が呼び出されたとき、サーバはバッファをフラッシュします。
いずれかの文でエラーが発生した場合、サーバは現在のトランザクションを中止し、次の同期ポイントまでキュー内の後続のコマンドを実行しません。
このようなコマンドごとにPGRES_PIPELINE_ABORTED
結果が生成されます(パイプラインのコマンドがトランザクションをロールバックする場合でも同様です)。
問い合わせ処理は同期ポイント後に再開されます。
1つの操作が前の操作の結果に依存することは問題ありません。 たとえば、1つの問い合わせが同じパイプラインの次の問い合わせが使用するテーブルを定義することができます。 同様に、アプリケーションは名前付きのプリペアドステートメントを作成し、同じパイプラインの後のステートメントで実行することができます。
パイプラインの1つの問い合わせの結果を処理するために、アプリケーションはPQgetResult
を繰り返し呼び出し、PQgetResult
がNULLを返すまで各結果を処理します。
パイプラインの次の問い合わせの結果は、再度PQgetResult
を使用して取得され、サイクルが繰り返されます。
アプリケーションは個々の文の結果を通常どおり処理します。
パイプラインのすべての問い合わせの結果が返されると、PQgetResult
は状態値PGRES_PIPELINE_SYNC
を含む結果を返します。
クライアントは、完全なパイプラインが送信されるまで結果処理を延期するか、パイプラインでさらに問い合わせを送信して結果処理をインターリーブするかを選択できます。 34.5.1.4を参照してください。
単一行モードに入るには、PQgetResult
で結果を取得する前にPQsetSingleRowMode
を呼び出します。
このモード選択は現在処理中の問い合わせに対してのみ有効です。
PQsetSingleRowMode
の使用に関する詳細については、34.6を参照してください。
PQgetResult
は通常の非同期処理と同じように動作しますが、新しいPGresult
型PGRES_PIPELINE_SYNC
とPGRES_PIPELINE_ABORTED
が含まれる場合があります。
PGRES_PIPELINE_SYNC
は、パイプラインの対応するポイントの各PQpipelineSync
ごとに1回だけ報告されます。
最初のエラーに対する通常の問い合わせ結果の代わりにPGRES_PIPELINE_ABORTED
が出力され、次のPGRES_PIPELINE_SYNC
までのすべての結果が出力されます。
34.5.1.3を参照してください。
パイプラインの結果を処理する場合、PQisBusy
やPQconsumeInput
などは通常どおりに動作します。
特に、パイプラインの途中でPQisBusy
を呼び出した場合、これまでに発行されたすべての問い合わせの結果が消費されていれば0を返します。
libpqは、現在処理されている問い合わせに関する情報をアプリケーションに提供しません(PQgetResult
はNULLを返し、次の問い合わせの結果を返し始めることを示します)。
アプリケーションは、問い合わせを送信した順序を追跡し、対応する結果と関連付ける必要があります。
アプリケーションは通常、ステートマシンまたはFIFOキューを使用します。
クライアント側から見ると、PQresultStatus
がPGRES_FATAL_ERROR
を返した後、パイプラインは中断されたフラグが立てられます。
PQresultStatus
は、中断されたパイプラインの残りのキュー操作ごとにPGRES_PIPELINE_ABORTED
結果を報告します。
PQpipelineSync
の結果はPGRES_PIPELINE_SYNC
として報告され、中断されたパイプラインの終了と通常の結果処理の再開を通知します。
クライアントは、エラー修復中にPQgetResult
で結果を処理しなければなりません。
パイプラインで暗黙的なトランザクションが使用された場合、すでに実行された操作はロールバックされ、失敗した操作に続くキューに入れられていた操作は完全にスキップされます。
パイプラインが開始され、単一の明示的なトランザクションをコミットした場合(つまり、最初の文がBEGIN
、最後の文がCOMMIT
)と同じ動作が成立します。
ただし、セッションはパイプラインの終了時に中断されたトランザクション状態のままです。
パイプラインに複数の明示的なトランザクションが含まれている場合、エラー以前にコミットされたすべてのトランザクションはコミットされたままになり、現在進行中のトランザクションは中断され、後続のトランザクションも含めて後続のすべての操作は完全にスキップされます。
パイプライン同期ポイントが中断状態の明示的なトランザクションブロックで発生した場合、次のコマンドがROLLBACK
を使用してトランザクションを通常モードにしない限り、次のパイプラインは即時に中断されます。
クライアントは、COMMIT
—を送信したときに作業がコミットされたと想定してはなりません。
コミットが完了したことを確認するために対応する結果を受信したときだけです。
エラーは非同期で到着するため、アプリケーションは最後に受信したコミット済みの変更から再起動し、何か問題が発生した場合にはその時点以降に行われた作業を再送信できる必要があります。
大規模なパイプラインでデッドロックを回避するためには、クライアントはselect
、poll
、WaitForMultipleObjectEx
などのオペレーティングシステム機能を使用して、ノンブロッキングイベントループを中心に構築する必要があります。
通常、クライアントアプリケーションは、登録される残りの作業キューと、登録されたがまだ結果が処理されていない作業キューを維持する必要があります。 ソケットが書き込み可能な場合は、より多くの作業を登録する必要があります。 ソケットが読み取り可能な場合は、結果を読み取って処理し、対応する結果キュー内の次のエントリと一致させる必要があります。 使用可能なメモリに基づいて、ソケットからの結果は頻繁に読み取られる必要があります:結果を読み取るためにパイプラインが終了するまで待つ必要はありません。 パイプラインは、通常(必ずしも必要ではありません)パイプラインごとに1つのトランザクションである論理作業単位にスコープされる必要があります。 パイプラインモードを終了してパイプライン間で再入力したり、次のパイプラインを送信する前に1つのパイプラインが終了するのを待つ必要はありません。
送受信された作業を追跡するためにselect()
と単純なステートマシンを使用する例は、PostgreSQLソース配布物のsrc/test/modules/libpq_pipeline/libpq_pipeline.c
にあります。
PQpipelineStatus
libpq接続の現在のパイプラインモード状態を返します。
PGpipelineStatus PQpipelineStatus(const PGconn *conn);
PQpipelineStatus
は以下のいずれかの値を返すことができます。
PQ_PIPELINE_ON
libpq接続はパイプラインモードです。
PQ_PIPELINE_OFF
libpq接続はパイプラインモードではありません。
PQ_PIPELINE_ABORTED
libpq接続はパイプラインモードで、現在のパイプラインの処理中にエラーが発生しました。
PQgetResult
がPGRES_PIPELINE_SYNC
型の結果を返すと、中断フラグがクリアされます。
PQenterPipelineMode
接続が現在アイドル状態であるか、すでにパイプラインモードになっている場合、接続をパイプラインモードにします。
int PQenterPipelineMode(PGconn *conn);
成功した場合は1を返します。 接続が現在アイドル状態でない場合、つまり結果を準備している場合や、サーバからの入力を待っている場合などには0を返し、何の効果もありません。 この関数は実際にはサーバに何も送信せず、単にlibpq接続状態を変更します。
PQexitPipelineMode
接続が現在パイプラインモードにあり、キューが空で、保留中の結果がない場合、接続はパイプラインモードを終了します。
int PQexitPipelineMode(PGconn *conn);
成功した場合は1を返します。
パイプラインモードでない場合は、1を返し、何も行いません。
現在の文の処理が終了していない場合、またはPQgetResult
が以前に送信されたすべての問い合わせから結果を収集するために呼び出されていない場合は、0を返します(この場合、失敗に関する詳細情報を取得するにはPQerrorMessage
を使用します)。
PQpipelineSync
同期メッセージを送信し、送信バッファをフラッシュすることにより、パイプラインの同期ポイントをマークします。 これは暗黙的なトランザクションとエラー修復ポイントの区切り文字として機能します。 34.5.1.3を参照してください。
int PQpipelineSync(PGconn *conn);
成功した場合は1を返します。 接続がパイプラインモードでないか、同期メッセージの送信に失敗した場合は0を返します。
PQsendFlushRequest
サーバに出力バッファをフラッシュする要求を送信します。
int PQsendFlushRequest(PGconn *conn);
成功した場合は1を返します。 失敗した場合は0を返します。
サーバは、PQpipelineSync
が呼び出された結果として、あるいはパイプラインモードでない要求があった場合に、自動的に出力バッファをフラッシュします。
この関数は、同期ポイントを確立せずに、サーバにパイプラインモードで出力バッファをフラッシュさせるのに便利です。
要求自体は自動的にサーバにフラッシュされないことに注意してください。
必要であればPQflush
を使用してください。
非同期問い合わせモードと同様に、パイプラインモードを使用する場合、意味のあるパフォーマンスオーバーヘッドはありません。 これはクライアントアプリケーションの複雑さを増加させ、クライアント/サーバのデッドロックを防ぐために特別な注意が必要ですが、パイプラインモードは、状態をより長く残すことによるメモリ使用量の増加と引き換えに、かなりのパフォーマンス改善を提供することができます。
パイプラインモードは、サーバが離れている場合、つまりネットワーク遅延(「ping時間」)が大きい場合や、多数の小さな操作が連続して実行されている場合に最も便利です。 各問い合わせが実行するのにクライアント/サーバのラウンドトリップ時間の何倍もかかる場合、パイプラインコマンドを使用するメリットは通常少なくなります。 ラウンドトリップ時間が300ミリ秒離れたサーバ上で100文の操作を実行すると、パイプライン処理なしでネットワーク遅延だけで30秒かかります。 パイプライン処理を使用すると、サーバからの結果を待つのに0.3秒ほどしかかかりません。
集合に対する操作やCOPY
操作に容易に変換できない小さなINSERT
、UPDATE
、DELETE
操作をアプリケーションが大量に行う場合は、パイプラインコマンドを使用してください。
パイプラインモードは、クライアントが次のオペレーションを生成するために1つのオペレーションからの情報を必要とする場合には便利ではありません。 このような場合、クライアントは同期ポイントを導入し、クライアント/サーバの完全なラウンドトリップを待機して、必要な結果を取得する必要があります。 ただし、クライアント設計を調整して、必要な情報をサーバ側で交換することも可能です。 読み取り-変更-書き込みサイクルは特に適した候補です。 たとえば、次のようになります。
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;
単一のパイプラインに複数のトランザクションが含まれている場合、パイプライン化はあまり有用ではなく、複雑になります(34.5.1.3を参照)。
[15] クライアントはサーバに問い合わせを送信しようとするのをブロックしますが、サーバは既に処理した問い合わせから結果をクライアントに送信しようとするのをブロックします。 これは、クライアントが出力バッファとサーバの受信バッファの両方を満たすのに十分な問い合わせを送信してから、サーバからの入力処理に切り替える場合にのみ発生しますが、いつ発生するかを正確に予測するのは困難です。