22 OCIパイプライン化

パイプライン化によって、アプリケーションの全体的なスループットと応答性が向上します。Oracle Databaseリリース23c以降では、アプリケーションがインターリーブを効果的に利用し、アプリケーション・リクエストをオーバーラップすることでサーバーをビジー状態にして、サーバーによって返されるレスポンスを維持できるようにパイプライン化機能が導入されました。

22.1 ブロック化および非ブロック化の概念

この項では、ブロック化と非ブロック化の概念について説明します。

ブロック化機能

ネットワーク・ラウンドトリップを行うすべてのOCI関数は、リクエストとレスポンスが本質的に行われます。アプリケーションがリクエストを送信し、レスポンスを待機してから別のリクエストを送信します。

たとえば、OCIStmtExecute()関数は、SQLを実行するリクエストを送信し、サーバーからのレスポンスを待機します。単一のOCI関数が一度に実行されます。前のリクエストを完了せずに別のOCI関数を同時に起動すると、操作はエラーになります。この実行モデルでは、現在のレスポンスと次のリクエストの間にサーバーがアイドル状態のままになります。また、クライアントは現在のリクエストとサーバーからのレスポンスの間にアイドル状態のままになります。

図22-1ブロック化機能

図22-1の説明が続きます
「図22-1ブロック化機能」の説明

非ブロック化機能

非ブロック化機能は、アイドル時間中にインターリーブを使用して他の操作を実行することでメリットが得られるため、アプリケーションで役立ちます。このモデルでは、サーバーからのレスポンスをブロックするのではなく、アプリケーションが何をするかを選択できるようにすることで、アプリケーションの応答性が向上します。つまり、クライアントのアイドル時間です。

図22-2 パイプライン化機能

図22-2の説明が続きます
「図22-2 パイプライン化機能」の説明

22.2 OCIパイプライン化の概要

この章では、パイプライン化機能について説明します。

パイプライン化の基本的な考え方は、サーバーをビジー状態に保ち、アプリケーションでインターリーブ・リクエストとレスポンスを適切に使用できるようにすることです。

アプリケーションは複数のリクエストを送信し続け、サーバーがキューを構築して1つずつ実行します。サーバーは、リクエストを受信した順序でクライアントにレスポンスを返します。

パイプライン機能に不可欠な要件であるため、リクエストが独立していることを確認するのはアプリケーションの責任です。

ノート:

パイプラインはレスポンスを読み取らないことが多いため、以前のSQLレスポンスからのデータを後続のリクエストへのバインド・データとして使用すると、依存関係が作成され、パイプラインが中断されます。

次の図は、パイプライン内のサンプル・シナリオのエンドツーエンドの説明を示しています。

図22-3 OCIパイプライン・ブロック図

図22-3の説明が続きます
「図22-3 OCIパイプライン・ブロック図」の説明
  • OCIパイプライン・アプリケーションは、前の図に示すように7つの操作を同時に処理しています。
  • サーバーは最初のリクエストを処理し、このリクエストに対するレスポンスがクライアントに到達します。レスポンスを読み取る準備ができました。
  • 2番目のリクエストは、サーバー側で完了処理中です。
  • 3から6のリクエストはサーバー側でキューに入れられ、サーバーによってまだ処理されません。
  • クライアントは、7番目のリクエストをサーバーに非同期に送信しています。
次の新しい関数が導入されました。
  • OCIPipelineBegin(): 操作のパイプライン・ブロックの開始を示します。
  • OCIPipelineProcess(): 操作を処理します。
  • OCIPipelineEnd(): 操作のパイプライン・ブロックの終了を示します。
パイプライン化をサポートし、ラウンドトリップに関与するOCI機能は、次の2つの部分に分かれています。
  • 上半分(リクエストを送信)
  • 下半分(レスポンスを受信)

操作の上半分は、処理のためにサーバーに送信されるリクエストです。下半分は、サーバーから受信した応答です。レスポンスは、OCIPipelineProcess()またはOCIPipelineEnd()関数を使用して取得されます。

