TiCDC の UPDATE イベントの分割動作
MySQLシンクのUPDATEイベントを分割する
v6.5.10、v7.1.6、v7.5.2、v8.1.1、v8.2.0以降では、MySQLシンクを使用する場合、テーブルのレプリケーション要求を受信したTiCDCノードは、下流へのレプリケーションを開始する前に、PDから現在のタイムスタンプthresholdTS取得します。このタイムスタンプの値に基づいて、TiCDCはUPDATEイベントを分割するかどうかを決定します。
- 1 つまたは複数の
UPDATE変更を含むトランザクションの場合、トランザクションcommitTSthresholdTS未満であれば、TiCDC はUPDATEイベントをDELETEイベントとINSERTイベントに分割してから、それらを Sorter モジュールに書き込みます。 - トランザクション
commitTSがthresholdTS以上であるイベントがUPDATEある場合、TiCDC はそれらを分割しません。詳細については、GitHub の問題#10918参照してください。
注記:
v8.1.0では、MySQL Sinkを使用する場合、TiCDCは
thresholdTS値に基づいてUPDATEイベントを分割するかどうかを決定しますが、thresholdTS取得方法は異なります。具体的には、v8.1.0では、thresholdTSTiCDC起動時にPDから取得される現在のタイムスタンプですが、この方法はマルチノードシナリオでデータの不整合の問題を引き起こす可能性があります。詳細については、GitHubのissue #11219ご覧ください。
この動作の変更 (つまり、 thresholdTSに基づいてUPDATEイベントを分割するかどうかを決定する) により、TiCDC が受信したUPDATEイベントの順序が正しくない可能性があり、その結果、分割されたDELETEとINSERTイベントの順序が間違ってしまう可能性がある、下流のデータの不整合の問題が解決されます。
次の SQL ステートメントを例に挙げます。
CREATE TABLE t (a INT PRIMARY KEY, b INT);
INSERT INTO t VALUES (1, 1);
INSERT INTO t VALUES (2, 2);
BEGIN;
UPDATE t SET a = 3 WHERE a = 2;
UPDATE t SET a = 2 WHERE a = 1;
COMMIT;
この例では、トランザクション内の2つのUPDATE文は実行時に順次依存関係を持ちます。主キーa 2から3に変更され、次に主キーa 1から2に変更されます。このトランザクションの実行後、上流データベースのレコードは(2, 1)と(3, 2)なります。
ただし、TiCDC が受信するUPDATEイベントの順序は、上流トランザクションの実際の実行順序と異なる場合があります。例:
UPDATE t SET a = 2 WHERE a = 1;
UPDATE t SET a = 3 WHERE a = 2;
この動作変更前、TiCDCはこれらの
UPDATEイベントをSorterモジュールに書き込み、その後DELETEつとINSERTイベントに分割していました。分割後、下流におけるこれらのイベントの実際の実行順序は以下のようになります。BEGIN; DELETE FROM t WHERE a = 1; REPLACE INTO t VALUES (2, 1); DELETE FROM t WHERE a = 2; REPLACE INTO t VALUES (3, 2); COMMIT;ダウンストリームがトランザクションを実行した後、データベース内のレコードは
(3, 2)なりますが、これはアップストリーム データベースのレコード ((2, 1)と(3, 2)) と異なり、データの不整合の問題があることを示しています。この動作変更後、TiCDCが対応するテーブルを下流にレプリケーションし始める際に、トランザクション
commitTSPDからフェッチされたthresholdTSより小さい場合、TiCDCはこれらのUPDATEイベントをDELETEつとINSERTイベントに分割してからSorterモジュールに書き込みます。Sorterモジュールによるソート後、下流におけるこれらのイベントの実際の実行順序は以下のようになります。BEGIN; DELETE FROM t WHERE a = 1; DELETE FROM t WHERE a = 2; REPLACE INTO t VALUES (2, 1); REPLACE INTO t VALUES (3, 2); COMMIT;ダウンストリームがトランザクションを実行すると、ダウンストリーム データベースのレコードはアップストリーム データベースのレコード (
(2, 1)と(3, 2)と同じになり、データの一貫性が確保されます。
前の例からわかるように、 UPDATEイベントをDELETEつとINSERTイベントに分割してから Sorter モジュールに書き込むと、分割後のINSERTイベントの前にDELETEイベントすべてが実行されるようになり、TiCDC が受信したUPDATEイベントの順序に関係なく、データの一貫性が維持されます。
注記:
この動作変更後、MySQLシンクを使用する場合、TiCDCはほとんどの場合、
UPDATEイベントを分割しません。その結果、変更フィード実行時に主キーまたは一意キーの競合が発生し、変更フィードが自動的に再起動される可能性があります。再起動後、TiCDCは競合するUPDATEイベントをDELETEつとINSERTイベントに分割してから、Sorterモジュールに書き込みます。これにより、同一トランザクション内のすべてのイベントが正しく順序付けされ、DELETEイベントすべてがINSERTイベントの前に配置されるため、データレプリケーションが正しく完了します。
MySQL以外のシンクの主キーまたは一意キーのUPDATEイベントを分割する
単一のUPDATE変更を含むトランザクション
v6.5.3、v7.1.1、v7.2.0以降、MySQL以外のシンクを使用する場合、単一の更新変更のみを含むトランザクションにおいて、主キーまたはnull以外の一意のインデックス値がUPDATEイベントで変更されると、TiCDCはこのイベントをDELETEつとINSERTイベントに分割します。詳細については、GitHubのissue #9086ご覧ください。
この変更は主に、CSVおよびAVROプロトコル使用時にTiCDCがデフォルトで新しい値のみを出力し、古い値は出力しないという問題に対処します。この問題により、主キーまたは非NULLの一意のインデックス値が変更された場合、コンシューマーは新しい値しか受信できず、変更前の値を処理する(例えば、古い値を削除する)ことができなくなります。次のSQLを例に挙げましょう。
CREATE TABLE t (a INT PRIMARY KEY, b INT);
INSERT INTO t VALUES (1, 1);
UPDATE t SET a = 2 WHERE a = 1;
この例では、主キーaが1から2に更新されます。イベントUPDATEが分割されていない場合、CSV プロトコルおよび AVRO プロトコルを使用する場合、コンシューマーは新しい値a = 2のみを取得でき、古い値a = 1取得できません。そのため、下流のコンシューマーは古い値1を削除せずに、新しい値2のみを挿入する可能性があります。
複数のUPDATE変更を含むトランザクション
v6.5.4、v7.1.2、v7.4.0以降、複数の変更を含むトランザクションにおいて、 UPDATEイベントで主キーまたはNULL以外の一意のインデックス値が変更された場合、TiCDCはイベントをDELETEとINSERTイベントに分割し、すべてのイベントがINSERTのイベントの前のDELETEのイベントのシーケンスに従うようにします。詳細については、GitHubのissue #9430ご覧ください。
この変更は主に、Kafkaシンクまたはその他のシンクからリレーショナルデータベースへのデータ変更の書き込み時、あるいは同様の操作を実行する際にコンシューマーが遭遇する可能性のある、主キーまたは一意キーの競合に関する潜在的な問題に対処します。この問題は、TiCDCが受信したUPDATEイベントの順序が誤っている可能性があることに起因します。
次の SQL を例に挙げます。
CREATE TABLE t (a INT PRIMARY KEY, b INT);
INSERT INTO t VALUES (1, 1);
INSERT INTO t VALUES (2, 2);
BEGIN;
UPDATE t SET a = 3 WHERE a = 1;
UPDATE t SET a = 1 WHERE a = 2;
UPDATE t SET a = 2 WHERE a = 3;
COMMIT;
この例では、2つの行の主キーを交換する3つのSQL文を実行することで、TiCDCは主キーa 1から2に変更し、主キーa 2から1に変更するという2つの更新変更イベントのみを受け取ります。コンシューマーがこれらの2つのUPDATEイベントをダウンストリームに直接書き込むと、主キーの競合が発生し、変更フィードエラーが発生します。
したがって、TiCDC はこれら 2 つのイベントを 4 つのイベントに分割します。つまり、レコード(1, 1)と(2, 2)削除し、レコード(2, 1)と(1, 2)書き込みます。
主キーまたは一意キーのUPDATEイベントを分割するかどうかを制御する
v6.5.10、v7.1.6、v7.5.3、v8.1.1以降、MySQL以外のシンクを使用する場合、TiCDCはGitHub Issue #11211に記載されているように、 output-raw-change-eventパラメータを介して主キーまたは一意キーのUPDATEイベントを分割するかどうかを制御できるようになりました。このパラメータの具体的な動作は次のとおりです。
output-raw-change-event = false設定すると、主キーまたは null 以外の一意のインデックス値がUPDATEイベントで変更された場合、TiCDC はイベントをDELETEとINSERTイベントに分割し、すべてのイベントがINSERTイベントの前のDELETEイベントのシーケンスに従うようにします。output-raw-change-event = true設定すると、TiCDCはUPDATEイベントを分割せず、 MySQL以外のシンクの主キーまたは一意キーのUPDATEイベントを分割するで説明した問題への対処はコンシューマー側で行います。そうしないと、データの不整合が発生するリスクがあります。テーブルの主キーがクラスター化インデックスである場合、主キーへの更新はTiDB内で依然としてDELETEつとINSERTイベントに分割されますが、この動作はoutput-raw-change-eventパラメータの影響を受けません。
注記
次の表では、UK/PK は主キーまたは一意キーを表します。
リリース6.5の互換性
| バージョン | プロトコル | UK/PK UPDATEイベントの分割 | UK/ UPDATEイベントを分割しない | コメント |
|---|---|---|---|---|
| バージョン6.5.2以下 | 全て | ✗ | ✓ | |
| v6.5.3 / v6.5.4 | 運河/オープン | ✗ | ✓ | |
| バージョン6.5.3 | CSV/アブロ | ✗ | ✗ | 分割しますが、並べ替えは行いません。1を参照してください#9086 |
| バージョン6.5.4 | 運河/オープン | ✗ | ✗ | 複数の変更を含むトランザクションのみを分割して並べ替える |
| v6.5.5 ~ v6.5.9 | 全て | ✓ | ✗ | |
| = v6.5.10 | 全て | ✓ (デフォルト値: output-raw-change-event = false ) | ✓ (オプション: output-raw-change-event = true ) |
リリース7.1の互換性
| バージョン | プロトコル | UK/PK UPDATEイベントの分割 | UK/ UPDATEイベントを分割しない | コメント |
|---|---|---|---|---|
| バージョン7.1.0 | 全て | ✗ | ✓ | |
| バージョン7.1.1 | 運河/オープン | ✗ | ✓ | |
| バージョン7.1.1 | CSV/アブロ | ✗ | ✗ | 分割しますが、並べ替えは行いません。1を参照してください#9086 |
| v7.1.2 ~ v7.1.5 | 全て | ✓ | ✗ | |
| = v7.1.6 | 全て | ✓ (デフォルト値: output-raw-change-event = false ) | ✓ (オプション: output-raw-change-event = true ) |
リリース7.5の互換性
| バージョン | プロトコル | UK/PK UPDATEイベントの分割 | UK/ UPDATEイベントを分割しない | コメント |
|---|---|---|---|---|
| バージョン7.5.2以下 | 全て | ✓ | ✗ | |
| = v7.5.3 | 全て | ✓ (デフォルト値: output-raw-change-event = false ) | ✓ (オプション: output-raw-change-event = true ) |
リリース8.1の互換性
| バージョン | プロトコル | UK/PK UPDATEイベントの分割 | UK/ UPDATEイベントを分割しない | コメント |
|---|---|---|---|---|
| バージョン8.1.0 | 全て | ✓ | ✗ | |
| = v8.1.1 | 全て | ✓ (デフォルト値: output-raw-change-event = false ) | ✓ (オプション: output-raw-change-event = true ) |