ログベースCDC(変更データキャプチャ)
夜間バッチのフルロードをやめ、業務DBの変更を数秒で分析基盤やデータレイクへ届けたい人へ。トランザクションログ(binlog/WAL)を読むログベースCDCの仕組みと、初期スナップショット・順序保証・下流配信の原理がわかります。
- 1.ログベースCDCはアプリを変更せず、DBがコミット済みのトランザクションログ(MySQLのbinlog/PostgreSQLのWAL)を購読して、INSERT/UPDATE/DELETEを構造化イベント化する。分析基盤側では、毎晩テーブル全体を再取り込みするフルロードを、低遅延の増分ストリームに置き換えられる。
- 2.巨大な既存テーブルは初期スナップショットで読み切り、その一貫読み取りの開始ログ位置を先に記録してから増分ログへ接続することで、スナップショットと増分の継ぎ目に欠落も重複も出さない。増分スナップショットならテーブルを止めずにチャンク単位で取り込める。
- 3.ログはコミット順の単一系列なので、行の主キーでパーティショニングすれば同一キーのコミット順を保てる。コネクタはログ位置(LSN/GTID)をオフセットとして永続化し、先に送信してから保存するため配信は少なくとも一度になる。下流はマージ(upsert)とべき等処理で収束させる。
分析基盤にとってのCDC:フルロードからの脱却
データウェアハウスやデータレイクへ業務DBの内容を運ぶ古典的な方法は、夜間の一括ロード(フルロード) です。毎晩テーブル全体を SELECT で吸い出し、下流を丸ごと置き換える。小さいうちは動きますが、テーブルが数億行に育つと破綻します。取り込みが一晩で終わらず、DBには重いスキャンが走り、分析データは常に「昨日の断面」で止まります。
差分だけを運ぼうと updated_at 列でポーリングしても、原理的な穴が残ります。削除された行はクエリ結果から消えるので下流に伝わらず、ポーリング間隔内の中間更新は最後の値しか見えません。根は同じで、ポーリングはDBの「現在の状態」を見るだけで、状態に至った「変更の系列」を見ていない。
ログベースCDC(Change Data Capture) は、この変更系列そのものを取り出します。DBがクラッシュリカバリとレプリケーションのために内部で持つトランザクションログを、レプリカのふりをして購読するのです。分析基盤の文脈では、これはフルロードを低遅延の増分ストリームへ置き換え、レイクハウスをほぼリアルタイムに保つ土台になります。
MySQLのbinlogもPostgreSQLのWAL(Write-Ahead Log)も、コミット済みの変更を起きた順に追記する単一系列です。DBはこれでレプリケーションを行うので、CDCコネクタはレプリカとして登録し、DBが正として確定した変更だけを受け取ります。アプリのコード変更もトリガー追加も不要で、捕捉漏れも原理的に起きません。分析基盤側から見れば「業務DBに触れずに、その全変更をタップできる」ことが最大の価値です。
ログから変更イベントへ:デコードの中身
CDCコネクタ(Debeziumが代表例)は、自身をDBのレプリケーションクライアントとして登録します。PostgreSQLなら論理レプリケーションのスロットを作り、MySQLならレプリカサーバーとしてbinlogをストリーム購読します。受け取る生ログは物理的・低レベルな表現なので、これを論理的な行変更イベントへデコードします。
# CDCイベント1件の論理構造(概念図)
op = "u" # c=作成, u=更新, d=削除, r=スナップショット読み出し
before = { id: 7, qty: 3 } # 更新前の行イメージ(UPDATE/DELETEで意味を持つ)
after = { id: 7, qty: 5 } # 更新後の行イメージ(INSERT/UPDATEで意味を持つ)
source = { lsn, gtid, table, ts_ms } # ログ位置・トランザクション・発生時刻
流すのは「実行されたSQL文」ではなく変更された行イメージである点が要点です。これにより下流は文を再解釈せず、行の値だけで状態を組み立てられます。ただし before(前イメージ)を取れるかはDB設定に依存します。PostgreSQLはテーブルの REPLICA IDENTITY が FULL でないと前イメージが主キーだけになり、MySQLはbinlogが ROW 形式(STATEMENT ではなく)でないと行単位の前後像が得られません。分析基盤で変更差分の履歴や削除の伝播を正しく扱うには、この設定が前提になります。
初期スナップショットと増分の接続:継ぎ目を埋める
分析基盤への取り込みで最初にぶつかるのが、既にある大量データです。ログには「これから起きる変更」しか流れないので、まず既存全行を読む初期スナップショットが要ります。難所は、スナップショット中もアプリが書き込みを続けるため、スナップショットと増分ログの境目で欠落も重複も出さないことです。
鍵は順序です。一貫読み取りを始める時点のログ位置を先に記録し、その読み取りでテーブルを読み切り、記録した位置から増分ログへ接続します。
1) 現在のログ位置 P を記録する
2) 一貫読み取り(スナップショット)で全行を読み、op="r" として流す
3) 位置 P から増分ログを購読し、以降の変更を op=c/u/d で流す
スナップショット中に起きた更新は、(2) の行イメージか (3) の増分ログのどちらか(または両方)に現れます。重複しても、後述のとおり配信が少なくとも一度でべき等前提なので、同じキーの後勝ちで収束します。逆に位置 P をスナップショット開始時点に取るのが肝で、これより後ろに取ると P 以前の更新を取りこぼします。
一貫読み取りを得るために、素朴な実装はスナップショット中にテーブルロックや長時間トランザクションを使い、稼働中の業務DBへ負荷と遅延を与えました。Debeziumの増分スナップショット(ウォーターマーク方式)は、テーブルを主キー順のチャンクに分割し、各チャンク読み出しの前後に増分ログへ低・高のウォーターマークを書き込みます。ウォーターマークの間に流れた同一キーの変更をスナップショット結果から差し引くことで、ロックなし・増分ストリームと並走しながら巨大テーブルを取り込めます。数十億行の初期ロードを、業務を止めずに進められるのが分析基盤にとって決定的です。
順序保証:単一系列を並列化に落とし込む
分析基盤の下流(Kafka、オブジェクトストレージ上のテーブル、ストリーム処理)はスループットのために並列化します。ところが同じ行への「在庫を5にする→3にする」が逆順で適用されると最終状態が壊れる。ここでログの性質が効きます。トランザクションログはコミット順の単一全順序系列なので、CDCはその順序をそのまま取り出せます。問題は、それを並列化した下流でどう保つかです。
| スコープ | 順序保証 | 理由 |
|---|---|---|
| 単一パーティション内 | コミット順の全順序を維持できる | ログが単一系列で、その順に読み出し配信するため |
| 同一行(同一キー) | 常に正しい順序になる | 同じキーは同じパーティションへ送られるため(キーでハッシュ) |
| 異なるパーティション間 | 全順序は保証されない | スループットのためキーで分割し並列に流すため |
定石は、イベントを行の主キーでパーティショニングすることです。こうすれば同じ行への変更は同じパーティションに入り、コミット順を維持する。異なる行どうしの相対順序は緩みますが、行ごとに正しければ大半の分析処理は整合します。グローバルな全順序が必要なら単一パーティションにするしかなく、並列度を失う——これは順序と並列性のトレードオフです。分散データ処理の一貫性設計は/devops/の一貫性・順序の議論と地続きです。
オフセットと配信保証:少なくとも一度になる理由
コネクタはログ内の現在位置をオフセットとして永続化します。PostgreSQLなら LSN(Log Sequence Number)、MySQLなら GTID(Global Transaction ID) やファイル名+位置です。再起動時はこの保存位置から読み直し、欠落なく再開します。
問題は「いつオフセットを保存するか」です。イベントを下流へ書く処理と、オフセットを保存する処理は別操作なので、その間でクラッシュすると齟齬が出ます。
ケースA: イベント送信 → (クラッシュ) → オフセット未保存
→ 再起動後、同じ位置から再送 → 重複(at-least-once)
ケースB: オフセット先に保存 → (クラッシュ) → イベント未送信
→ 再起動後、保存位置から進む → 欠落(at-most-once)
業務データの欠落は致命的なので、CDCは先にイベントを送ってからオフセットを保存する設計を採り、少なくとも一度(at-least-once) の配信になります。すなわち重複は定常的に起こりうる前提で、下流は同じイベントが二度来ても壊れないべき等処理が必要です。分析基盤ではこれを、source 内のLSN/GTIDや (主キー, ログ位置) をべき等キーとし、行の主キーでマージ(upsert) して吸収するのが定番です。IcebergやDelta Lakeのようなマージ可能なテーブル形式は、まさにCDCの後勝ちマージを表現するために使われます。
配信そのものが少なくとも一度でも、下流の適用がべき等なら結果は一意に収束します。CDCイベントは主キーを持つので、下流テーブルへ「同じ主キーなら上書き、削除フラグなら物理削除」というマージを行えば、重複到着も順序前後(同一キー内は保証済み)も安全に畳めます。ストリーム処理側でログ位置を進捗管理と組み合わせれば、実務上のちょうど一度の効果(effectively-once) を得られます。
下流アーキテクチャ:CDCストリームを基盤に流す
取り出したCDCイベントは、多くの場合ログ指向のメッセージ基盤(Kafka等)へ主キーでパーティション分割して載せ、そこから複数の下流が並列に購読します。分析基盤では二つの読み方が要点になります。
- 追記ログとして:全変更イベントを時系列に保持すれば、任意時点の状態を再構築でき、監査・履歴分析・タイムトラベルが可能になります。CDCは変更の全系列を持つので、状態のスナップショットだけでなく「いつ何が起きたか」を失いません。
- 最新状態テーブルとして:主キーで畳んで「各行の現在値」を得れば、フルロードで作っていた複製テーブルを増分で維持できます。ログのコンパクション(同一キーの古いイベントを間引く)を使えば、最新状態だけを効率よく保てます。
PostgreSQLの論理レプリケーションスロットは、CDCがまだ読んでいないWALをDB側に保持させ続けます。コネクタや下流が長時間止まると未読WALが溜まり、最悪ディスクを食い潰して業務DB本体を停止させます。MySQLのbinlogも保持期間内に読み切れないと欠落します。CDCは業務DBと分析基盤を密結合させるので、スロットの遅延(保持WAL量)とコネクタのラグを必ず監視し、下流障害時の増分ログ保持容量を見積もっておくことが不可欠です。
スキーマ変更への追従
長期運用で必ず来るのがスキーマ変更(DDL) です。列の追加・削除・型変更が起きると、ログ中の行イメージの構造も変わります。重要なのは、過去のイベントは当時のスキーマで解釈しなければならない点です。コネクタは時点ごとのスキーマ履歴を保持し、各ログ位置のイベントをその位置で有効だったスキーマで復元します。
分析基盤では、これをスキーマレジストリ(Avro/Protobuf等)と組み合わせ、下流のテーブル形式へスキーマ進化を伝えます。列の追加のような後方互換な変更なら古い消費者を壊さず進化できますが、列の削除や非互換な型変更は下流を一斉に壊しかねません。CDCを敷くと業務DBのスキーマ変更が分析基盤全体への公開API変更になるため、追加中心の進化に寄せ、削除は猶予期間を置くのが安全です。
まとめ
- ログベースCDCは業務DBのトランザクションログ(binlog/WAL) を読み、INSERT/UPDATE/DELETEを構造化イベント化する。分析基盤では夜間フルロードを低遅延の増分ストリームへ置き換えられ、削除や中間状態も取りこぼさない。
- 流すのは「SQL文」ではなく変更後(と可能なら変更前)の行イメージ。前イメージには
REPLICA IDENTITY FULLやbinlogのROW形式が要る。 - 初期スナップショットは開始時点のログ位置を先に記録してから全行を読み、その位置以降の増分ログへ接続して継ぎ目を埋める。増分スナップショットならロックなしで巨大テーブルを取り込める。
- ログはコミット順の単一全順序系列なので、主キーでパーティショニングすれば同一行の順序を保てる。全順序と並列度はトレードオフ。
- コネクタはLSN/GTIDをオフセットに永続化し、先に送信→後で保存とするためat-least-once。下流は主キーでのマージ(upsert) とべき等処理で収束させ、実質ちょうど一度にする。
- CDCストリームは追記ログ(履歴・監査) と最新状態テーブルの両面で使える。下流停止がDBの可用性に跳ね返るため、スロット遅延とラグの監視が必須。
データ工学 Article
ログベースCDC(変更データキャプチャ)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
CDC
比較で見る軸
難易度: advanced / カテゴリ: データ工学 / タグ数: 6
導入後に効く点
巨大な既存テーブルは初期スナップショットで読み切り、その一貫読み取りの開始ログ位置を先に記録してから増分ログへ接続することで、スナップショットと増分の継ぎ目に欠落も重複も出さない。増分スナップショットならテーブルを止めずにチャンク単位で取り込める。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データ工学
- タグ数
- 6
判断チェックリスト
- 自社の用途が「CDC / データ基盤」に近いか確認する。
- 強みである「ログベースCDCはアプリを変更せず、DBがコミット済みのトランザクションログ(MySQLのbinlog/PostgreSQLのWAL)を購読して、INSERT/UPDATE/DELETEを構造化イベント化する。分析基盤側では、毎晩テーブル全体を再取り込みするフルロードを、低遅延の増分ストリームに置き換えられる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。