>> 連載トップページに戻る

 

基本からわかる!高性能×高可用性データベースシステムの作り方

第5回 高可用性構成でのOracle Net構成(2)


著者紹介


日下部 明 (くさかべ あきら)

日本オラクル Oracle Database担当。Oracle GRID Centerのラインマネージャとしてオラクルの持つ最新技術をパートナー各社と共同で検証し、多くのホワイトペーパーを執筆・レビューしてきました。以後、Oracle Databaseのセキュリティ製品のリリースマネージャを担当。これらの経験を元にミッションクリティカルな案件のソリューションデザインの提案などを担当しています。著書に「これは使えるOracle新機能活用術」(翔泳社)。


第5回 高可用性構成でのOracle Net構成(2)


データベース・サーバーの高可用性構成にはクラスタやレプリケーションの技術があります。これらの構成を活かすには、データベース・クライアント側の対応も必要です。第4回ではOracle Netの接続時フェイルオーバーの機能を解説しました。これは新規の物理コネクションを確立するときに検出された障害に対するフェイルオーバー機能です。データベース・サーバーの障害に対しては、別の場合分けがまだ必要です。第5回は確立済みコネクションの挙動とトランザクションの自動再実行について解説します。


1 Oracleサーバー障害時の挙動

OracleクライアントがOracleサーバーに接続し、更新トランザクションを実行しているとします。一般的に、1つのトランザクションは複数のSQLから構成されています。もし、更新トランザクションの途中でOracleサーバーが異常終了してしまうと、Oracleクライアントには何らかのエラーが返ります。アプリケーションのコードの中では、このエラーで異常終了したことを検出します。

Oracleサーバー側では、トランザクションの途中まで更新されたデータブロックは一貫性のある状態にロールバックされなければなりません。Oracleクライアントが接続している特定のOracleサーバー・プロセスに障害が発生した場合は、Oracleインスタンスのバックグラウンド・プロセスによって自動的にロールバックされます。シングル・インスタンス構成でOracleインスタンス自体に障害が発生した場合は、Oracleインスタンスが再起動されたときに自動的にリカバリされます。複数のOracleインスタンスが1つのデータベースをマウントしているOracle Real Application Clusters構成では、Oracleインスタンス障害が発生した場合は、残りの正常インスタンスによって自動的にリカバリされます。

2 Oracleコネクションの切断検出

Oracleサーバーに障害が発生した場合、SQL実行中のOracleクライアントにはエラーが返りますが、そのタイミングはいつでしょうか。障害の種類によって、実際に障害が発生した時刻から検出されるまでの時間は大きな開きがあります。

Oracleサーバー・プロセスやOracleインスタンスの障害では、OSは正常に稼働しているという前提で、Oracleクライアントが生成しているTCP/IPコネクションはOracleサーバー側から明示的に切断処理されます。そのため、アプリケーションはごく短時間でエラーを検出できます。

しかし、OracleサーバーのOS障害などの場合、TCP/IPのレベルで応答がなくなります。OracleクライアントはTCP/IPの応答を「待つ」という挙動になり、エラー検出時間に大きな影響を与えます。OracleクライアントがTCP/IPのレベルで送信中にOracleサーバーのOS障害が発生すると、TCP/IPの送信タイムアウトを待つか、Oracle Grid Infrastructureなどのクラスタウェアを使用している場合は仮想IPアドレスのフェイルオーバーを待つことで、TCP/IPコネクションの切断を検出することになります。しかし、OracleクライアントがTCP/IPのレベルで受信待ち中にOracleサーバーのOS障害が発生すると、受信待ちTCPソケットは永遠に受信待ちになってしまいます。これを回避するために、Oracle9iまではOracleクライアント側でTCP keepaliveを有効にし、OSのTCP keepaliveタイマーの調整を実施するということを行っていました。

Oracle NetでTCP keepaliveを有効にするには、接続記述子にENABLE=BROKENと設定します。この設定ははるかOracle8のころにはすでに存在していたのですが、マニュアルには記載されておらず、裏技的な扱いでした。マニュアルに記載されたのはOracle Database 11g Release 1からです。

(DESCRIPTION =
  (ENABLE=BROKEN)
  (ADDRESS = (PROTOCOL = TCP)(HOST = hostname)(PORT = port))
  (CONNECT_DATA =
     (SERVICE_NAME = service)
  )
)

このTCP/IPレベルでの応答待ちの問題を解決するため、Oracle Database 10g Release 1で高速接続フェイルオーバー(Fast Connection Failover: FCF)という仕組みが導入されました。

2.1 高速接続フェイルオーバー

