データベースについて調べてみた

データベースについて調べたことのメモ。Oracle要素多めになる予定。

Oracle RACの接続時フェイルオーバーについて調べてみた

この記事は、JPOUG Advent Calendar 2024 5日目の記事です。4日目はcharade_oo4oさんの記事「Oracle on Hyper-V 2024」でした。明日はs4r_agentさんです。

 

今回は、Oracle RAC(Real Application Clusters)を使用する際に検討が必要となる、接続時フェイルオーバーについて調べてみました。

調査したくなったきっかけは、Oracle Database 11gR2から登場した、SCAN(Single Client Access Name)機能使用時における接続時フェイルオーバーの構成方法がよくわからないことに今更ながら気づいたためなので、この辺の調査結果も含めて記載していきます。

 

1. 接続時フェイルオーバーとは

Oracle RACにおける接続時フェイルオーバーとは、APなどOracleへ接続するクライアント側からRACを構成するDBサーバの1つへ新規接続を行う際に、接続先DBサーバが障害や高負荷などにより新規接続を受付けられなかった場合に、クライアント側がRACを構成する別のDBサーバへ新規接続を自動的に(AP側で明示的なリトライ処理等を組み込むことなく)試行してくれる機能です。

 

まずは、SCANリスナーを使用しない前提での接続時フェイルオーバーについて記載していきます。

接続時フェイルオーバーを使用できるようにするためには、DBサーバにリスナー経由で接続する際に使用するTNS接続名の定義に、「FAILOVER=ON」と「接続先候補となるリスナー情報のリスト」を以下の様に含める必要があります。

tnsStr01 =
 (DESCRIPTION =
  (LOAD_BALANCE=OFF)
  (FAILOVER=ON)
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv1-vip)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv2-vip)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv3-vip)(PORT=1521))
  (CONNECT_DATA=(SERVICE_NAME=ORCL))
 )

 

接続時フェイルオーバー順序の制御について

接続時フェイルオーバーを構成したTNS接続名を用いた新規接続を行う場合、接続先候補となるリスナー情報の上から順に接続を試行していきます(※補足参照)。接続先(CONNECT_DATA)にDBインスタンスではなく、RACの全インスタンスに跨るサービスが指定されていることで、フェイルオーバー先のDBサーバでも接続することができます。

もし分散フェイルオーバー的なことをしたいのであれば、分散パターン毎にTNS接続名を用意して、各業務処理は使用したい分散パターンにマッチしたTNS接続名を用いてDB接続することで実現できるかと思います。

例えば、普段DBサーバ#1に接続する業務処理について、DBサーバ#1への新規接続不可時に業務処理AはDBサーバ#2へ、業務処理BはDBサーバ#3へ接続させたい場合は、#1→#2→#3の順に接続試行するTNS接続名Aと、#1→#3→#2の順に接続試行するTNS接続名Bを下記の様に用意し、業務処理AはTNS接続名Aを、業務処理BはTNS接続名Bを用いてDB接続することで、業務処理毎に接続時フェイルオーバー先を制御することができるようになります。

 

※補足
接続先候補となるリスナー情報の上から順に接続を試行させるためには、TNS接続名の定義中に「LOAD_BALANCE=OFF」を含める必要があります。逆にLOAD_BALANCE=ONにすると、クライアント側接続時ロードバランス機能が有効となり、接続候補リスナーの中から無作為に接続試行するリスナーが選択されてしまうため、フェイルオーバー先の制御ができなくなります。

 

 

2.APパーティショニング構成時の注意点

接続時フェイルオーバー構成の説明をしましたが、RACのキャッシュフュージョンによるSQL性能影響を考慮して、ある業務処理は1つのDBサーバだけで実行させる様な設計(APパーティショニング)をしている場合は、接続時フェイルオーバー構成について注意が必要だと考えています。

 

注意が必要な理由について

注意が必要と考えている理由は、接続時フェイルオーバーの発動が、接続先サーバ(DBインスタンス)のダウン時とは限らない可能性があるためです。

接続時フェイルオーバーは、接続先サーバ(DBインスタンス)の死活状況を正確に確認して発動されているわけではなく、接続先のリスナー経由で接続確立できるかで発動されているので、サーバやリスナープロセスが一時的に高負荷だったなどの理由で、接続時フェイルオーバーが発生する可能性があります。

