はじめに
IBM Db2のQレプリケーション(QREP)をについて、いろいろ触っているのですが、マニュアルのみでは理解が難しいと感じたので、手元の環境で簡単に動作確認をしてみました。
本記事では、IBMQREP_TARGETS表(アプライの制御表のひとつ)のCONFLICT_ACTIONの設定値について、とてもシンプルな表を作成して試してみました。
QREPのセットアップはこちらで実施しています。
Qレプリケーションをセットアップしてみた
コンフリクト時の動作について
QREPでコンフリクトとされるSQLエラーの例は次のようなケースが考えられます。
- UPDATE対象行がターゲット表に存在しない
- INSERT対象行がターゲット表に既に存在している
- DELETE対象行がターゲット表に存在しない
コンフリクトが発生した場合、Qサブスクリプションの設定値である "CONFLICT_ACTION"の値によって、アプライの動作が変わってくるようなので、ここの値を変更し、処理のされ方がどう変わるのかを確認してみました。
試してみる
事前準備
ソース側とターゲット側のデータベースにテスト用の単純なテーブルを適当に作成しておきます。
$ db2 "select * from testschema.fruit"
FRUIT_ID FRUIT_NAME ENGLISH_NAME
----------- ------------------------------ ------------------------------
1 りんご Apple
2 みかん Mikan (Mandarin Orange)
3 ぶどう Grape
4 いちご Strawberry
5 さくらんぼ Cherry
5 レコードが選択されました。
同じ表をターゲット側のデータベースでも作成しておきます。(省略)
そして、Qサブスクリプションを定義します。(データをソース表からターゲット表に複製する単位)
Qサブスクリプション作成部分は省略(参考として)
ASNCLPスクリプト・ファイルの作成
### Qレプリケーション用のASNCLPセッションであることを宣言
ASNCLP SESSION SET TO Q REPLICATION;
### 生成されたSQL文を保管するファイル
SET OUTPUT CAPTURE SCRIPT "cre.QCAP_QSUB_FRUIT.sql";
SET OUTPUT TARGET SCRIPT "cre.QAPP_QSUB_FRUIT.sql";
### ASNCLPコマンドの実行ログファイル
SET LOG "cre.QREP_QSUB_FRUIT.clpin.log";
### SQLファイルの出力のみ(SQLファイルの内容は自分で実行する)
SET RUN SCRIPT LATER ;
### 即時実行
# SET RUN SCRIPT NOW STOP ON SQL ERROR ON;
### ソースおよびターゲット データベースの指定
### パスワード・ファイルを利用して、接続ユーザー情報とパスワード情報を読みこませる場合
SET PWDFILE "/opt/qrep/apply/asnpwd.aut";
SET SERVER CAPTURE TO DB SAMPLE ;
SET SERVER TARGET TO DB TARGETDB ;
### Qキャプチャー、Qアプライのスキーマ名(ASNはデフォルト、任意の名前を指定可能)
SET CAPTURE SCHEMA SOURCE ASN;
SET APPLY SCHEMA ASN;
### Qサブスクリプションの作成
CREATE QSUB
USING REPLQMAP SAMPLE_ASN_TO_TARGETDB_ASN
(
SUBNAME SUB-FRUIT
TESTSCHEMA.FRUIT
OPTIONS
EXIST
TARGET NAME "TESTSCHEMA"."FRUIT"
) ;
実行
asnclp -f cre.QREP_QSUB_FRUIT.clpin.txt
以下が作成される。
cre.QCAP_QSUB_FRUIT.sql
cre.QAPP_QSUB_FRUIT.sql
cre.QCAP_QSUB_FRUIT.sql をソース側(Capture側)で実行
$ db2 -tvf cre.QCAP_QSUB_FRUIT.sql
ALTER TABLE TESTSCHEMA.FRUIT DATA CAPTURE CHANGES
DB20000I SQL コマンドが正常に完了しました。
INSERT INTO ASN.IBMQREP_SUBS (subname, source_owner, source_name, sendq, subtype, all_changed_rows , before_values, changed_cols_only, has_loadphase, state, source_node, target_node, options_flag, suppress_deletes, target_server, target_alias, target_owner, target_name, target_type, apply_schema, capture_load, repl_addcol, igntrig, igncasdel, ignsetnull, capture_truncate, sub_creator) VALUES ('SUB-FRUIT', 'TESTSCHEMA', 'FRUIT', 'QREP.SENDQ', 'U', 'N', 'N', 'Y' , 'I', 'N', 0, 0, 'NNNN', 'N', 'TARGETDB', 'TARGETDB', 'TESTSCHEMA', 'FRUIT', 1, 'ASN', 'W', 'N', 'N', 'N', 'N', 'W', 'asnclp')
DB20000I SQL コマンドが正常に完了しました。
INSERT INTO ASN.IBMQREP_SRC_COLS (subname, src_colname, is_key, col_options_flag) VALUES ('SUB-FRUIT', 'FRUIT_ID', 1, 'YNNNNNNNNN')
DB20000I SQL コマンドが正常に完了しました。
INSERT INTO ASN.IBMQREP_SRC_COLS (subname, src_colname, is_key, col_options_flag) VALUES ('SUB-FRUIT', 'FRUIT_NAME', 0, 'NNNNNNNNNN')
DB20000I SQL コマンドが正常に完了しました。
INSERT INTO ASN.IBMQREP_SRC_COLS (subname, src_colname, is_key, col_options_flag) VALUES ('SUB-FRUIT', 'ENGLISH_NAME', 0, 'NNNNNNNNNN')
DB20000I SQL コマンドが正常に完了しました。
cre.QAPP_QSUB_FRUIT.sql をターゲット側(Apply側)で実行
$ db2 -tvf cre.QAPP_QSUB_FRUIT.sql
INSERT INTO ASN.IBMQREP_TARGETS (subname, recvq, source_owner, source_name, target_owner, target_name , modelq, source_server, source_alias, target_type, state, subtype, conflict_rule, conflict_action, error_action, source_node, target_node, load_type, has_loadphase, source_type, sub_creator) VALUES ('SUB-FRUIT', 'QREP.RECVQ', 'TESTSCHEMA', 'FRUIT', 'TESTSCHEMA', 'FRUIT', 'IBMQREP.SPILL.MODELQ', 'SAMPLE', 'SAMPLE', 1, 'I', 'U', 'K' , 'I', 'Q', 0, 0, 0, 'I', 'D', 'asnclp')
DB20000I SQL コマンドが正常に完了しました。
INSERT INTO ASN.IBMQREP_TRG_COLS (subname, recvq, target_colname, source_colname, is_key, target_colNo , mapping_type, src_col_map, bef_targ_colname) VALUES ('SUB-FRUIT', 'QREP.RECVQ', 'FRUIT_ID', 'FRUIT_ID', 'Y', 0, 'R', null , null)
DB20000I SQL コマンドが正常に完了しました。
INSERT INTO ASN.IBMQREP_TRG_COLS (subname, recvq, target_colname, source_colname, is_key, target_colNo , mapping_type, src_col_map, bef_targ_colname) VALUES ('SUB-FRUIT', 'QREP.RECVQ', 'FRUIT_NAME', 'FRUIT_NAME', 'N', 1, 'R', null, null)
DB20000I SQL コマンドが正常に完了しました。
INSERT INTO ASN.IBMQREP_TRG_COLS (subname, recvq, target_colname, source_colname, is_key, target_colNo , mapping_type, src_col_map, bef_targ_colname) VALUES ('SUB-FRUIT', 'QREP.RECVQ', 'ENGLISH_NAME', 'ENGLISH_NAME', 'N', 2, 'R', null, null)
DB20000I SQL コマンドが正常に完了しました。
確認
$ db2 "select substr(subname,1,30)as subname, state, state_info, has_loadphase from asn.ibmqrep_subs where subname='SUB-FRUIT'"
SUBNAME STATE STATE_INFO HAS_LOADPHASE
------------------------------ ----- ---------- -------------
SUB-FRUIT N ASN7010I I
1 レコードが選択されました。
$ db2 "select substr(subname,1,30)as subname, state, state_info, has_loadphase from asn.ibmqrep_targets where subname='SUB-FRUIT'"
SUBNAME STATE STATE_INFO HAS_LOADPHASE
------------------------------ ----- ---------- -------------
SUB-FRUIT I - I
1 レコードが選択されました。
作成したQサブスクリプションをアクティブにする
$ db2 "insert into asn.ibmqrep_signal (signal_time, signal_type, signal_subtype, signal_input_in, signal_state) values (current timestamp, 'CMD', 'CAPSTART', 'SUB-FRUIT', 'P')"
DB20000I SQL コマンドが正常に完了しました
状況例
何かが原因で、ソース表とターゲット表間でデータの不整合となってしまったとします。
例えば、故意にターゲット表の"2.みかん"を削除してみます。
$ db2 "delete from testschema.fruit where fruit_name='みかん' "
$ db2 "select * from testschema.fruit"
FRUIT_ID FRUIT_NAME ENGLISH_NAME
----------- ------------------------------ ------------------------------
1 りんご Apple
3 ぶどう Grape
4 いちご Strawberry
5 さくらんぼ Cherry
4 レコードが選択されました。
一方、ソース表ではみかんのデータは存在したままなので、みかんをオレンジに変更するような操作が行われると想定します。このときの CONFLICT_ACTION の設定値の違いでどういう動作となるか見てみます。
CONFLICT_ACTION の設定
CONFLICT_ACTIONのパラメータはIBMQREP_TARGETS表(アプライの制御表のひとつ)を確認します。
各設定値の説明についてはマニュアルを参考
IBMQREP_TARGETS 表
1. CONFLICT_ACTION=I (デフォルト)
$ db2 "select substr(subname,1,30)as subname, state, state_info, has_loadphase, conflict_action from asn.ibmqrep_targets where subname='SUB-FRUIT'"
SUBNAME STATE STATE_INFO HAS_LOADPHASE CONFLICT_ACTION
------------------------------ ----- ---------- ------------- ---------------
SUB-FRUIT A ASN7606I N I
1 レコードが選択されました。
Qサブスクリプション定義時の CONFLICT_ACTION はデフォルトで"I"となっています。
この状態で、データ不整合の部分をUPDATE文で更新してみます。
$ db2 "update testschema.fruit set fruit_name='オレンジ', english_name='Orange' where fruit_name='みかん' "
アプライのログには特にエラーがでませんでした。
デフォルト時の"I"は競合行には適用されないようです。(無視されます)
2. CONFLICT_ACTION=S
次に、CONFLICT_ACTION=S として同じことを実施してみます。
$ db2 "select substr(subname,1,30)as subname, state, state_info, has_loadphase, conflict_action from asn.ibmqrep_targets where subname='SUB-FRUIT'"
SUBNAME STATE STATE_INFO HAS_LOADPHASE CONFLICT_ACTION
------------------------------ ----- ---------- ------------- ---------------
SUB-FRUIT A ASN7606I N S
1 レコードが選択されました。
※値の変更後はアプライ・プログラムを再起動させてください。設定値の変更が反映されないためです。
更新処理
$ db2 "update testschema.fruit set fruit_name='オレンジ', english_name='Orange' where fruit_name='みかん' "
するとアプライ・プログラムがエラーを出して停止しました。
<appAgnt::handleRowError> ASN8999D Stop apply according to the conflict or error action because of reason "NOTFOUND" (SQL code "100") for subscription "SUB-FRUIT" for receive queue "QREP.RECVQ", row operation is "UPDATE"
<appAgnt::handleRowError> ASN7522E "Q Apply" : "ASN" : "BR00000AG013" : The Q Apply program stopped because it encountered an error for Q subscription "SUB-FRUIT" (receive queue "QREP.RECVQ", queue map "SAMPLE_ASN_TO_TARGETDB_ASN"). The error occurred while applying a row corresponding to the LSN "0000:0000:0000:3345:0000:0000:0007:4ef5" from the source transaction corresponding to the commit LSN "0000:0000:0000:3345:0000:0000:0007:4ef6", authorization ID "DB2INST1".
ソース表にあるはずの、みかんのデータがターゲット表に無いことからUPDATE処理で"NOTFOUND"となったというエラーが出力されていました。
さらにアプライのIBMQREP_EXCEPTIONS表(アプライの制御表のひとつ)を確認します。
$ db2 "select * from ASN.IBMQREP_EXCEPTIONS"
IBMQREP_EXCEPTIONS表には、競合やSQLエラーにより適用できない行の変更に関する情報が格納されています。
そのため、ログから競合・エラーと判断した後、どれが該当の操作であるかをこの表を見て確認します。
表の中からTEXTの箇所を見てみると、先ほどのupdate処理が記載されているため、この情報から競合・エラーとなった箇所を特定することができました。
$ db2 "select substr(text,1,120) as text from ASN.IBMQREP_EXCEPTIONS"
TEXT
------------------------------------------------------------------------------------------------------------------------
UPDATE "TESTSCHEMA"."FRUIT" SET "ENGLISH_NAME" = 'Orange', "FRUIT_NAME" = 'オレンジ' WHERE "FRUIT_ID" = 2
1 レコードが選択されました。
このような状態になったら、下記が考えられると思います。
- ターゲット表を正しい状態、今回の場合、みかんのレコードを再びINSERTしてあげてからアプライ・プログラムを起動
- CONFLICT_ACTION=F にして再起動
- フルリフレッシュ
ここではターゲット表で、データを戻してアプライを再起動してみます。
$ db2 "INSERT INTO TESTSCHEMA.FRUIT (FRUIT_ID, FRUIT_NAME, ENGLISH_NAME) VALUES(2, 'みかん', 'Mikan (Mandarin Orange)')"
~その後、アプライ再起動してから、表を確認~
$ db2 "select * from testschema.fruit"
FRUIT_ID FRUIT_NAME ENGLISH_NAME
----------- ------------------------------ ------------------------------
1 りんご Apple
2 オレンジ Orange
3 ぶどう Grape
4 いちご Strawberry
5 さくらんぼ Cherry
5 レコードが選択されました。
これにより、アプライは再度正常に稼働し、ソース表での変更内容(みかん→オレンジ)も反映されていました。受信・キューに溜まったままでしたのでそれを取り込んだようです。
※実は、CONFLICT_ACTION=I のときも このIBMQREP_EXCEPTIONS表にはエラー内容が出力されていました。
3. CONFLICT_ACTION=F(強制適用)
CONFLICT_ACTION=F(強制適用)についても確認してみます。
$ db2 "select substr(subname,1,30)as subname, state, state_info, has_loadphase, conflict_action f
rom asn.ibmqrep_targets where subname='SUB-FRUIT'"
SUBNAME STATE STATE_INFO HAS_LOADPHASE CONFLICT_ACTION
------------------------------ ----- ---------- ------------- ---------------
SUB-FRUIT A ASN7606I N F
1 レコードが選択されました。
そして、また同じケースを実施します。
更新処理
$ db2 "update testschema.fruit set fruit_name='オレンジ', english_name='Orange' where fruit_name='みかん' "
その結果、アプライのログにはエラーは出ておらず停止もしませんでした。そしてターゲット表にはUPDATE結果(オレンジ変更)が適用されていました。
$ db2 "select * from testschema.fruit"
FRUIT_ID FRUIT_NAME ENGLISH_NAME
----------- ------------------------------ ------------------------------
1 りんご Apple
2 オレンジ Orange
3 ぶどう Grape
4 いちご Strawberry
5 さくらんぼ Cherry
5 レコードが選択されました。
"I"の場合は、ターゲット表は変更されていませんでしたが、"F"だと変更されています。
ただし、これまで同様に IBMQREP_EXCEPTIONS表には競合・エラーの内容が追加されていました。
4. CONFLICT_ACTION=D
CONFLICT_ACTION=D についても同様に確認してみます。
$ db2 "select substr(subname,1,30)as subname, state, state_info, has_loadphase, conflict_action from asn.ibmqrep_targets where subname='SUB-FRUIT'"
SUBNAME STATE STATE_INFO HAS_LOADPHASE CONFLICT_ACTION
------------------------------ ----- ---------- ------------- ---------------
SUB-FRUIT A ASN7606I N D
1 レコードが選択されました。
更新処理をします。
$ db2 "update testschema.fruit set fruit_name='オレンジ', english_name='Orange' where fruit_name='みかん' "
すると、アプライ・ログには、conflict_actionによってQサブスクリプションを非アクティブ化したと出力されていました。
ただし、アプライ・プログラムは停止していません。
さらに、キャプチャー・ログにも対象のQサブスクリプションを非アクティブ化したことが出力されていました。
<appAgnt::handleRowError> ASN8999D Disable subscription "SUB-FRUIT" for receive queue "QREP.RECVQ" according to the conflict action because of reason "NOTFOUND" (SQL code "100"), operation applied is "UPDATE"
<appAgntMain> ASN7597E "Q Apply" : "ASN" : "BR00000AG004" : Q サブスクリプション "SUB-FRUIT" (受信キュー "QREP.RECVQ"、 レプリケーション・キュー・マップ "SAMPLE_ASN_TO_TARGETDB_ASN") が、競合アクションまたはエラー・アクションのために使用不可にされようとしています。 この Q サブスクリプションで指定されるソース表に対する将来の行変更は、Q サブスクリプションが再始動するまでスキップされます。
<browser::verifyMsgVersion> ASN8999D Browser for queue 'QREP.RECVQ' received a 'ASNMQ_SUBDEACTIVATED' message.
<browser::processRecvQ> ASN7700I "Q Apply" : "ASN" : "BR00000" : Q アプライ・プログラムは Q サブスクリプション "SUB-FRUIT" (受信キュー "QREP.RECVQ"、レプリケーション・キュー・マップ "SAMPLE_ASN_TO_TARGETDB_ASN") の非活動化サブスクリプション・メッセージを受信しました。 Q サブスクリプションが非活動化されます。
<subMgr::handleSignal> ASN7019I "Q Capture" : "ASN" : "WorkerThread" : "CAPSTOP" シグナルが受信されたので処理されます。
<subMgr::handleCAPSTOP> ASN7013I "Q Capture" : "ASN" : "WorkerThread" : The publication, Q subscription, or mapping "SUB-FRUIT" was deactivated.
<updateReplMapDbState> ASN7133I "Q Capture" : "ASN" : "WorkerThread" : The publication, Q subscription, or mapping "SUB-FRUIT" was stopped.
そして、ターゲット表には反映されておらず、エラーとして IBMQREP_EXCEPTIONS表にレコードが追加されていました。
この場合、競合行に対する変更はスキップされてしまったため、受信・キューにも残っていません。そのためこの場合のアプライの復旧は、以下が考えられると思います。
- フルリフレッシュ
- ターゲット表に更新予定後のデータを入れて、再起動後にQサブスクリプションをアクティブ化
ただし、エラーによってQサブスクリプションが非活動化されてしまい、その間にソース表が更新された内容は読み取らないため、フルリフレッシュでの同期がよさそうです。
4. CONFLICT_ACTION=Q
最後に CONFLICT_ACTION=Q についても同様に確認してみます。
$ db2 "select substr(subname,1,30)as subname, state, state_info, has_loadphase, conflict_action from asn.ibmqrep_targets where subname='SUB-FRUIT'"
SUBNAME STATE STATE_INFO HAS_LOADPHASE CONFLICT_ACTION
------------------------------ ----- ---------- ------------- ---------------
SUB-FRUIT A ASN7606I N Q
1 レコードが選択されました。
更新処理をします。
$ db2 "update testschema.fruit set fruit_name='オレンジ', english_name='Orange' where fruit_name='みかん' "
アプライ・ログには次のように出ていました。
<appAgnt::handleRowError> ASN7523E "Q Apply" : "ASN" : "BR00000AG002" : The Q Apply program encountered an error or conflict for Q subscription "SUB-FRUIT" (receive queue "QREP.RECVQ", queue map "SAMPLE_ASN_TO_TARGETDB_ASN"). The Q Apply program will stop reading from the receive queue. The error occurred while applying a row corresponding to the LSN "0000:0000:0000:392d:0000:0000:0007:72ad" from the source transaction corresponding to the commit LSN "0000:0000:0000:392d:0000:0000:0007:72ae", authorization ID "DB2INST1".
・・・中略・・・
<brwzMain> ASN7524E "Q Apply" : "ASN" : "BR00000" : Q アプライ・プログラムは、Q サブスクリプション "SUB-FRUIT" (受信キュー "QREP.RECVQ"、 レプリケーション・キュー・マップ "SAMPLE_ASN_TO_TARGETDB_ASN" ) でエラーまたは矛盾を検出しました。 受信キューからの読み取りを停止しました。 LSN "0000:0000:0000:392d:0000:0000:0007:72ae" に対応するトランザクションからの行の適用中にエラーが発生しました。
結果としては、競合行の更新は適用されずにアプライが受信・キューからの読み取りを停止させることとなりました。
更新は破棄されずキューに残るため、後から確認はできます。
読み取り停止後に別の正しい更新処理があったとすると、その更新もキューには残るが、アプライを再起動すると競合の更新だけがまた残り、正しい更新分の処理は取り込まれました。(さらに競合の更新があればそのキューも残ります)
対応としては、以下が考えられます。
- CONFLICT_ACTION=F にして再起動
- ターゲット表のデータを修正して再起動
- フルリフレッシュ
IBMQREP_EXCEPTIONS表
すべてに共通してですが、競合エラー時は、IBMQREP_EXCEPTIONS表に記録されます。
これは復旧後も自動で削除されないため、手動で削除が必要になります。
$ db2 "delete asn.ibmqrep_exceptions"
さいごに
今回はコンフリクト時の設定値について、マニュアルのすべてのパターンを試してみました。
そして簡単に動作についてを整理すると以下のようになりました。
| CONFLICT_ACTION | 競合した場合のアプライの動作概要 |
|---|---|
| I | 競合行は適用せず、アプライは停止しない。 |
| S | 競合エラーを出力。競合行は適用せず、アプライが停止する。 |
| F | 競合行は強制的に適用、アプライは停止しない。 |
| D | 競合行は適用せず、競合エラーを出力。Qサブスクリプションを非アクティブ化するがアプライは停止しない。 |
| Q | 競合行は適用せず、競合エラーを出力。受信・キューからの読み取りを停止するがアプライは停止しない |
もっと複雑なテーブルの更新時は、コンフリクトの考え方も難しくなる可能性がありますが、今回はとてもシンプルな表で試した結果を確認してみました。
どの設定値にするのかはその環境や要件によって違ってくるので、参考になれば幸いです。