高速接続フェイルオーバー(FCF)はOracle Grid InfrastructureとOracleクライアントの連係機能です。この連係機能は高速アプリケーション通知(Fast Application Notification: FAN)と呼ばれるイベント通知経路を持ちます。FAN対応Oracleクライアントは、SQLを実行するためのコネクションとは別に、Oracle Grid Infrastructureとのコネクションを持ちます。Oracle Grid Infrastructureはノード障害を検出すると、正常ノードからFANを通じてノード障害(NODE DOWNイベント)を明示的にOracleクライアントに通知します。Oracleクライアントはこの通知でOracleサーバーの障害を知ることができ、アプリケーションにエラーを返します。この仕組みを高速接続フェイルオーバーといいます。FANに対応したOracleクライアントには、Universal Connection Pool for Java(UCP)、ODP.NET、OCIがあります。

FANの通知経路は、Oracle Database 12c Release 2までは、JavaではOracle Notification Services(ONS)で、OCIとODP.NETはOracle Advanced Queuingでしたが、Oracle Database 18cからはONSに統一されました。

img-1

高速接続フェイルオーバーではNODE DOWNイベント以外にも、サービスがOracleインスタンスで起動/停止したことを表すSERVICE UP/DOWNイベントも通知します。そして、SERVICE DOWNイベントは障害によってサービスが停止した(reason=failure)のか、管理者がsrvctlコマンドで意図して停止させた(reason=user)のかを区別します。障害によってサービスが停止した場合、FAN対応Oracleクライアントは即座にそのOracleインスタンスと接続していた物理コネクションをクリーンアップします。しかし、管理者がsrvctlコマンドで停止させた場合、FAN対応Oracleクライアントはアプリケーションが論理コネクションをコネクション・プールに返却するのを待ってから物理コネクションをクリーンアップします。そのため、計画停止のためにOracleインスタンスを停止させる場合でも、そのOracleインスタンスと接続されていた物理コネクションを安全に切断することができます。

また、Oracle Database 10g Release 2以降のRAC環境では、複数あるOracleインスタンスにどれだけの負荷を配分するのが最適かをOracleクライアントに通知するランタイム接続ロード・バランスの仕組みもFANを使用して実装されています。

3 アプリケーション・コンティニュイティ(Application Continuity)

高速接続フェイルオーバーによって、ノード障害の場合でもOracleクライアントは短時間で障害を検出することができるようになりました。Oracleサーバーの障害を検出するとアプリケーションにはエラーが返り、何らかのエラー・ハンドリングを行います。Oracle Database 12cからは、Oracleサーバーに障害が発生した場合にもアプリケーションにエラーを返さずに、透過的にトランザクションを自動再実行するアプリケーション・コンティニュイティが導入されました。アプリケーション・コンティニュイティを使用するにはOracle Database Enterprise Editionが必要です。

Oracle Database 12c以前にも、SELECT文を自動的に再実行する透過的アプリケーション・フェイルオーバー(Transparent Application Failover: TAF)という機能がありました。しかし、これはトランザクションに更新SQLを含んでいる場合は再実行できないという強い制限があり、非常に限られた状況でしか有効に機能しませんでした。これに対しアプリケーション・コンティニュイティでは、更新SQLを含むトランザクションを透過的に再実行することができ、ほとんどのケースでアプリケーションのコードを変更せずに適用することができます。Oracle Database 12c Release 1ではJDBC Thinドライバのみの対応でしたが、Release 2ではODP.NET管理対象外ドライバとOCIにも対応しました。

3.1 トランザクション・ガード(Transaction Guard)

OracleクライアントがOracleサーバーの障害を検出したからといって、無条件で更新トランザクションを再実行してもよいとは限りません。障害のタイミングによっては、Oracleサーバーの中ではトランザクションのCOMMITが完了している場合が考えられます。トランザクションのCOMMITはオンラインREDOログにCOMMITフラグが書き込まれた時点で完了したとみなされます。COMMITの完了がOracleクライアントに通知されるまでの間の時間にOracleサーバーに障害が発生すると、Oracleサーバー内ではCOMMITは完了しているのにOracleクライアントはCOMMITが完了したかどうかがわからないという状態になります。この状態で更新トランザクションを再実行してしまうと、アプリケーションの意味的にデータに矛盾が発生してしまいます。このような状態でも更新トランザクションを安全に再実行できるかを判断するために、アプリケーション・コンティニュイティではトランザクション・ガードという機能が内部的に使用されます。

トランザクション・ガードの機能は、論理トランザクション識別子を与えるとそのトランザクションが完了しているかどうかを確認することができます。アプリケーション・コンティニュイティがトランザクションを再実行しようとするとき、トランザクション・ガードを使用してこのトランザクションがOracleサーバー内で完了していたのかどうかを問い合わせます。トランザクションが未完了である場合は、再実行されます。

トランザクション・ガードの機能はAPIが公開されているため、アプリケーション・プログラマが明示的に使用することもできますが、アプリケーション・コンティニュイティの機能はこれら一連の動作をアプリケーションから透過的に行うため、トランザクション・ガードのための追加のプログラミングは不要です。

