12 OCIでの高可用性

この章では、OCIでの高可用性(HA)機能について説明します。

実行時接続ロード・バランシング

ランタイム接続ロード・バランシングは、セッション・プール内で作業の処理に最も適切なセッションに作業リクエストをルーティングします。

これは既存のセッション・プールからセッションを選択する際に発生するため、頻度の高いアクティビティです。単一インスタンスでのみサービスをサポートするセッション・プールの場合は、プール内で使用可能な最初のセッションで十分です。プールが複数インスタンスにまたがるサービスをサポートしている場合は、適切なサービスを提供できる、または容量の大きいインスタンスが多数のリクエストを取得するように、作業リクエストをインスタンス間で分散する必要があります。

実行時接続ロード・バランシングを有効化するには、アプリケーションはOracle RACインスタンスに接続する必要があります。また、これらのアプリケーションで次を行う必要があります。

  • OCI_EVENTSモードでのOCI環境の初期化

  • 実行時接続ロード・バランシングが有効なサービスに接続します(DBMS_SERVICE.MODIFY_SERVICEプロシージャを使用してGOALおよびCLB_GOALの適切な値を設定します)

  • スレッド・ライブラリとのリンク

関連項目:

HAイベント通知

Oracle RACデータベースに接続された高可用性のクライアントにデータベース障害が発生したときに、HAイベント通知を使用して、ベストエフォート型プログラム・シグナルをクライアントに提供します。

バックエンド・データベース・サーバーにアクセスするアプリケーション・サーバーにログインするのにWebブラウザをユーザーが使用するとします。データベース・インスタンスに障害が発生した場合、障害がユーザーに通知されるまでに最大で数分かかる場合があります。HAイベント通知には、サーバー・インスタンスの障害を迅速に検出する機能、これをクライアントに通知する機能、および接続プールのアイドル状態の接続をクリーン・アップする機能が用意されています。

Oracle RACデータベースに接続された項可用性のクライアントの場合、データベース障害が発生したときに、HAイベント通知を使用して、ベストエフォート型プログラム・シグナルをクライアントに提供できます。クライアント・アプリケーションは、環境ハンドルにコールバックを登録し、この情報に興味があることを伝えます。このクライアントによって確立された接続に重大な障害イベントが発生すると、そのイベントに関する情報(イベント・ペイロード)や、障害によって切断された接続(サーバー・ハンドル)のリストとともにこのコールバックが呼び出されます。

たとえば、インスタンスAに対する2つの接続と、同じデータベースのインスタンスBに対する2つの接続を持つクライアント・アプリケーションを考えてみます。インスタンスAに障害が発生すると、イベント通知がクライアントに送信され、次に、クライアントにより、インスタンスBに対する2つの接続が切断され、かつ登録されているコールバックが呼び出されます。この場合、同じデータベースの別のインスタンスCに障害が発生しても、クライアントには通知されません(この障害がこのクライアントの接続に影響しないためです)。

HAイベント通知メカニズムにより、障害が発生した際のアプリケーションの応答時間が短縮されます。Oracle Database 10gリリース2 (10.2)でメカニズムが導入される以前は、障害が発生した場合、TCPタイムアウトの期限が切れるまで接続は切断されず、これには数分かかることがありました。HAイベント通知を使用すると、スタンドアロン、接続プールおよびセッション・プール接続はOCIによって自動的に切断されてクリーン・アップされ、障害イベントが発生してから数秒以内にアプリケーション・コールバックが呼び出されます。これらのサーバー・ハンドルのいずれかがTAFに対応している場合は、フェイルオーバーもOCIによって自動的に実行されます。

現在のリリースでは、この機能はOracle Notification Service (ONS)に応じて異なります。クライアントがONSを介してHA通知を受け取ることができるように、データベース・サーバー上にOracle Clusterwareをインストールして構成する必要があります。すべてのClusterwareインストール(たとえば、Oracle Data Guard)では、同じONSポートを指定する必要があります。ONSではクライアント構成は必要ありません。

ノート:

クライアントでは、接続されているデータベースからONSサーバー情報が透過的に取得されます。アプリケーションの管理者は、デプロイメント構成ファイルoraaccess.xmlを使用してこの情報を拡張するか、またはオーバーライドできます。

HAイベント通知を有効化するには、アプリケーションはOracle RACインスタンスに接続する必要があります。また、これらのアプリケーションで次を行う必要があります。

  • OCI_EVENTSモードでのOCI環境の初期化

  • 通知が有効化されたサービスへの接続(DBMS_SERVICE.MODIFY_SERVICEプロシージャを使用してAQ_HA_NOTIFICATIONSTRUEに設定する)

  • スレッド・ライブラリとのリンク

これにより、これらのアプリケーションはコールバックを登録でき、このコールバックが、HAイベントの発生のたびに呼び出されるようになります。

関連項目:

oraaccess.xmlの詳細は「oraaccess.xmlで指定されるクライアント側デプロイメント・パラメータについて」と、<events><fan>および<ons>のパラメータの詳細を参照してください

OCIEventハンドル

OCIEventハンドルは、イベント・ペイロードの属性をカプセル化します。

OCIでは、イベント・コールバックを呼び出す前にこのハンドルを暗黙的に割り当て、その結果イベント・コールバックでは、OCIAttrGet()を呼び出すことにより、イベントの読取り専用属性を取得できます。これらの属性に関連するメモリーが有効なのは、イベント・コールバック期間中のみです。

接続プールおよびセッション・プールのOCIフェイルオーバー

Oracle RACのインスタンス内の接続プールは、Oracle RACの別のインスタンスに接続されている接続のプールから構成されます。

ノード障害の通知を受け取った場合は、特定のインスタンスに接続されているすべての接続をクリーンアップする必要があります。接続が使用されている場合、OCIで接続を閉じる必要があります。これにより、即座に透過的アプリケーション・フェイルオーバー(TAF)が発生し、これらの接続が再確立されます。アイドル状態でプールの空きリストに入っている接続は消去する必要があるため、不適切な接続はプールからユーザーには戻されません。

カスタム接続プールに対応するために、OCIには、環境ハンドルに登録可能なコールバック関数が用意されています。登録すると、このコールバックはHAイベントが発生したときに呼び出されます。セッション・プールは、接続プールと同じように処理されます。OCI接続プールまたはセッション・プールのサーバー・ハンドルは、コールバックには渡されません。このため、場合によっては、空の接続リストを使用してコールバックが呼び出されることがあります。

独立した接続のOCIフェイルオーバー

独立した接続に対しては特別な処理は必要なく、障害の発生したインスタンスに接続されている接続はすべて即座に切断されます。

アイドル状態の接続に対しては、TAFにより、この接続が後続のOCIコールで使用された際に再確立されます。接続が失敗イベントで使用されている場合は即座に切断されるため、TAFを開始できます。これは、接続の「使用中」の接続およびセッション・プールにも当てはまります。

イベント・コールバック

OCIEventCallback型のイベント・コールバックのシグネチャを示します。

OCIEventCallback型のイベント・コールバックには、次のシグネチャがあります。

void evtcallback_fn (void      *evtctx,
                     OCIEvent  *eventhp );

このシグネチャでは、evtctxはクライアントのコンテキストで、OCIEventは、OCIライブラリに対しては不透明なイベント・ハンドルです。もう1つの入力引数は、イベント・ハンドルであるeventhpです(イベントに関連付けられている属性)。

この関数は、登録されると、イベントのたびに1回ずつ呼び出されます。Oracle RAC HAイベントの場合、このコールバックは、影響を受けた接続が切断されてから呼び出されます。次の環境ハンドル属性を使用して、イベントのコールバックとコンテキストをそれぞれ登録します。

  • OCI_ATTR_EVTCBKのデータ型はOCIEventCallback *です。これは読取り専用です。

  • OCI_ATTR_EVTCTXのデータ型はvoid *です。これも読取り専用です。