このアプローチでは、パイプライン操作ブロック内のパイプラインをサポートするすべてのOCI関数(たとえば、OCIStmtExecute()OCIStmtFetch2())が暗黙的にパイプライン化されます。

ノート:

パイプライン・モードでは、2つの連続した操作への依存性が解決または回避されることを保証するのは、アプリケーションの責任です。

パイプライン操作の2つの隣接ブロックがサポートされています。次に、サンプル・コード・スニペットを示します。

OCIPipelineBegin();
OCIPipelineEnd();

;;;
OCIPipelineBegin();
OCIPipelineEnd();
同じサービス・コンテキストでの重複およびネストされたパイプライン・ブロックはサポートされていません。次に、サンプル・コード・スニペットを示します。
OCIPipelineBegin();
OCIPipelineBegin();
OCIPipelineEnd();
OCIPipelineEnd();

ノート:

Oracle Database 23c以降、パイプライン化機能を提供するために、クライアントとサーバーの両方に拡張が行われています。

22.2.1 OCIパイプライン化の有効化

この項では、OCI APIのパイプライン化を有効にする方法について説明します。

デフォルトでは、OCI APIはアプリケーションでパイプライン化に使用できません。パイプライン化を有効にするために、新しい属性OCI_PIPELINE_ENABLEが導入されました。パイプライン機能は、アプリケーションが環境に属性を設定する場合にのみ使用できます。このモードが設定されていないOCIパイプライン関数をコールすると、エラーが返されます。

構文

boolean pipelineEnable = TRUE;
status = OCIAttrSet((dvoid *) envhp,
           OCI_HTYPE_ENV,
           (dvoid *) &sts,
           (ub4) sizeof(sts),
           OCI_ATTR_PIPELINE_ENABLE,
           (OCIError *) errhp));

ほとんどのアプリケーションでは、ブロック化モードまたは非ブロック化モードでOCI関数が使用されます。OCI_ATTR_PIPELINE_ENABLE属性は、ブロック化シナリオおよび非ブロック化シナリオでのパフォーマンスの低下を回避し、OCIアプリケーションでパイプライン化を無効にするスイッチのように機能します。

22.3 パイプライン操作のモード

この項では、パイプライン操作の様々なモードについて説明します。

OCIPipelineBegin()関数は、操作のパイプライン・ブロックの開始をマークします。