3.2 アプリケーション・コンティニュイティの設定

アプリケーション・コンティニュイティはサービスの属性として設定します。トランザクション・ガードを有効にする設定も同時に行います。

# サービス作成
$ srvctl add service -service acservice -db rac122a -pdb rac122apdb1 -preferred rac122a1,rac122a2

# アプリケーション・コンティニュイティとトランザクション・ガードの設定
$ srvctl modify service -db rac122a -service acservice -failovertype TRANSACTION -replay_init_time 300 -failoverretry
30 -failoverdelay 3 -notification TRUE -commit_outcome TRUE

# サービス起動
$ srvctl start service -db rac122a -service acservice

そして、データベース・ユーザーにトランザクション・ガード用のPL/SQLプロシージャの実行権限も必要です。

SQL> GRANT EXECUTE ON DBMS_APP_CONT TO hr;

 

3.3 可変オブジェクトの扱い

データベース・オブジェクトにはSELECTでアクセスするたびに値が変化するものがあります。SYSDATEやSYSTIMESTAMP、SYS_GUID、そしてSEQUENCEです。これらのオブジェクトは可変オブジェクトと呼ばれます。アプリケーション・コンティニュイティでトランザクションが再実行されるとき、可変オブジェクトは再実行前の値を使用しなければリプレイは失敗します。そのため、これらの可変オブジェクトには再実行前の値を保持するための設定が追加されています。

SQL> grant KEEP DATE TIME to hr;
SQL> grant KEEP SYSGUID to hr;
SQL> grant KEEP SEQUENCE on user2.seq1 to hr;

SQL> ALTER SEQUENCE my_seq KEEP;

3.4 アプリケーション・コンティニュイティのためのコード

アプリケーション・コンティニュイティでは、リクエスト境界という概念が重要になります。アプリケーション・コンティニュイティで自動的に再実行されるのはリクエストの開始から終了までの間です。コネクション・プールを使用しないJavaのコードではbeginRequest()とendRequest()の間です。

Connection con = ods.getConnection();      // コネクション取得
((OracleConnection)con).beginRequest();    // リクエスト開始

     ... // SQL実行

con.commit();
((OracleConnection)con).endRequest();       // リクエスト終了
con.close();                                 // コネクション・クローズ

ただし、UCPを使用した場合はbeginRequest()とendRequest()を記述する必要はありません。getConnection()でコネクションを取得するところからclose()で返却するところまでがリクエストです。UCPはOracle WebLogic ServerのActive GridLinkデータ・ソースでも内部的に使用されています。また、UCPをIBM WebSphere Application Serverに組み込む例Apache Tomcatに組み込む例が公開されています。

ほとんどのアプリケーションでは、SELECT/INSERT/UPDATE/DELETEによる表データの操作のみを行い、COMMITを行います。COMMITがリクエストの中で1回のみ、そしてリクエストの最後の処理である場合、アプリケーション・コンティニュイティを使用するにあたって特にコードを修正することはありません。多くのWebアプリケーションではこのような構造になっています。ただし、これはアプリケーションからエラー・ハンドリングのコードをなくしてもよいという意味ではありません。アプリケーション・コンティニュイティが有効なのは、Oracleサーバー障害で再実行すれば処理が成功するケースのみです。ORA-1(一意制約違反)などのエラーはアプリケーションがハンドリングする必要があります。

また、分散トランザクションXAの扱いなど、バージョンによって制限が緩和されている機能もありますので、細かな制限事項は使用を予定しているバージョンのマニュアルで確認してください。アプリケーション・コンティニュイティの説明は複数のマニュアルにわたっています。

  • Real Application Clusters管理およびデプロイメント・ガイド
  • JDBC開発者ガイド
  • Universal Connection Pool開発者ガイド
  • Oracle Call Interfaceプログラマーズ・ガイド

アプリケーションによっては、UTL_SMTPパッケージなどのDBMS外部に対して副作用を持つ操作を含んでいる場合があります。アプリケーション・コンティニュイティではこれらの副作用を持つ処理も再実行されます。再実行させたくない場合はdisableReplay()メソッドで再実行を無効化するコード修正が必要になります。

データベース・サーバーに障害が発生したとき、障害の内容とタイミングによってはデータベース・クライアントが検出できない場合があることを説明しました。障害が検出できなければ障害対応機能は発動しません。Oracle9iまではTCP/IPタイマーの設定を行うことでこれを回避していました。Oracle Grid Infrastructureでは、高速接続フェイルオーバーの仕組みでOracleサーバーの障害を明示的にOracleクライアントに通知することができるようになっています。

OracleクライアントがOracleサーバーの障害を検出したとき、新規コネクションの確立には接続時フェイルオーバーの機能が働きます。確立済みコネクションの切断に対しては、アプリケーション・コンティニュイティの機能で更新トランザクションを透過的に自動再実行できるようになりました。