text *myctx = "dummy context"; /* dummy context passed to callback fn */
...
/* OCI_ATTR_EVTCBK and OCI_ATTR_EVTCTX are read-only. */
OCIAttrSet(envhp, (ub4) OCI_HTYPE_ENV, (void *) evtcallback_fn,
           (ub4) 0, (ub4) OCI_ATTR_EVTCBK, errhp);
OCIAttrSet(envhp, (ub4) OCI_HTYPE_ENV, (void *) myctx,
           (ub4) 0, (ub4) OCI_ATTR_EVTCTX, errhp);
...

OCIイベント・コールバック内では、影響を受けるサーバー・ハンドルのリストは、OCIEventハンドルにカプセル化されます。Oracle RAC HA DOWNイベントの場合、クライアント・アプリケーションは、属性タイプOCI_ATTR_HA_SRVFIRSTおよびOCI_ATTR_HA_SRVNEXTとともにOCIAttrGet()を使用することにより、イベントの影響を受けるサーバー・ハンドルのリストを反復できます。

OCIAttrGet(eventhp, OCI_HTYPE_EVENT, (void *)&srvhp, (ub4 *)0,
           OCI_ATTR_HA_SRVFIRST, errhp); 
/* or, */
OCIAttrGet(eventhp, OCI_HTYPE_EVENT, (void *)&srvhp, (ub4 *)0,
           OCI_ATTR_HA_SRVNEXT, errhp);

この関数は、属性OCI_ATTR_HA_SRVFIRSTを使用して呼び出されると、影響を受けるサーバー・ハンドルのリスト内の最初のサーバー・ハンドルを取得します。この関数は、属性OCI_ATTR_HA_SRVNEXTを使用して呼び出されると、リスト内の次のサーバー・ハンドルを取得します。戻すサーバー・ハンドルがなくなると、この関数はOCI_NO_DATAを戻し、srvhpNULLポインタになります。

srvhpは、HAイベントにより接続が閉じられたサーバー・ハンドルへの出力ポインタです。errhpは、移入されるエラー・ハンドルです。アプリケーションは、取得する影響を受けたサーバー・ハンドルがなくなると、OCI_NO_DATAエラーを戻します。

HAイベントの影響を受けたサーバー・ハンドルのリストを取得する場合、接続はクローズ済でありかつ多くのサーバー・ハンドル属性が無効になっていることに注意してください。かわりに、イベント通知コールバックに必要な接続ごとの属性を、サーバー・ハンドルのユーザー・メモリー・セグメントに格納します。このメモリーは、サーバー・ハンドルが解放されるまで有効です。

関連項目:

OCIAttrGet()

カスタム・プーリング: タグ付けされたサーバー・ハンドル

カスタム・プールを使用して、サーバー・ハンドルのタグ情報を取得できるため、適切なクリーンアップが実行できます。

次の機能は、カスタム・プールに適用されます。

  • サーバー・ハンドルをカスタム・プールのかわりに作成する場合、サーバー・ハンドルに親接続オブジェクトのタグを付けることができます。OCIHandleAlloc()のユーザー・メモリー・パラメータを使用して、ユーザー・メモリー・セグメントをサーバー・ハンドルに割り当てるよう要求します。ユーザー・メモリー・セグメントのポインタは、OCIHandleAlloc()によって戻されます。

  • HAイベントが発生し、影響を受けるサーバー・ハンドルが取得された場合、適切なクリーン・アップを実行できるようサーバー・ハンドルのタグ情報を取得するための方法があります。OCI_ATTR_USER_MEMORY属性を使用して、ハンドルのユーザー・メモリー・セグメントのポインタを取得します。OCI_ATTR_USER_MEMORYは、ユーザーが割り当てたすべてのハンドルに対して有効です。ハンドルに追加メモリーが割り当てられている場合、ユーザー・メモリーへのポインタが戻されます。追加メモリーが割り当てられていないハンドルに関しては、NULLポインタが戻されます。この属性は読取り専用で、データ型はvoid*です。

ノート:

サーバー・ハンドルのユーザー・メモリー・セグメントの正確な内容は自由に定義できます。OCIは、このメモリーからは書込みまたは読取りを行わないため、これにより、HAイベント・コールバックからのクリーン・アップ・アクティビティ(必要な場合は、その他の目的)が簡単になります。ユーザー・メモリー・セグメントは、サーバー・ハンドルに対するOCIHandleFree()のコールによって解放されます。

例12-1では、イベント通知の例を示しています。

例12-1 イベント通知