以下の図の例では、APサーバからコネクションプールのコネクション生成時に、DBサーバ#1側の一時的な高負荷により、一部のコネクション生成時に接続時フェイルオーバーが発生し、DBサーバ#2とのコネクションが混在した状況を表しています。

この状態でDBサーバ#1で処理する様にAPパーティショニングをしていた業務処理が発生すると、DBサーバ#1とDBサーバ#2で業務処理が意図せず実行されることになり、キャッシュフュージョンによる性能影響が発生する可能性があります。

 

対策例について

先程のAPパーティショニングしていた処理が意図せず複数DBサーバで実行される例について、性能劣化が発生してしまっても、システムとしては動いていればよいとされる場合は、本事象についての対策は不要だと思いますが、性能劣化をしてレスポンス要件を満たせないなら業務閉塞させる様な、性能要件がシビアなシステムの場合は対策が必要になると思います。

後者のようなシステムの場合、意図しないDBサーバに対して勝手に接続されるくらいなら、接続に失敗してエラーを返した方が、時間をおいてからコネクションの生成処理を再実行するなどの運用対処を講じられるので嬉しいということであれば、APパーティショニングとして接続させたいDBサーバ上でのみ稼働するRACのサービスを構成することが、対策方法として挙げられます。

下記の例は、正常時はDBサーバ#1で稼働するサービス#1を構成し、サービス#1に接続する様なTNS名を定義することで、一時的な負荷でDBサーバ#1上のリスナーが新規接続を受付けられず、DBサーバ#2(#3も)に新規接続リクエストがフェイルオーバーしても、サービス#1が稼働していないため、DBサーバ#2に新規接続されない様にガードすることができます。

 

TNS接続名の定義でポイントとなるのは、SERVICE_NAMEに作成したサービス#1のサービス名を指定する点です。

tnsA =
 (DESCRIPTION =
  (LOAD_BALANCE=OFF)
  (FAILOVER=ON)
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv1-vip)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv2-vip)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv3-vip)(PORT=1521))
  (CONNECT_DATA=(SERVICE_NAME=svc1))
 )

 

また、サービスを作成する際は、本当のサーバ(インスタンス)ダウンを考慮して、フェイルオーバー可能なサービスを作成する必要があり、サービスのフェイルオーバー順序を、TNS接続名の定義で指定したフェイルオーバー順序(接続試行するサーバの順序)と一致する様に作成します()。

サービスの作成はsrvctlコマンドを用います。通常時にサービスを稼働させるインスタンス(サーバ)を、優先インスタンスとして-preferredで指定し、このインスタンスが停止した場合のサービスのフェイルオーバー先を、使用可能インスタンスとして-availableでカンマ区切りで指定します。なお、-availableで指定した順序でサービスのフェイルオーバーが試行されます。

srvctl add service -db ORCL -service svc1 -preferred dbinst1 -available dbinst2,dbinst3

 

※サービスのフェイルオーバー順序指定について

サービスのフェイルオーバー順序を指定できるのは19.24.0以降となります。19.24.0より前のバージョンでは、フェイルオーバー先が-availableで指定されたインスタンスからランダムに選択されます。また、-availableで指定した順序でフェイルオーバーが試行されることについて、本記事執筆時点ではマニュアルに記載されていませんが、Oracleサポート契約者向けサイト(My Oracle Support)で公開されている、下記の技術情報に記載されております。

Doc 3053798.1: How To Specify The FailOver Instance On The Service

 

19.24.0より前のバージョンでサービスのフェイルオーバー順序を制御したい場合、Oracle Clusterwareのカスタムリソースをサービスと同数用意することで対応できる可能性があります。
カスタムリソースに関するマニュアルを見ると、カスタムリソースに対してフェイルオーバー先の順序をリスト指定(HOSTING_MEMBER)できたり、カスタムリソースに重みづけをした制御(LOAD)ができる様に見えるので、カスタムリソースが起動する際に自サーバでサービスを起動させるスクリプトを実行する様にすれば、実現できるのではないかと思います。

 

 

3. SCAN機能を使用した場合の接続時フェイルオーバー構成について

ここまでは、SCAN機能を使用しない場合の接続時フェイルオーバーについて記載してきましたが、ここからはSCAN機能について軽く説明をしつつ、SCAN機能を使用した場合の接続時フェイルオーバー構成について記載していきます。

 