パイプライン操作のモードは、パイプライン操作の実行方法を定義します。パイプライン・モードには2つのレベルがあります。各レベルは、2つの実行モードのいずれかを設定できます。

  • 操作レベル・モード(OCI_ATTR_PIPELINE_OP_MODE): OCIPipelineEnd()関数が実行されるまで、パイプライン操作のいずれかがエラーで失敗した場合でも、サーバーは操作の実行を続行します。

    アプリケーションでは、OCIAttrSet属性を使用して、操作のハンドル((svchp)OCI_ATTR_PIPELINE_OP_MODE属性を設定する必要があります。

    例:

    OCIStmtExecuteの場合、次の属性が設定されます。
    rc = OCIAttrSet((dvoid *) svchp, (ub4) OCI_HTYPE_SVC,
                    (dvoid *) OCI_PIPELINE_CONT_ON_ERROR, (ub4) 0,
                    (ub4) OCI_ATTR_PIPELINE_OP_MODE, (OCIError *) 
                    errhp);
    rc = OCIStmtExecute(svchp, stmthp, … , OCI_DEFAULT);
    
  • ブロックレベル・モード(OCI_PIPELINE_ABORT_ON_ERROR): パイプライン操作ブロックの処理中にエラーが発生した場合、パイプライン操作は中断されます。

    パイプライン操作ブロックのいずれかの操作でエラーが返された場合、その操作に続くすべてのパイプライン操作が中断されます。OCIPipelineEnd()関数までのすべての操作は、クライアントにエラーを返します。

    操作が失敗する前に正常に実行された操作のレスポンスがアプリケーションに返されます。エラーが発生した操作も有効な操作です。エラー・ハンドルには、返されたエラーの詳細があります。

    次の例は、OCI_PIPELINE_ABORT_ON_ERRORモードの使用方法を示しています。
    rc = OCIPipelineBegin(svchp, cbk, cbkCtx, errhp, OCI_PIPELINE_ABORT_ON_ERROR);
    ブロック・レベル・モードがOCI_PIPELINE_ABORT_ON_ERRORの場合、パイプライン操作の処理中にエラーが発生すると、パイプラインは異常終了します。

    パイプライン操作が終了するまで、サーバーは操作ごとにエラー(ORA-43610)を返します。

22.4 OCIPipelineOperation

OCIPipelineOperationは、パイプライン・ブロック内のパイプライン操作を表す不透明なハンドルです。OCIPipelineOperationには、実行されたOCI操作の情報が含まれます。操作インスタンスには、パイプライン操作の上(送信リクエスト)および下(受信レスポンス)の部分を完了するためのパラメータと状態が格納されます。

22.5 OCIパイプライン・ハンドルのライフ・サイクル

この項では、OCIパイプライン・ハンドルのライフ・サイクルについて説明します。

パイプライン操作ハンドルは、パイプライン操作ブロック内のOCI関数で始まります。操作の上半分(リクエストの送信)操作が実行されると、操作ハンドルの状態はOCI_PIPELINE_OP_SENTになります。レスポンスを受信すると、OCI_PIPELINE_OP_READY状態に変わります。操作が暗黙的または明示的に完了すると、登録されたコールバックが処理されます。

ノート:

アプリケーションには、操作の下半分(レスポンスを受信)が実行されるまでOCIコール・パラメータを有効にしておく責任があります。
アプリケーションは、前のOCIStmtExecute()関数の完了後にINバインドの値を変更できます。

ノート:

同じアウトバインドまたは定義パラメータは、操作の下半分(レスポンスを受信)の後にオーバーライドされます。

22.5.1 パイプライン操作のステータス

この項では、パイプライン操作ハンドルのステータスを取得する方法について説明します。

OCI_ATTR_PIPELINE_OP_STATUS

OCIAttrGet()属性を使用して、パイプライン操作ハンドルのステータスを取得できます。次のいずれかの値を使用できます。
  • OCI_PIPELINE_OP_READY
  • OCI_PIPELINE_OP_SENT

ノート:

すべての操作ハンドルは、OCIPipelineEnd()操作の実行後に解放されます。

22.6 OCIパイプライン属性

この項では、OCIパイプライン属性をリストし、説明します。

OCIパイプライン属性は次のとおりです。

OCI_ATTR_PIPELINE_PROCESS_FIRST

モード

READ

説明

サービス・コンテキストのパイプライン・キュー内の最初の操作を取得します。

属性のデータ型

OCIPipelineOperationID

例22-1 例:

OCIPipelineOperationID first;
status = OCIAttrGet (svchp, OCI_HTYPE_SVC, &first, NULL,
OCI_ATTR_PIPELINE_PROCESS_LAST, errhp);

OCI_ATTR_PIPELINE_PROCESS_LAST

モード

READ

説明

サービス・コンテキストのパイプライン・キュー内の最後の操作を取得します。

属性のデータ型

OCIPipelineOperationID

例22-2 例:

OCIPipelineOperationID last;
status = OCIAttrGet (svchp, OCI_HTYPE_SVC, &last, NULL,
OCI_ATTR_PIPELINE_PROCESS_LAST, errhp);

OCI_ATTR_PIPELINE_DEPTH

モード

書込み

説明

パイプライン・キューの深さは、パイプライン全体でこの属性の値に設定されます。

パイプラインのデフォルトの深さは256操作です。256操作の後、1つのレスポンスが処理されてから、別のリクエストがエンキューされます。このようにして、パイプライン・キューの深さが保持されます。

属性のデータ型
ub4 *

例22-3 例:

ub4 depth = 300;
status = OCIAttrSet (svchp, OCI_HTYPE_SVC, &depth, size of(depth), OCI_ATTR_PIPELINE_DEPTH, errhp);

OCI_ATTR_PIPELINE_HANDLE

モード

Read

説明

OCI_ATTR_PIPELINE_HANDLE属性は、実際のOCIハンドルを取得するために操作ハンドルで使用されます。操作で実際のハンドルを取得した後は、他のOCIハンドルに類似した属性を問い合せて属性を取得できます。

属性のデータ型

void *

例22-4 例:

OCIStmt *updateHandle; 
status = OCIAttrGet (operation, OCI_HTYPE_OPERATION, &updateHandle, NULL, OCI_ATTR_PIPELINE_HANDLE, errhp);

OCI_ATTR_PIPELINE_HANDLE_TYPE

モード

Read

説明

コールバック・アプリケーションは、OCI_ATTR_PIPELINE_HANDLE_TYPE属性から取得したハンドル・タイプを問い合せることができます。

これにより、コールバックでのプログラミング・パラダイムが容易になります。操作ハンドルを取得したら、実際のOCIハンドルとそのタイプを問い合せ、OCIハンドルの属性を取得します。

属性のデータ型
ub4 *

例22-5 例:

ub4 handleType = 0;status = OCIAttrGet (operation, OCI_HTYPE_OPERATION, &handleType, sizeof(handleType),
OCI_ATTR_PIPELINE_HANDLE_TYPE, errhp);

22.7 パイプライン化をサポートするOCI関数

パイプライン化をサポートするOCI関数のリストを次に示します。

ノート:

  • OCIStmtExecute()OCIStmtFetch()およびOCIStmtFetch2()リクエストにOCIオブジェクトが含まれている場合は、パイプライン化できません。
  • OCITransCommit()およびOCITransRollback()トランザクション・リクエスト・パイプライン化関数は、ローカル・トランザクションのみをサポートします。
  • OCIStmtExecute()
  • OCIStmtFetch()
  • OCIStmtFetch2()
  • OCITransCommit
  • OCITransRollback
  • OCILobAppend
  • OCILobArrayWrite
  • OCILobArrayRead
  • OCILobClose
  • OCILobCopy2
  • OCILobCreateTemporary
  • OCILobErase2
  • OCILobFileClose
  • OCILobFileCLoseAll
  • OCILobFileExists
  • OCILobFileIsOpen
  • OCILobFileOpen
  • OCILobFreeTemporary
  • OCILobGetChunkSize
  • OCILobGetLength2
  • OCILobOpen
  • OCILobIsOpen
  • OCILobLoadFromFile2
  • OCILobOpen
  • OCILobRead
  • OCILobRead2
  • OCILobTrim2
  • OCILobWrite
  • OCILobWrite2
  • OCILobWriteAppend2

22.8 パイプライン化機能を使用する場合

このセクションでは、パイプライン化機能を使用するタイミングについて説明します。

パイプライン化機能は、多数の小さな操作が連続して実行されている場合に便利です。

たとえば、次のようにします。
  • OCIStmtExecute
  • DDL、DML、問合せ、PL/SQL、配列挿入、戻り句を含むDML
  • 完全フェッチ、複数のフェッチ
  • 2つの異なる文ハンドルからのフェッチをパイプライン化

パイプライン化は、サーバーが遠い場合により高い効果があります。つまり、ネットワーク待機時間が長い場合です。

操作に、前の操作の結果に対する暗黙的または明示的な依存関係がある場合、パイプライン化の効果は低下します。このような場合、クライアントは同期ポイントを導入し、クライアントまたはサーバーの完全往復を待って必要な結果を取得する必要があります。

たとえば、次のようにします。
  • 暗黙的な依存関係: 文の実行後に、同じ文ハンドルに対するフェッチを実行
  • 明示的な依存関係: 前の操作の結果は、次の操作の実行をバインドするために使用