sword retval;
OCIServer *srvhp;
struct myctx {
   void *parentConn_myctx;
   uword numval_myctx;
};
typedef struct myctx myctx; 
myctx  *myctxp;
/* Allocate a server handle with user memory - pre 10.2 functionality */
if (retval = OCIHandleAlloc(envhp, (void **)&srvhp, OCI_HTYPE_SERVER,
                            (size_t)sizeof(myctx), (void **)&myctxp)
/* handle error */
myctxp->parentConn_myctx = <parent connection reference>;
 
/* In an event callback function, retrieve the pointer to the user memory */
evtcallback_fn(void *evtctx, OCIEvent *eventhp)
{ 
  myctx *ctxp = (myctx *)evtctx;
  OCIServer *srvhp;
  OCIError *errhp;
  sb4       retcode;
  retcode = OCIAttrGet(eventhp, OCI_HTYPE_SERVER, &srvhp, (ub4 *)0,
                       OCI_ATTR_HA_SRVFIRST, errhp); 
  while (!retcode) /* OCIAttrGet will return OCI_NO_DATA if no more srvhp */ 
  {  
     OCIAttrGet((void *)srvhp, OCI_HTYPE_SERVER, (void *)&ctxp,
                (ub4)0, (ub4)OCI_ATTR_USER_MEMORY, errhp);
           /* Remove the server handle from the parent connection object */
     retcode = OCIAttrGet(eventhp, OCI_HTYPE_SERVER, &srvhp, (ub4 *)0,
                          OCI_ATTR_HA_SRVNEXT, errhp);
...
  }
...
}

透過的アプリケーション・フェイルオーバー(TAF)機能の決定について

接続がTAFに対応している場合もいない場合も、アプリケーションにその動作を調整させることができます。

次のようにOCIAttrGet()を使用して、サーバー・ハンドルがTAFに対応しているかどうかを確認します。

boolean taf_capable;
...
OCIAttrGet(srvhp, (ub4) OCI_HTYPE_SERVER, (void *) &taf_capable, 
           (ub4) sizeof(taf_capable), (ub4)OCI_ATTR_TAF_ENABLED, errhp);
...

この例では、taf_capableブール変数で、サーバー・ハンドルがTAFに対応している場合はTRUE、対応していない場合はFALSEに設定されます。srvhpは、入力ターゲットのサーバー・ハンドルです。OCI_ATTR_TAF_ENABLEDは、ブール変数のポインタで読取り専用の属性です。errhpは、入力エラー・ハンドルです。

OCIでの透過的アプリケーション・フェイルオーバー

透過的アプリケーション・フェイルオーバー(TAF)はクライアント側の機能であり、インスタンスまたはネットワークの障害によりデータベース接続に失敗すると発生するエンドユーザー・アプリケーションに対する障害を最小限にするために設計されています。

TAFは、Oracle Real Application Clusters (Oracle RAC)およびOracle Data Guardフィジカル・スタンバイ・データベースなどの各種システム構成に実装できます。TAFは、単一インスタンス・システムの再起動後(修復時など)にも使用できます。

TAFは、データベース・セッションをリストアし、オプションでオープン問合せを再実行するよう構成できます。Oracle Database 10gリリース2 (10.2)以降では、障害後にアプリケーションが使用しようとしたすべての文はフェイルオーバーを試行します。つまり、他の文に対する実行またはフェッチの試行により、障害時文の場合と同様にTAFリカバリが実行されます。後続の文が今回成功する(前回は失敗)か、または試行されたTAFリカバリに対応するエラー(ORA-25401など)をアプリケーションで受信する可能性があります。

ノート:

フェイルオーバーの発生時にコールバックを使用してセッションを必要な状態にリストアできるように、アプリケーションでコールバックを登録することをお薦めします。

ノート:

リモート・データベース・リンクまたはDML文については、TAFはサポートされていません。

透過的アプリケーション・フェイルオーバーの構成について

TAFは、クライアント側とサーバー側のどちらでも構成できます。両方とも構成した場合、サーバー側の設定が優先されます。

クライアント側でTAFを構成するには、接続記述子のCONNECT_DATAの部分にFAILOVER_MODEパラメータを含めます。

サーバー側でTAFを構成するには、DBMS_SERVICE.MODIFY_SERVICEパッケージ・プロシージャによりターゲット・サービスを変更します。

最初のフェイルオーバーは成功しないことがあります。OCIでは、失敗した後にフェイルオーバーを再試行するメカニズムを提供しています。

関連項目:

OCIでの透過的アプリケーション・フェイルオーバー・コールバック

フェイルオーバー中に遅延が発生する可能性があるため、アプリケーション開発者は、フェイルオーバーが進行中であることをユーザーに知らせ、フェイルオーバーの完了通知をユーザーが待機するよう要求した方がよい場合があります。

さらに、最初のインスタンスのセッションで、いくつかのALTER SESSIONコマンドを受け取る可能性もあります。これらのALTER SESSIONコマンドが、2番目のインスタンスで自動的に再実行されることはありません。したがって、開発者は、これらを2番目のインスタンスで再実行する可能性があります。セッションに影響を与えるOCIAttrSet()コールも再実行する必要があります。

これらの要件に対応するため、アプリケーションの開発者は、フェイルオーバー・コールバック関数を登録できます。フェイルオーバーが発生すると、コールバック関数が、ユーザーのセッションを再確立する間に数回呼び出されます。

コールバック関数が最初にコールされるのは、データベースがインスタンスの接続中断を最初に検出したときです。このコールバックは、アプリケーションがユーザーに対して遅延の発生を通知することを目的にしています。フェイルオーバーが正常終了すると、コールバック関数の2番目のコールは、接続が再確立して使用可能になったときに発生します。

接続が再確立されると、クライアントではALTER SESSIONコマンドを再実行し、ユーザーにフェイルオーバーが発生したことを通知できます。フェイルオーバーが失敗すると、フェイルオーバーが発生しなかったことをアプリケーションに通知するために、コールバックがコールされます。さらに、1次ハンドル以外のユーザー・ハンドルが新しい接続で再認証されるたびに、コールバックがコールされます。各ユーザー・ハンドルはサーバー側セッションを表すため、クライアントでは、そのセッションのためにALTER SESSIONコマンドを再実行できます。

関連項目:

透過的アプリケーション・フェイルオーバー・コールバック構造およびパラメータ

TAFコールバック構造およびパラメータを説明します。

透過的アプリケーション・フェイルオーバー(TAF)・コールバック関数の基本的な構造は次のとおりです。

sb4  TAFcbk_fn(OCISvcCtx *svchp, 
               OCIEnv    *envhp, 
               void      *fo_ctx, 
               ub4        fo_type, 
               ub4        fo_event);
svchp

サービス・コンテキスト・ハンドルです。

envhp

OCI環境ハンドルです。

fo_ctx

クライアント・コンテキストです。これは、クライアントが指定するメモリーへのポインタです。この領域には、クライアントが必要な状態またはコンテキストを保管できます。

fo_type
フェイルオーバーの種類です。このパラメータにより、クライアントが要求したフェイルオーバーの種類をコールバックが判断できます。通常の値は次のとおりです。
  • OCI_FO_SESSION- ユーザーがセッションのフェイルオーバーのみを要求したことを示します。

  • OCI_FO_SELECT- ユーザーが選択フェイルオーバーも要求したことを示します。

fo_event
フェイルオーバー・イベントは、フェイルオーバーの現在のステータスを示します。
  • OCI_FO_BEGIN- フェイルオーバーが中断した接続を検出し、フェイルオーバーが開始することを示します。

  • OCI_FO_END- フェイルオーバーが正常終了したことを示します。

  • OCI_FO_ABORT- フェイルオーバーが失敗し、再試行できないことを示します。

  • OCI_FO_ERROR- 同様にフェイルオーバーが失敗したことを示しますが、アプリケーションでエラーを処理してフェイルオーバーを再試行する機会が与えられます。

  • OCI_FO_REAUTH- 複数の認証ハンドルがあり、最初の認証の後にフェイルオーバーが発生したことを示します。ユーザー・ハンドルが再認証されたことを示します。どのユーザー・ハンドルかを調べるために、アプリケーションで、サービス・コンテキスト・ハンドルsvchpOCI_ATTR_SESSION属性をチェックします。

アプリケーション・コンティニュイティが構成されている場合、TAFコールバックは、再接続、再認証、および処理中のトランザクションのステータスの判別を正常に行った後に、OCI_FO_ENDとともにコールされます。

TAFコールバックの完了時に、オープン・トランザクションが存在し、OCIのアプリケーション・コンティニュイティが有効になっている場合、OCIからエラーが戻されます。

フェイルオーバー・コールバック構造およびパラメータ

ユーザー定義アプリケーション・フェイルオーバー・コールバック関数の基本構造を示し、説明します。

ユーザー定義のアプリケーション・フェイルオーバー・コールバック関数の基本的な構造は次のとおりです。

sb4 appfocallback_fn ( void       * svchp, 
                       void       * envhp, 
                       void       * fo_ctx, 
                       ub4        fo_type, 
                       ub4        fo_event );

次のパラメータの例は、「フェイルオーバー・コールバックの例」(9‐31ページ)にあります。

svchp

最初のパラメータのsvchpは、サービス・コンテキスト・ハンドルです。これはvoid *型です。

envhp

2番目のパラメータのenvhpは、OCI環境ハンドルです。これはvoid *型です。

fo_ctx

3番目のパラメータのfo_ctxは、クライアント・コンテキストです。これは、クライアントが指定するメモリーへのポインタです。この領域には、クライアントが必要な状態またはコンテキストを保管できます。このパラメータはvoid *型です。

fo_type

4番目のパラメータのfo_typeは、フェイルオーバーの種類です。このパラメータにより、クライアントが要求したフェイルオーバーの種類をコールバックが判断できます。通常の値は次のとおりです。

  • OCI_FO_SESSION- ユーザーがセッションのフェイルオーバーのみを要求したことを示します。

  • OCI_FO_SELECT- ユーザーが選択フェイルオーバーも要求したことを示します。

fo_event

最後のパラメータは、フェイルオーバーのイベントです。このパラメータは、コールバックがコールされた理由を示します。次の値をとることができます。

  • OCI_FO_BEGIN- フェイルオーバーが中断した接続を検出し、フェイルオーバーが開始することを示します。

  • OCI_FO_END- フェイルオーバーが正常終了したことを示します。

  • OCI_FO_ABORT- フェイルオーバーが失敗し、再試行できないことを示します。

  • OCI_FO_ERROR- 同様にフェイルオーバーが失敗したことを示しますが、アプリケーションでエラーを処理してフェイルオーバーを再試行する機会が与えられます。

  • OCI_FO_REAUTH- 複数の認証ハンドルがあり、最初の認証の後にフェイルオーバーが発生したことを示します。ユーザー・ハンドルが再認証されたことを示します。どのユーザー・ハンドルかを調べるために、アプリケーションで、最初のパラメータであるサービス・コンテキスト・ハンドルのOCI_ATTR_SESSION属性をチェックします。

フェイルオーバー・コールバックの登録

フェイルオーバー・コールバックを使用するには、それをサーバー・コンテキスト・ハンドルに登録する必要があります。この登録は、コールバック定義構造体を作成し、サーバー・ハンドルのOCI_ATTR_FOCBK属性にこの構造体を設定することにより行います。

コールバック定義構造の型はOCIFocbkStructにする必要があります。ここには、2つのフィールドがあり、フィールドcallback_functionにはコールする関数のアドレスが含まれており、フィールドfo_ctxにはクライアント・コンテキストのアドレスが含まれています。

関連項目:

コールバック登録の例については、例12-3を参照してください。

フェイルオーバー・コールバックの例

フェイルオーバー・コールバックの例をいくつか示します。

この項では、簡単なユーザー定義コールバック関数の定義の例(例12-2を参照)、フェイルオーバー・コールバックの登録の例(例12-3を参照)およびフェイルオーバー・コールバックの登録解除の例(例12-4を参照)を示しています。

例12-2 ユーザー定義フェイルオーバー・コールバック関数の定義

sb4  callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event)
void * svchp;
void * envhp;
void *fo_ctx;
ub4 fo_type;
ub4 fo_event;
{
switch (fo_event) 
   {
   case OCI_FO_BEGIN:
   {
     printf(" Failing Over ... Please stand by \n");
     printf(" Failover type was found to be %s \n",
                     ((fo_type==OCI_FO_SESSION) ? "SESSION" 
                     :(fo_type==OCI_FO_SELECT) ? "SELECT"
                     : "UNKNOWN!")); 
     printf(" Failover Context is :%s\n", 
                    (fo_ctx?(char *)fo_ctx:"NULL POINTER!"));
     break;
   }
   case OCI_FO_ABORT:
   {
     printf(" Failover stopped. Failover will not occur.\n");
     break;
   }
   case    OCI_FO_END:
   {
       printf(" Failover ended ...resuming services\n");
     break;
   }
   case OCI_FO_REAUTH:
   {
       printf(" Failed over user. Resuming services\n");
     break;
   }
   default:
   {
     printf("Bad Failover Event: %d.\n",  fo_event);
     break;
   }
   }
   return 0;
}