SCAN機能とは

SCAN(Single Client Access Name)機能とは、Oracle Database 11gR2から登場したもので、Oracle RACデータベースにアクセスする際に、クライアント側がRACインスタンスやサービスが起動しているDBサーバを意識する必要がなく、1つの名前(リスナーアドレス)を指定すればアクセスできる機能です。
SCAN機能を提供するために、SCANリスナー、SCANリスナーに紐づく仮想IPアドレス(SCAN VIP)と、1つのSCANホスト名の構成が必要となります。

SCANリスナーは、RACを構成するDBサーバ台数に関わらず3つのリスナーで構成され、RACを構成するDBサーバのうち3台の上で稼働します(2ノードRACの場合は2台)。
また、SCANホスト名はDNSサーバに登録する必要があり、SCANホスト名に対して3つのSCAN VIPを紐づける(3つのSCAN VIPの内1つをラウンドロビンで返却する)様に登録する必要があります。こうすることで、SCANホスト名に問い合わせする度にDNSから3つのSCAN VIPのうちの1つが(ラウンドロビンで)返却され、SCANリスナーに対する負荷分散が行われます。

1点注意すべき点として、SCANリスナーはDBインスタンス(サービス)への接続処理自体は行いません

SCANリスナーは、RACを構成する各DBサーバ(DBインスタンス)上で稼働しているサービスの情報を保持しており、クライアントから接続リクエストが来ると、保持している情報から適切なDBサーバ上のローカルリスナーへ接続リクエストのリダイレクトのみを行います。そして、DBインスタンスへの接続処理自体は、各DBサーバ上に構成されるローカルリスナーが行います。

説明が長くなりましたが、ここまでの説明をざっくり絵にしたものが、以下となります。

 

SCAN機能を使用したTNS接続名の定義について

SCAN機能の利用を前提としたTNS接続名の定義イメージは、以下の様になります。特徴的なのは、ADDRESS句がSCANホスト名を指定する1つだけになっているところです。SCAN機能が無かったときは、RACを構成するDBサーバ数分のADDRESS句が必要だったので、RACを構成するDBサーバが追加されたりすると、都度TNS接続名の定義を修正する必要がありました。言い換えると、SCANを使用することで、TNS接続名の定義内容が、RACを構成するDBサーバ数に依存しなくなると言えます。

tnsStr01 =
 (DESCRIPTION =
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv-scan)(PORT=1521))
  (CONNECT_DATA=(SERVICE_NAME=ORCL))
 )

 

なお、DNSサーバがなくSCANホスト名を構成できない、またはOracle Database 11gR1以前のクライアントから接続する場合におけるTNS接続名の定義例は、以下の様になります。

tnsStr01 =
 (DESCRIPTION =
  (LOAD_BALANCE=ON)
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv-scan-vip1)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv-scan-vip2)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=dbsrv-scan-vip3)(PORT=1521))
  (CONNECT_DATA=(SERVICE_NAME=ORCL))
 )

SCANホスト名から3つあるSCANリスナーのうち1つのアドレスを取得することができないことから、定義の中に直接3つのSCANリスナーをADDRESS句で指定しつつ、LOAD_BALANCE=ONを指定することで、負荷分散を考慮したSCANリスナーへの接続を実現することができます。

 

フェイルオーバーの制御方法について

ようやく今回調査をしたくなったきっかけとなった、SCAN機能使用時の接続時フェイルオーバーの制御方法に辿り着きました。

先程例示しましたが、SCAN機能を使用するとTNS接続名の定義に含まれるADDRESS句が、RACを構成するDBサーバ数に依存しなくなるため、FAILOVER=ONにしてフェイルオーバー順にADDRESS句を記載することによる、フェイルオーバー順序の制御ができません。

SCAN機能を使用する場合にフェイルオーバー順序を制御するためには、APパーティショニング時の対策例の部分で登場した、RACのサービス機能を用いる必要があります。再掲ですが、下記の様にsrvctlコマンドを用いてフェイルオーバー順序を設定したサービスの作成を行い、そのサービスに対して接続する様にTNS接続名を定義します。

srvctl add service -db ORCL -service svc1 -preferred dbinst1 -available dbinst2,dbinst3