例12-3 フェイルオーバー・コールバックの登録

int register_callback(srvh, errh)
void *srvh; /* the server handle */
OCIError *errh; /* the error handle */
{
  OCIFocbkStruct failover;                 /*  failover callback structure */
  /* allocate memory for context */
  if (!(failover.fo_ctx = (void *)malloc(strlen("my context.")+1)))
     return(1);
  /* initialize the context. */
  strcpy((char *)failover.fo_ctx, "my context.");
  failover.callback_function = &callback_fn;
  /* do the registration */
  if (OCIAttrSet(srvh, (ub4) OCI_HTYPE_SERVER,
                (void *) &failover, (ub4) 0,
                (ub4) OCI_ATTR_FOCBK, errh)  != OCI_SUCCESS)
     return(2);
  /* successful conclusion */
  return (0);
}

例12-4 フェイルオーバー・コールバックの登録解除

OCIFocbkStruct failover;   /*  failover callback structure */
sword status;
 
  /* set the failover context to null */
  failover.fo_ctx = NULL; 
  /* set the failover callback to null */ 
  failover.callback_function = NULL; 
  /* unregister the callback */
  status = OCIAttrSet(srvhp, (ub4) OCI_HTYPE_SERVER,
                      (void *) &failover, (ub4) 0,
                      (ub4) OCI_ATTR_FOCBK, errhp);

OCI_FO_ERRORの処理

フェイルオーバーは、成功しないことがあります。失敗した場合は、コールバック関数には、fo_eventパラメータにOCI_FO_ABORTまたはOCI_FO_ERRORの値が戻されます。

OCI_FO_ABORTの場合は、フェイルオーバーが失敗し、フェイルオーバーを続行できないことを示します。ただし、OCI_FO_ERRORは、エラーを処理する機会のあるコールバック関数を提供します。たとえば、一定時間待機してから、コールバックからOCIライブラリにフェイルオーバーの再試行を指示する必要があります。

ノート:

この機能が利用できるのは、Oracle Databaseサーバーに対して実行されている、リリース8.0.5以上のOCIライブラリにリンクしているアプリケーションのみです。

LOB列が選択リストの一部である場合、フェイルオーバーは機能しません。

表12-1で提示されているイベントの時系列について考えます。

表12-1 時間およびイベント

時間 イベント

T0

データベースに障害が発生します(障害はT5まで続きます)。

T1

ユーザーの操作によってフェイルオーバーが動作します。

T2

ユーザーが再接続を試行します。試行が失敗します。

T3

OCI_FO_ERRORによってフェイルオーバー・コールバックがコールされます。

T4

フェイルオーバー・コールバックは事前に定義されたスリープ状態になります。

T5

データベースがリストアされます。

T6

フェイルオーバー・コールバックによって新しいフェイルオーバーが動作し、成功します。

T7

ユーザーは再接続に成功します。

コールバック関数からOCI_FO_RETRYの値が戻されると、新しいフェイルオーバーが動作します。

例12-5では、前述のシナリオと類似したフェイルオーバー手法の実装に使用できるコールバック関数を示しています。この場合、フェイルオーバー・コールバックはループに入り、スリープ状態になってから、成功するまでフェイルオーバーを再試行します。

例12-5 フェイルオーバー手法を実装するコールバック関数

/*--------------------------------------------------------------------*/
/* the user-defined failover callback  */
/*--------------------------------------------------------------------*/
sb4  callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event )
void * svchp;
void * envhp;
void *fo_ctx;
ub4 fo_type;
ub4 fo_event;
{
   OCIError *errhp;
   OCIHandleAlloc(envhp, (void **)&errhp, (ub4) OCI_HTYPE_ERROR,
              (size_t) 0, (void **) 0);
   switch (fo_event) 
   {
   case OCI_FO_BEGIN:
   {
     printf(" Failing Over ... Please stand by \n");
     printf(" Failover type was found to be %s \n",
            ((fo_type==OCI_FO_NONE) ? "NONE"
             :(fo_type==OCI_FO_SESSION) ? "SESSION" 
             :(fo_type==OCI_FO_SELECT) ? "SELECT"
             :(fo_type==OCI_FO_TXNAL) ? "TRANSACTION"
             : "UNKNOWN!")); 
     printf(" Failover Context is :%s\n", 
            (fo_ctx?(char *)fo_ctx:"NULL POINTER!"));
     break;
   }
   case OCI_FO_ABORT:
   {
     printf(" Failover aborted. Failover will not occur.\n");
     break;
   }
   case    OCI_FO_END:
   { 
       printf("\n Failover ended ...resuming services\n");
     break;
   }
   case OCI_FO_REAUTH:
   { 
       printf(" Failed over user. Resuming services\n");
     break;
   }
   case OCI_FO_ERROR:
   {
     /* all invocations of this can only generate one line. The newline
      * will be put at fo_end time.
      */
     printf(" Failover error gotten. Sleeping...");
     sleep(3);
     printf("Retrying. ");
     return (OCI_FO_RETRY);
     break;
   }
   default:
   {
     printf("Bad Failover Event: %d.\n",  fo_event);
     break;
   }
   }
   return 0;
}

OCIとトランザクション・ガード

トランザクション・ガードでは、計画または未計画の停止があった場合に、アプリケーションがフェイルオーバー時に元の発行の重複発行を行わないように最大1回のトランザクションの実行をするという概念を紹介します。

アプリケーションでこのサービスを使用してデータベースへの接続がオープンされると、論理トランザクションID (LTXID)が認証時に生成され、セッション・ハンドルに格納されます。これはデータベース・トランザクションをアプリケーションの観点から識別する、グローバルに一意なIDです。停止したとき、トランザクション・ガードを使用しているアプリケーションは以前に失敗したセッションのハンドルからLTXIDを取得し、セッション障害が起こる前にアクティブだったトランザクションの結果をそれによって判定できます。LTXIDが使用されていないと判断される場合、アプリケーションでは、取得されたLTXIDを使用して最初に元の発行をブロックすることによりコミットされていないトランザクションを再実行できます。LTXIDが使用されていると判断される場合は、トランザクションがコミットされ、結果がアプリケーションに戻されます。

トランザクション・ガードは、JDBC Type 4 (Oracle Thin)、OCI、OCCIおよびOracle Data Provider for .NET (ODP.NET)ドライバにサポートされている開発者APIです。OCIの場合、トランザクション・ガードをサポートするようにアプリケーションが作成されていれば、停止時にOCIクライアント・ドライバでOCI_ATTR_LTXIDセッション・ハンドル属性を使用してOCI_ATTR_GET()をコールすることにより、以前に失敗したセッションのハンドルからLTXIDが取得されます。

この項には次のトピックが含まれます。「トランザクション・ガードと使用するアプリケーションの開発」

関連項目:

トランザクション・ガードの概要、サポートされているトランザクションの型、サポートされていないトランザクションの型、およびトランザクション・ガードの使用に関するデータベース構成情報は、『Oracle Database開発ガイド』のトランザクション・ガードの使用に関する章を参照してください。

トランザクション・ガードを使用するアプリケーションの開発

この項では、トランザクション・ガードを使用するOCIユーザー・アプリケーションの開発について説明します。

トランザクション・ガードを使用するアプリケーションの開発の詳細は、『Oracle Database開発ガイド』のトランザクション・ガードの使用に関する章を参照してください。

OCIでセッションのフェイルオーバーを可能にするためにサードパーティまたはユーザー・アプリケーションでトランザクション・ガードを使用するようにするには、次の主要なステップを含める必要があります。

  1. 接続に対して透過的アプリケーション・フェイルオーバー(TAF)が有効になっているかどうかを確認します。TAFでは自動的にLTXIDがチェックされるため、TAFに対応している接続でトランザクション・ガードを明示的に使用しないでください。
  2. エラー受信時に、OCI_ERRORハンドルのOCI_ATTR_ERROR_IS_RECOVERABLEで、エラーがリカバリ可能なエラーかどうかを判断します。エラーがリカバリ可能である場合は、ステップ3に進みます。

    ノート:

    接続がリカバリ可能なエラーの影響を受けていない場合は、LTXIDを使用してトランザクションの結果をチェックしないようにしてください。

  3. OCI_ATTR_GET()を使用してユーザー・セッション・ハンドルからOCI_ATTR_LTXIDを取得し、失敗したセッションと関連付けられているLTXIDを取り出します。
  4. データベースに再接続します。

    ノート:

    新しいセッションには新しいLTXIDが付きますが、元のセッションの状態をチェックする間、これは必要ありません。

  5. DBMS_APP_CONT.GET_LTXID_OUTCOME PL/SQLプロシージャをOCI_ATTR_GET()コールから取得したLTXIDを使用して起動します。フェイルオーバー・セッションの元のLTXIDが使用されていない場合、このLTXIDが強制済としてマークされます。戻り状態により、ドライバは最後のトランザクションがCOMMITTED (TRUE/FALSE)およびUSER_CALL_COMPLETED (TRUE/FALSE)であったことを確認します。
  6. アプリケーションで、コミットされていないトランザクションが再実行されるか、またはユーザーに結果が戻されます。リプレイ自体によって停止する場合は、リプレイ・セッションのLTXIDがDBMS_APP_CONT.GET_LTXID_OUTCOMEプロシージャに使用されます。
トランザクション・ガードの使用方法および例については、次の各項を参照してください。
典型的なトランザクション・ガードの使用例

擬似コードを使用したトランザクション・ガードの標準使用例を示します。

次の疑似コードは、トランザクション・ガードの標準使用例です。

  1. FAN DOWNイベント(またはリカバリ可能エラー)を受信します。

  2. FANで停止したセッションが異常終了されます。

  3. サーバー・ハンドルのOCI_ATTR_TAF_ENABLED属性を使用してOCIAttrGet()をコールします。値がTRUEの場合、停止します。値がFALSEの場合、次のステップに進みます。

  4. リカバリ可能なエラーであれば、OCI (OCI_ERRORハンドルのOCI_ATTR_ERROR_IS_RECOVERABLE)の場合、次のようにします。

    1. OCI_ATTR_LTXIDセッション・ハンドル属性を使用してOCIAttrGet()をコールすることにより停止したセッションから最後のLTXIDを取得し、このセッションのハンドルに関連付けられているLTXIDを取り出します。

    2. 新しいセッションを取得します。

    3. 最後のLTXIDを使用してDBMS_APP_CONT.GET_LTXID_OUTCOMEをコールし、戻り状態を取得します。

  5. 戻り状態が次の場合、

    1. COMMITTEDおよびUSER_CALL_COMPLETED

      結果が戻されます。

    2. ELSEIF COMMITTEDおよびNOT USER_CALL_COMPLETED

      結果が警告とともに戻されます(OUTバインドまたは行カウントが戻されなかった、などの詳細も含む)。

    3. ELSEIF NOT COMMITTED

      トランザクションまたは一連のコール、あるいは両方を再発行するか、ユーザーにエラーを戻します。

関連項目:

OCIAttrGet()

トランザクション・ガードの例

トランザクション・ガードのデモ・プログラムを示します。

例12-6は、次を示すOCIトランザクション・ガード・デモ・プログラム(cdemotg.c)です。

  • OCI_ATTR_ERROR_IS_RECOVERABLE属性の使用。エラー発生時に、エラーがリカバリ可能かどうかプログラムでチェックします。

  • DBMS_APP_CONT.GET_LTXID_OUTCOMEパッケージ・プロシージャの使用。エラーがリカバリ可能な場合、プログラムでDBMS_APP_CONT.GET_LTXID_OUTCOMEがコールされ、アクティブ・トランザクションの状態が判断されます。

トランザクションがコミットされていない場合、失敗したトランザクションがプログラムで再実行されます。

ノート:

このプログラムでは、NLSパラメータなどのセッション状態は修正されません。そのような操作を実行するプログラムでは、エラーに続き、プールから新しいセッションを取得した後でこれらのコマンドを再実行する必要がある場合があります。

例12-6 トランザクション・ガード・デモ・プログラム

*/
 
#ifndef OCISP_ORACLE
# include <cdemosp.h> 
#endif
 
/* Maximum Number of threads  */ 
#define MAXTHREAD 1
static ub4 sessMin = 1;
static ub4 sessMax = 9;
static ub4 sessIncr = 2;
 
static OCIError   *errhp;
static OCIEnv     *envhp;
static OCISPool   *poolhp=(OCISPool *) 0;
static int employeeNum[MAXTHREAD];
 
static OraText *poolName;
static ub4 poolNameLen;
static CONST OraText *database = (text *)"ltxid_service";
static CONST OraText *appusername =(text *)"scott";
static CONST OraText *apppassword =(text *)"tiger";
 
static CONST char getLtxid[]= 
  ("BEGIN DBMS_APP_CONT.GET_LTXID_OUTCOME ("
   ":ltxid,:committed,:callComplete); END;");
 
static CONST char insertst1[] = 
  ("INSERT INTO EMP(ENAME, EMPNO) values ('NAME1', 1000)");
 
static void checkerr (OCIError *errhp, sword status);
static void threadFunction (dvoid *arg);
 