RACのサービスによるフェイルオーバー順序の制御は、Oracle Databaseのバージョンが19.24.0以降である必要があるため、これより前のバージョンで制御したい場合は、前述したOracle Clusterwareのカスタムリソースを併用するなどの検討が必要になります。

 

 

4. さいごに

今回はRAC構成における接続時フェイルオーバーについて調べましたが、srvctlでRACのサービスを作成する際に指定する、使用可能インスタンスのリスト順にフェイルオーバーが試行されることを公式ドキュメントを通じて知れたのは大きな収穫でした。

あと、今回の調査にあたって参照させて頂いた、オラクル社が公開している資料を紹介しておきます。13年前の資料ですがRACの接続時フェイルオーバーやロードバランス、SCANについても纏められていたので、詳細を知りたい方は是非ご参照ください。

解説!Oracle RACへの接続ロードバランスと接続フェイルオーバー

 

 

おまけ:SCANリスナーとローカルリスナーのサービス登録状況

SCANリスナーの登場により、ローカルリスナーは他のローカルリスナーに接続要求をリダイレクトする処理から解放されました(リダイレクトはSCANリスナーの役割)が、本当にリダイレクトできないのか気になり、2ノードRAC環境でSCANリスナーとローカルリスナーに登録されているサービス情報を実機確認してみました。

まず、DBサーバ#1で稼働しているSCANリスナーに対して、lsnrctlコマンドを実行し、リスナーに登録されているサービス情報の出力結果を抜粋したものが以下となります。全DBインスタンス(サーバ)で稼働しているORCLサービスに対して、他サーバ(#2)で稼働している情報もSCANリスナーに登録されていることから、他サーバへのリダイレクトに必要な情報が保持できていることがわかりました。

[grid@dbsrv1 ~]$ lsnrctl status LISTENER_SCAN1

LSNRCTL for Linux: Version 19.0.0.0.0 - Production on 24-11月-2024  09:55:51

Copyright (c) 1991, 2023, Oracle.  All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=LISTENER_SCAN1)))に接続中
リスナーのステータス
------------------------
別名                      LISTENER_SCAN1

(略)

サービスのサマリー...

(略)

サービス"ORCL"には、2件インスタンスがあります。
  インスタンス"ORCL1"、状態READYには、このサービスに対する1件のハンドラがあります...
  インスタンス"ORCL2"、状態READYには、このサービスに対する1件のハンドラがあります...

(略)

コマンドは正常に終了しました。

 

次にDBサーバ#1で稼働しているローカルリスナーに対して、lsnrctlコマンドを実行し、リスナーに登録されているサービス情報の出力結果を抜粋したものが以下となります。ORCLサービスが稼働しているインスタンスとして登録されているのは、ローカルリスナーが起動しているサーバと同じ#1の情報しか登録されていないことがわかります。他サーバの情報がないとリダイレクト先を選定できないため、ローカルリスナーはリダイレクトできないことがわかりました。

[oracle@dbsrv1 ~]$ lsnrctl status

LSNRCTL for Linux: Version 19.0.0.0.0 - Production on 24-11月-2024 10:01:09

Copyright (c) 1991, 2023, Oracle.  All rights reserved.

(ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521))に接続中
リスナーのステータス
------------------------
別名                      LISTENER

(略)

サービスのサマリー...

(略)

サービス"ORCL"には、1件インスタンスがあります。
  インスタンス"ORCL1"、状態READYには、このサービスに対する1件のハンドラがあります...

(略)

コマンドは正常に終了しました。

 

ローカルリスナーに他DBサーバ上で稼働するサービス情報が登録されないのは、初期化パラメータのremote_listenerにSCANリスナーを表す値が設定されている(※)ためだと考えられ、この設定値を強制的に各サーバで起動するローカルリスナーを表す情報に変更すれば、ローカルリスナーにリダイレクトさせられる気もしますが、サポート可否については機会があれば確認してみたいと思います。

SQL> show parameter remote_listener
NAME                 TYPE        VALUE
-------------------- ----------- ------------------------------
remote_listener      string      dbsrv-scan:1521

※今回の環境はDBCAを用いてDB構築を行ったため、その際に自動設定されたのかもしれません。

 

 

次の記事:Oracle RACのサービスのフェイルバックについて調べてみた

 

前回の記事:Oracle Data Redactionについてもう少し調べてみた