int main(void)
{
  int i = 0;
  sword lstat;
  int timeout =1;
  OCIEnvCreate (&envhp, OCI_THREADED, (dvoid *)0,  NULL,
                NULL, NULL, 0, (dvoid *)0);
 
  (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR,
                        (size_t) 0, (dvoid **) 0);
 
  (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &poolhp, OCI_HTYPE_SPOOL,
                        (size_t) 0, (dvoid **) 0);
 
  /* Create the session pool */
  checkerr(errhp, OCIAttrSet((dvoid *) poolhp,
           (ub4) OCI_HTYPE_SPOOL, (dvoid *) &timeout, (ub4)0, 
           OCI_ATTR_SPOOL_TIMEOUT, errhp));
 
  if (lstat = OCISessionPoolCreate(envhp, errhp,poolhp, (OraText **)&poolName, 
              (ub4 *)&poolNameLen, database, 
              (ub4)strlen((const char *)database),
              sessMin, sessMax, sessIncr,
              (OraText *)appusername,
              (ub4)strlen((const char *)appusername),
              (OraText *)apppassword,
              (ub4)strlen((const char *)apppassword),
              OCI_SPC_STMTCACHE|OCI_SPC_HOMOGENEOUS))
  {
    checkerr(errhp,lstat);
  }
 
  printf("Session Pool Created \n");
 
  /* Multiple threads using the session pool */
  {
    OCIThreadId *thrid[MAXTHREAD];
    OCIThreadHandle *thrhp[MAXTHREAD];
 
    OCIThreadProcessInit ();
    checkerr (errhp, OCIThreadInit (envhp, errhp));
    for (i = 0; i < MAXTHREAD; ++i)
    {
      checkerr (errhp, OCIThreadIdInit (envhp, errhp, &thrid[i]));
      checkerr (errhp, OCIThreadHndInit (envhp, errhp, &thrhp[i]));
    }
    for (i = 0; i < MAXTHREAD; ++i)
    {
      employeeNum[i]=i;
      /* Inserting into EMP table */
      checkerr (errhp, OCIThreadCreate (envhp, errhp, threadFunction,
                (dvoid *) &employeeNum[i], thrid[i], thrhp[i]));
    }
    for (i = 0; i < MAXTHREAD; ++i)
    {
      checkerr (errhp, OCIThreadJoin (envhp, errhp, thrhp[i]));
      checkerr (errhp, OCIThreadClose (envhp, errhp, thrhp[i]));
      checkerr (errhp, OCIThreadIdDestroy (envhp, errhp, &(thrid[i])));
      checkerr (errhp, OCIThreadHndDestroy (envhp, errhp, &(thrhp[i])));
    }
    checkerr (errhp, OCIThreadTerm (envhp, errhp));
  } /* ALL THE THREADS ARE COMPLETE */
  lstat =  OCISessionPoolDestroy(poolhp, errhp, OCI_DEFAULT);
 
  printf("Session Pool Destroyed \n");
  
  if (lstat != OCI_SUCCESS)
    checkerr(errhp, lstat);
    
  checkerr(errhp, OCIHandleFree((dvoid *)poolhp, OCI_HTYPE_SPOOL));
    
  checkerr(errhp, OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR));
  return 0;
 
} /* end of main () */
 
/* Inserts records into EMP table */ 
static void threadFunction (dvoid *arg)
{
  int empno = *(int *)arg;
  OCISvcCtx *svchp = (OCISvcCtx *) 0;
  OCISvcCtx *svchp2 = (OCISvcCtx *) 0;
  OCISession *embUsrhp = (OCISession *)0;
  OCIBind *bnd1p, *bnd2p, *bnd3p;
 
  OCIStmt *stmthp = (OCIStmt *)0;
  OCIStmt *getLtxidStm = (OCIStmt *)0;
  OCIError  *errhp2 = (OCIError *) 0;
  OCIAuthInfo *authp = (OCIAuthInfo *)0;
  sword lstat;
  text name[10];
 
  boolean callCompl, committed, isRecoverable;
  ub1 *myLtxid;
  ub4  myLtxidLen;
 
  ub4 numAttempts = 0;
 
  (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp2, OCI_HTYPE_ERROR,
                     (size_t) 0, (dvoid **) 0);
 
  lstat =  OCIHandleAlloc((dvoid *) envhp,
                          (dvoid **)&authp, (ub4) OCI_HTYPE_AUTHINFO,
                          (size_t) 0, (dvoid **) 0);
  if (lstat)
    checkerr(errhp2, lstat);
 
  checkerr(errhp2, OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, 
           (dvoid *) appusername, (ub4) strlen((char *)appusername),
           (ub4) OCI_ATTR_USERNAME, errhp2));
 
  checkerr(errhp2,OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, 
           (dvoid *) apppassword, (ub4) strlen((char *)apppassword),
           (ub4) OCI_ATTR_PASSWORD, errhp2));
 
restart:
  if  (lstat = OCISessionGet(envhp, errhp2, &svchp, authp,
               (OraText *)poolName, (ub4)strlen((char *)poolName), NULL, 
               0, NULL, NULL, NULL, OCI_SESSGET_SPOOL))
  {
    checkerr(errhp2,lstat);
  } 
  
  /* save the ltxid from the session in case we need to call
   *  get_ltxid_outcome to determine the transaction status.
   */
  checkerr(errhp2, OCIAttrGet(svchp, OCI_HTYPE_SVCCTX,
                              (dvoid *)&embUsrhp, (ub4 *)0,
                              (ub4)OCI_ATTR_SESSION, errhp2));
  checkerr(errhp2, OCIAttrGet(embUsrhp, OCI_HTYPE_SESSION,
                              (dvoid *)&myLtxid, (ub4 *)&myLtxidLen,
                              (ub4)OCI_ATTR_LTXID, errhp2));
  
 
  /* */
  checkerr(errhp2, OCIStmtPrepare2(svchp, &stmthp, errhp2, 
                                   (CONST OraText *)insertst1, 
                                   (ub4)sizeof(insertst1),
                                   (const oratext *)0, (ub4)0,
                                   OCI_NTV_SYNTAX, OCI_DEFAULT));
 
  if (!numAttempts)
  {
    char input[1];
 
    printf("Kill SCOTT's session now. Press ENTER when complete\n");
    gets(input);
  }
  lstat = OCIStmtExecute (svchp, stmthp, errhp2, (ub4)1, (ub4)0,
                          (OCISnapshot *)0, (OCISnapshot *)0, 
                          OCI_DEFAULT );
  if (lstat == OCI_ERROR)
  {
    checkerr(errhp2, OCIAttrGet(errhp2, OCI_HTYPE_ERROR,
                                (dvoid *)&isRecoverable, (ub4 *)0,
                                (ub4)OCI_ATTR_ERROR_IS_RECOVERABLE, errhp2));
    if (isRecoverable)
    {
 
      printf("Recoverable error occurred; checking transaction status.\n");
      /* get another session to use for the get_ltxid_outcome call */
      if  (lstat = OCISessionGet(envhp, errhp2, &svchp2, authp,
                                 (OraText *)poolName, 
                                 (ub4)strlen((char *)poolName), NULL, 
                                 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL))
      {
        checkerr(errhp2,lstat);
      } 
      
      checkerr(errhp2,OCIStmtPrepare2(svchp2,&getLtxidStm, errhp2, 
                                      (CONST OraText *)getLtxid,
                                      (ub4)sizeof(getLtxid),
                                      (const oratext *)0, (ub4)0,
                                      OCI_NTV_SYNTAX, OCI_DEFAULT));
      checkerr(errhp, OCIBindByPos(getLtxidStm, &bnd1p, errhp, 1,
                                   (dvoid *) myLtxid, (sword)myLtxidLen,
                                   SQLT_BIN, (dvoid *)0,
                                   (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0,
                                   OCI_DEFAULT));
      checkerr(errhp, OCIBindByPos(getLtxidStm, &bnd2p, errhp, 2,
                                   (dvoid *) &committed, 
                                   (sword)sizeof(committed),
                                   SQLT_BOL, (dvoid *)0,
                                   (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0,
                                   OCI_DEFAULT));
      checkerr(errhp, OCIBindByPos(getLtxidStm, &bnd3p, errhp, 3,
                                   (dvoid *) &callCompl, 
                                   (sword)sizeof(callCompl),
                                   SQLT_BOL, (dvoid *)0,
                                   (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0,
                                   OCI_DEFAULT));
      
      checkerr(errhp2,OCIStmtExecute(svchp2, getLtxidStm, errhp2, 
                                     (ub4)1, (ub4)0,
                                     (OCISnapshot *)0, (OCISnapshot *)0, 
                                     OCI_DEFAULT ));
      checkerr(errhp2, OCISessionRelease(svchp2, errhp2, 
                                         NULL, 0, OCI_DEFAULT));
      if (committed && callCompl)
        printf("Insert successfully commited \n");
      else if (!committed)
      {
        printf("Transaction did not commit; re-executing last transaction\n");
        numAttempts++;
 
        /* As there was an outage, do not return this session to the pool */
        checkerr(errhp2, 
                 OCISessionRelease(svchp, errhp2, 
                                   NULL, 0, OCI_SESSRLS_DROPSESS));
        svchp = (OCISvcCtx *)0;
        goto restart;
      }
    }
  }
  else
  {
    checkerr(errhp2, OCITransCommit(svchp,errhp2,(ub4)0));
    printf("Transaction committed successfully\n");
  }
  if (stmthp)
    checkerr(errhp2, OCIStmtRelease((dvoid *) stmthp, errhp2,
                                    (void *)0, 0, OCI_DEFAULT));
  if (getLtxidStm)
    checkerr(errhp2, OCIStmtRelease((dvoid *) getLtxidStm, errhp2,
                                    (void *)0, 0, OCI_DEFAULT));
 
  if (svchp)
    checkerr(errhp2, OCISessionRelease(svchp, errhp2, NULL, 0, OCI_DEFAULT));
  OCIHandleFree((dvoid *)authp, OCI_HTYPE_AUTHINFO);
  OCIHandleFree((dvoid *)errhp2, OCI_HTYPE_ERROR);
 
} /* end of threadFunction (dvoid *) */
 
/* This function prints the error */
void checkerr(errhp, status)
OCIError *errhp;
sword status;
{
  text errbuf[512];
  sb4 errcode = 0;
 
  switch (status)
  {
  case OCI_SUCCESS:
    break;
  case OCI_SUCCESS_WITH_INFO:
    (void) printf("Error - OCI_SUCCESS_WITH_INFO\n");
    break;
  case OCI_NEED_DATA:
    (void) printf("Error - OCI_NEED_DATA\n");
    break;
  case OCI_NO_DATA:
    (void) printf("Error - OCI_NODATA\n");
    break;
  case OCI_ERROR:
    (void) OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
                       errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
    (void) printf("Error - %.*s\n", 512, errbuf);
    break;
  case OCI_INVALID_HANDLE:
    (void) printf("Error - OCI_INVALID_HANDLE\n");
    break;
  case OCI_STILL_EXECUTING:
    (void) printf("Error - OCI_STILL_EXECUTE\n");
    break;
  case OCI_CONTINUE:
    (void) printf("Error - OCI_CONTINUE\n");
    break;
  default:
    break;
  }
}

OCIとアプリケーション・コンティニュイティ

アプリケーション・コンティニュイティ(AC)により、計画済停止および計画外停止時の高可用性(HA)が得られます。OCIでのACサポートは、Oracle Database12cリリース2 (12.2)で導入されました。

ACは、Oracle RAC、Oracle RAC One、またはActive Data Guardをインスタンスまたはサイトのフェイルオーバー用に実行しているHA環境でハードウェア、ソフトウェア、ネットワーク、ストレージのエラーとタイムアウトをマスキングします。ACでは、SQL*Plus、Tuxedo、WebLogic Serverに加え、JDBC Type 4 (Oracle Thin)、OCIおよびOracle Data Provider for .NET (ODP.NET)ドライバに対するサポートが提供されます。

OCIセッション・プールを使用するアプリケーションの計画済停止では、OCIセッション・プールは、接続がPLANNED DOWNイベントによって影響を受けた時点を検出し、接続がプールに戻されると同時に接続を終了します。OCIセッション・プールを使用しないアプリケーションの計画済停止では、OCIアプリケーションは、接続が計画済停止イベントによって影響を受けた時点を検出します。どちらの場合でも、OCIは、DMLリプレイが安全である時点を暗黙的に判断するため、停止イベント後のエラーの発生数は抑制されます。

計画外停止では、OCIは、トランザクション・ガードを使用して、リカバリ可能なエラーの発生後に処理中のトランザクションをリカバリすることで、OCIアプリケーションがトランザクションの結果を確実に判別できるようにします。つまり、このサポートによって、停止中のアプリケーション要求の完了時に発生するデータベース接続性とセッション状態のリストアによる遅延はごくわずかとなります。ACは、当初の実行の最中にトランザクションがコミットしていなかったことを判別できる場合にのみ、処理中のトランザクションのリプレイを試みます。

OCIのACサポートでは、OCIセッション・プールまたはTuxedoを使用することをお薦めします。

関連項目:

アプリケーション・コンティニュイティの追加サポートについて

Oracle Databaseリリース18c、バージョン18.1には、アプリケーション・コンティニュイティのさらなるサポートが追加されています。

Oracle Databaseリリース18c、バージョン18.1以上では、次のサポートがアプリケーション・コンティニュイティに追加されています。
  • OCI動的バインドのサポートが追加され、数値、文字、日付/時刻データ型を定義します。これは、OCI APIのOCIBindDynamic()およびOCIDefineDynamic()がアプリケーション・コンティニュイティをサポートするように拡張されていることを意味します。

  • オブジェクトのバインドおよび定義のサポートが追加されています。これは、OCI APIのOCIBindObject()OCIDefineObject()およびOCITypeByName()がアプリケーション・コンティニュイティをサポートするように拡張されていることを意味します。

  • LOBコールの実行時に、アプリケーション・コンティニュイティは、停止によって中断されたLOBコールを再開することで、接続失敗の処理をサポートするようになりました。

  • OCIはAUTOの新しいアプリケーション・コンティニュイティFAILOVER_TYPEをサポートするようになりました。これは、セッション・ステートが明示的なリクエスト境界でリストア可能と認識された場合、フェイルオーバーのみを試行します。

リカバリ可能なエラーの後に発生すること

リカバリ可能なエラーの発生後、データベース・セッションは、あるデータベース・インスタンスから別のデータベース・インスタンスにフェイルオーバーされます。

新しいインスタンスは、同じOracle RACクラスタの一部である場合も、サイトの障害後にプライマリ・データベースとして起動されたOracle Data Guardスタンバイ・データベースである場合もあります。透過的アプリケーション・フェイルオーバー(TAF)が正常に再接続および再認証を行った後、OCIのアプリケーション・コンティニュイティによって、すべてのSQLおよびPL/SQL文を含め、失敗したセッションに関連するコール履歴がリプレイされます。リプレイは、単一セッション上で動作し、再発行アクティビティを他のデータベース・セッションと同期することはありません。リプレイが成功するのは、トランザクション・リプレイについてクライアントが認識する結果が、元の発行と同一である場合のみです。

正常なリプレイのための基準

ドライバのリプレイが成功するには、フェイルオーバー後のトランザクションについてクライアントが認識する結果が、最初の発行と同一である必要があります。

この成功は、次の基準で示されます。

  • リターン・コードとエラー・メッセージ・テキストは、同一である必要があります。

  • 結果セットは、同一である必要があります。定義データは同一で、行は同じ順序で戻される必要があります。

  • 処理された行数は、同一である必要があります。たとえば、フェイルオーバー後の更新文では、元の更新文と同じ数の行を更新する必要があります。

  • 新規接続のセッション状態は、元の接続のセッション状態と一致します。

これらの基準の詳細は、『Oracle Real Application Clusters管理およびデプロイメント・ガイド』を参照してください。

可変データおよびアプリケーション・コンティニュイティの安定性

可変オブジェクトの値が、ある実行から次の実行までに変化した場合、そのデータは、可変であるとみなされるため、リプレイ不可であることが保証されます。順序は、この可変データの例です。

DMLリプレイの成功率を向上するには、最初の発行で使用された値を持つ可変データを含むDMLをリプレイする必要があります。元の値が保持されておらず、これらの可変オブジェクトの別の値がクライアントに戻される場合、クライアントが認識する値が異なるため、リプレイは拒否されます。

可変オブジェクト値を保持するためのサポートは現在、SYSDATESYSTIMESTAMPSYS_GUIDおよびsequence.NEXTVALに対して提供されています。

関連項目:

可変オブジェクトおよびアプリケーション・コンティニュイティの詳細は、『Oracle Real Application Clusters管理およびデプロイメント・ガイド』を参照してください。

OCIのアプリケーション・コンティニュイティを無効化する要因

その後のアプリケーション要求が開始するまで、OCIのアプリケーション・コンティニュイティを暗黙的に無効化する要因を示します。

次の状況では、その後のアプリケーション要求が開始するまで、OCIのアプリケーション・コンティニュイティは暗黙的に無効化されます。

  • リプレイと一貫性のない状況をサーバーが検出した場合。たとえば、SESSION_STATE_CONSISTENCY=DYNAMICの状態で、PL/SQL無名ブロックに埋込みのトップレベルCOMMIT文が含まれる場合(自律型トランザクションはトップレベルとはみなされません)、ドライバは、OCIのアプリケーション・コンティニュイティを暗黙的に無効化します。

  • OCIのアプリケーション・コンティニュイティでサポートされないOCI関数をアプリケーションがコールした場合。

アプリケーションでは、OCIRequestDisableReplay()をコールすることで、OCIのアプリケーション・コンティニュイティを明示的に無効化できます。

リプレイの失敗

リプレイが失敗する原因を説明します。

OCIのアプリケーション・コンティニュイティがトランザクションをリプレイする場合、次の状況ではリプレイに失敗します。

  • リプレイ時にCOMMIT文が実行された場合

  • リプレイ結果がトランザクションの最初の発行と一貫性がない場合

  • 最初のリプレイの再試行制限を超えている状態で、リプレイ中にリカバリ可能なエラーが発生した場合

  • OCIStmtPrepare()を使用するアプリケーションが「エラー - ORA-25412: OCIStmtPrepareへのコールにより、トランザクションの再実行が無効化されています」というエラーを戻した場合。HAインフラストラクチャでアプリケーション・コンティニュイティの使用をサポートするには、OCIStmtPrepare2()コールを使用します。

アプリケーション・コンティニュイティは、失敗したトランザクションを正常にリプレイできない場合、エラーを戻します。リプレイの失敗の理由を示すため、クライアント側のトレース・ファイルに追加の診断情報が記録されます。

アプリケーション・コンティニュイティが最も効果的な状況

OCIのアプリケーション・コンティニュイティの有効性を決定する条件を説明します。

OCIのアプリケーション・コンティニュイティは、次の状況で最も効果的です。

  • データベース・サービスでCOMMIT_OUTCOME属性が指定され、透過的アプリケーション・フェイルオーバー(TAF)が構成されています。

  • 明示的に(OCIRequestBegin()およびOCIRequestEnd()をコールして)、またはOCIセッション・プールの使用を通じて暗黙的に、アプリケーションでアプリケーション要求の開始と終了をマークできます。

  • アプリケーション要求に、その要求の最後にコミットされる1つ以下のデータベース・トランザクションが含まれます。

  • アプリケーションがサーバーでPL/SQLまたはJavaを実行する場合、そのPL/SQLまたはJavaの動作が次のとおりです。
    • 埋込みCOMMIT文が含まれません

    • PL/SQLまたはJavaの完了後に永続化される必要のある状態(パッケージ変数など)を設定しません。

  • TAFコールバックがオープン・データベース・トランザクションを後に残しません。

OCIのアプリケーション・コンティニュイティがフェイルオーバーできる場合

停止が発生した場合にOCIのアプリケーション・コンティニュイティがフェイルオーバーできる関数について説明します。

OCIのアプリケーション・コンティニュイティは、次のいずれかの関数の実行時に停止が発生した場合にフェイルオーバーできます。
  • OCILobAppend()

  • OCILobArrayRead()

  • OCILobArrayWrite()

  • OCILobAssign()

  • OCILobCharSetForm()

  • OCILobClose()

  • OCILobCopy2()

  • OCILobCreateTemporary()

  • OCILobDisableBuffering()

  • OCILobEnableBuffering()

  • OCILobFileClose()

  • OCILobFileCloseAll()

  • OCILobFileGetName()

  • OCILobFileIsOpen()

  • OCILobFileOpen()

  • OCILobFileSetName()

  • OCILobFlushBuffer()

  • OCILobFreeTemporary()

  • OCILobGetChunkSize()

  • OCILobGetLength()

  • OCILobGetLength2()

  • OCILobGetStorageLimit()

  • OCILobIsEqual()

  • OCILobIsOpen()

  • OCILobIsTemporary()

  • OCILobLoadFromFile()

  • OCILobLoadFromFile2()

  • OCILobLocatorAssign()

  • OCILobLocatorIsInit()

  • OCILobOpen()

  • OCILobRead()

  • OCILobRead2()

  • OCILobTrim()

  • OCILobTrim2()

  • OCILobWriteAppend()

  • OCILobWriteAppend2()

  • OCILobWrite()

  • OCILobWrite2()

  • OCIPing()

  • OCIStmtExecute()

  • OCIStmtFetch()

  • OCIStmtFetch2()

  • OCISessionEnd()

  • OCITransCommit()

  • OCITransRollback()

OCIのアプリケーション・コンティニュイティでサポートされない構成要素

OCIのアプリケーション・コンティニュイティでサポートされない構成要素を説明します。

OCIのアプリケーション・コンティニュイティでは、次の構成要素はサポートされません。

  • XAトランザクション

  • 埋込みCOMMIT文を含むPL/SQLブロック

  • デキュー即時モードのAQデキュー(deqopt.visibility)

  • オブジェクトやLOBロケータなどの記述子ベース・タイプのストリーム・バインドまたは定義

  • 関数OCIStmtPrepare()

  • OCI_CONTINUEを戻さないOCI_CBTYPE_ENTRY型の登録済OCIコールバック

  • COMMIT NOWAIT

  • DCLコマンド

アプリケーション・コンティニュイティで発生する可能性のある副作用

OCIのアプリケーション・コンティニュイティは、セッションが再構築されてデータベース状態がリストアされると、リカバリ可能なエラーの後に元のPL/SQLおよびSQL文をリプレイします。リプレイによって、副作用が2回発生しますが、これは望ましい場合と望ましくない場合があります。

アプリケーションでは、これらの副作用を理解して、重複する実行を受け入れることができるかどうかを判断することが重要です。受け入れることができない場合、リプレイの影響を受容または緩和するためのアクションをアプリケーションで実行する必要があります。たとえば、OCIRequestDisableReplay()をコールします。

関連項目:

副作用を発生させるアクションの例は、『Oracle Real Application Clusters管理およびデプロイメント・ガイド』を参照してください。