Claude Media
Claude CodeでDebeziumのCDC設定をレビューする — スロット肥大とWAL枯渇を防ぐ点検

Claude CodeでDebeziumのCDC設定をレビューする — スロット肥大とWAL枯渇を防ぐ点検

DebeziumのPostgreSQLコネクタ設定を、スロット名・スロット削除・スナップショットモード・tombstone・ハートビートの5点でClaude Codeに点検させ、WAL肥大によるディスク枯渇を見落とさない手順です。

DebeziumのCDC設定は、コネクタのJSONが正しく動いていても危険なことがあります。PostgreSQLのレプリケーションスロットは、コネクタが止まっている間もWALを保持し続けるからです。設定の見落としは数日後に、データベースのディスク枯渇という形で出ます。Claude Codeには、コネクタJSONと、スロットの実状態を返す読み取り専用のクエリを並べて読ませると、この種の見落としを洗い出せます。ここではDebeziumのPostgreSQLコネクタを対象に、点検の観点と、Claude Code側の設定をまとめます。

なぜCDC設定のレビューはディスクの話になるのか

Debeziumは、PostgreSQLの論理デコーディングを使い、WAL(先行書き込みログ)から変更を取り出します。論理デコーディングはレプリケーションスロットを使い、スロットは、Debeziumが停止していても必要なWALセグメントをすべて保持することが保証されています。保証の裏返しとして、スロットが長く使われないとディスクを食い、カタログの肥大も起きます。Debeziumのドキュメントも、レプリケーションスロットを注意深く監視するよう求めています。

つまりコネクタ設定のレビューは、機能が動くかどうかではなく、止まったときや偏ったときにWALがどう振る舞うかを見る作業です。人間のレビューで漏れやすいのは、コネクタJSONだけを見て、データベース側の状態を見ないことです。Claude Codeには、両方を同じ場で読ませられる利点があります。

点検する5つの設定と、見るべき理由

Debeziumの設定項目のうち、WAL枯渇と直結するものを絞ります。

設定既定値点検の観点
slot.name既定値debezium点検の観点コネクタごとに別名か。使い回しは禁止
slot.drop.on.stop既定値false点検の観点本番で true になっていないか
snapshot.mode既定値initial点検の観点再起動時の挙動を理解した値か
tombstones.on.delete既定値true点検の観点Kafka側のログ圧縮と整合しているか
heartbeat.interval.ms既定値0点検の観点低トラフィックの表だけ監視していないか

slot.name — コネクタとスロットは1対1にする

複数のコネクタを展開するときは、それぞれに一意のスロット名を付けます。スロットが記録するLSNは1つだけで、1つの消費者の位置しか追えません。複数のコネクタが同じスロットを共有すると、各変更はそのうち1つにしか届かず、残りは取りこぼします。PostgreSQLはこの誤設定に警告を出さないため、データ欠落は静かに起きます。

レビューでは、リポジトリ内のすべてのコネクタJSONを横断し、slot.name の重複を見ます。これは1ファイルずつ読む人間より、全ファイルを一度に読めるClaude Codeが得意な作業です。既定の debezium のまま複数環境に展開していないかも、同時に拾えます。

slot.drop.on.stop — 本番はfalseのまま

true にしてよいのはテスト・開発環境だけです。true だとコネクタが正常停止したときにスロットが削除され、データベースはWALを破棄できます。再起動時はスナップショットをやり直すか、Kafka Connectのオフセットトピックにある永続オフセットから続きます。

一方 false(既定)ではスロットが残るので、止めっぱなしのコネクタがWALを溜め続けます。ここで見るべきは値そのものより、運用側にコネクタを停止したまま放置しない仕組みがあるかどうかです。Claude Codeには、JSONの値と、停止中のコネクタに対するアラート定義の有無を両方探させます。

snapshot.mode — 再起動で何が起きるかを言える値にする

既定の initial は、コネクタ名に対するオフセットが記録されていないときだけスナップショットを取ります。always は起動のたびに取ります。no_data はスナップショットを一切取らず、保存済みLSNがあればそこから、なければスロット作成時点から流れ始めます。このモードを使えるのは、関心のあるデータがすべてWALに残っていると分かっている場合だけです。when_needed は、オフセットが見つからないか、記録済みオフセットの位置がサーバー上に無いときだけスナップショットを取ります。

レビューの問いは1つです。「このコネクタが明日の朝に再起動したとき、何が起きるか」を、設定値から説明できるかどうかです。always が大きな表に付いていれば、再起動のたびに全件がKafkaに流れます。

tombstones.on.delete — Kafka側の設定と対で読む

true(既定)では、削除操作が削除イベントとその後のtombstoneイベントで表されます。tombstoneがあるおかげで、トピックでログ圧縮が有効なとき、削除された行のキーに関するイベントをKafkaがすべて消せます。false にすると削除イベントだけが出ます。

見落としやすいのは、コンシューマー側の仕様と組になっている点です。tombstoneを無視する下流を作っているのに、圧縮が無効なトピックで true のままになっていると、設定の意図が誰にも分からなくなります。レビューでは、設定値と並べてトピックの圧縮設定を確認する項目を立てます。

heartbeat.interval.ms — 低トラフィックの表を守る

既定は 0 で、ハートビートは送られません。ハートビートが必要なのは、追跡しているデータベースで更新が多いのに、キャプチャ対象の表の更新がごく少ない場合です。この状況ではコネクタがWALを読んでもKafkaにほとんど出力せず、最新のLSNをデータベースに返す機会がありません。データベースは処理済みのWALを保持し続けます。

ハートビートを有効にすると、コネクタが最新のLSNを返し、データベースが不要なWALのディスク領域を回収できるようになります。同じ原因は、1つのPostgreSQLインスタンスに複数のデータベースがあり、高トラフィックのものと低トラフィックのものが共存するときにも起きます。WALは全データベースで共有されるのに、スロットはデータベースごとだからです。

対策は heartbeat.action.query の利用です。低トラフィック側のデータベースにハートビート用の表を作り、そこへ行を挿入するクエリを指定します。ハートビート用の表は、publication.name で指定したパブリケーションに含める必要があります。パブリケーションが FOR ALL TABLES でなければ、ALTER PUBLICATION ... ADD TABLE で明示的に加えます。

Claude Codeに点検させる準備

データベース側の状態は、読み取り専用のMCPサーバーで渡す

コネクタJSONだけでは、スロットの現状は分かりません。DBHub(@bytebase/dbhub)の接続文字列に読み取り専用のデータベースユーザーを入れれば、Claudeが実行するクエリでデータは変更されません。この書き方をスロットの確認用に応用します。

claude mcp add --transport stdio db --scope project -- \
  npx -y @bytebase/dbhub \
  --dsn "postgresql://readonly:<パスワードは環境側で管理>@db.example.com:5432/appdb"

--scope project を付けると、.mcp.json にサーバー設定が書かれ、チームで共有できます。ただし接続文字列にパスワードを直書きしたままコミットしないでください。値は環境変数などで渡す形にするのが安全です。プロジェクトスコープのサーバーは、対話セッションで使う前に承認のプロンプトが出ます。なお、claude -p のような非対話実行では、そのプロンプトが出ずにサーバーが読み込まれます。自動実行にこの構成を載せるときは注意が要ります。

この読み取り専用ユーザーでスロットの状態を引きます。特に読むのは restart_lsn と confirmed_flush_lsn の2列です。

SELECT slot_name, active, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots;

confirmed_flush_lsn は、コネクタがデータを受け取ったLSNです。これより古いデータは使えないので、データベースが回収を担当します。restart_lsn は、コネクタが必要とする可能性のある最古のWALのLSNです。ドキュメントでは、confirmed_flush_lsn が着実に進んでいて restart_lsn が遅れているだけなら、データベースがまとめて回収するのを待つ通常の挙動であり、対処は不要としています。逆に confirmed_flush_lsn が止まっているなら、ハートビートやコネクタ停止を疑います。active 列の意味は、PostgreSQLの pg_replication_slots ビューの説明で確かめられます。

秘密情報を読ませない

コネクタJSONにはデータベースの接続情報が含まれるのが普通です。Read(./.env) のような規則で、特定ファイルの読み取りを拒否できます。コネクタ設定の置き場所に合わせ、次のように書けます。

{
  "permissions": {
    "deny": [
      "Read(./connectors/secrets/**)",
      "Read(./.env)"
    ]
  }
}

deny、ask、allowの順で評価され、denyが最優先です。広いdenyの中に、狭いallowで例外を作ることはできません。パスワードを別ファイルに分け、そのファイルだけを拒否するのが、この規則に合う構成です。拒否規則の使い方は、依存ライブラリのソースを読ませるときのdeny設定でも扱っています。

点検の観点はCLAUDE.mdとサブエージェントに固定する

毎回「Debeziumの設定を見て」と頼むと、点検の観点が日によって変わります。観点はプロジェクトのCLAUDE.mdに書いておきます。

## Debezium コネクタ設定のレビュー規約
- connectors/ 配下の全JSONを横断し、slot.name の重複を最初に確認する
- slot.drop.on.stop が true のコネクタは、本番用ファイルにないか確認する
- snapshot.mode は「再起動時に何が起きるか」を1行で説明してから評価する
- heartbeat.interval.ms が 0 のコネクタは、同じインスタンスに他DBがあるかを確認する
- tombstones.on.delete は、対応するトピックの圧縮設定とセットで読む
- 結果は設定名・現在値・懸念・確認に使ったクエリ の表で返す

これを毎回の点検に使うなら、サブエージェントに切り出す方法もあります。サブエージェントは独自のコンテキストウィンドウで動き、使えるツールを限定できます。

---
name: cdc-config-reviewer
description: Debezium コネクタ設定の点検。コネクタJSONの変更時に使う。
tools: Read, Grep, Glob
model: sonnet
---
connectors/ の JSON を読み、slot.name の重複、slot.drop.on.stop、
snapshot.mode、tombstones.on.delete、heartbeat.interval.ms を点検して表で返す。
設定の書き換えはしない。

tools を Read, Grep, Glob に絞ってあるので、このサブエージェントは設定ファイルを書き換えられません。データベースの状態確認(MCPサーバー経由のクエリ)は、メインの会話で行います。分担は、設定の静的な読み取りをサブエージェントに、実状態の確認を主会話に置く形です。

点検の実行と、結果の読み方

準備ができたら、レビューの指示を出します。指示は短くて足ります。

connectors/ の全コネクタ設定を cdc-config-reviewer で点検して。
その後、db サーバーで pg_replication_slots を読み、
JSON の slot.name と突き合わせて、スロットが無いコネクタと
コネクタが無いスロットを両方挙げて。

返ってくる結果で、特に見る点は3つあります。

  • JSONにあるのにデータベースに存在しないスロット名。slot.name の未作成か、誤記の可能性があります
  • データベースにあるのにどのJSONにも対応しないスロット。使われていないスロットの候補で、WALを溜め続けます
  • active でないのに restart_lsn が古いスロット

3つめの判断は、Claudeの出力を鵜呑みにせず、人間が確認します。ここで見つかったスロットを削除するかどうかは、クエリで判断する範囲を超えます。削除は、コネクタ側の再開手順と一緒に決める作業です。読み取り専用ユーザーを使うのは、Claudeに削除させないためでもあります。

レビューで見落としやすい前提

  • PostgreSQLのバージョン: slot.failover は、プライマリがPostgreSQL 17以降のときに使える設定です。それ以前のバージョンでは、フェイルオーバー対応のスロットは作られません
  • 複数インスタンスの展開: 同じサーバーに複数のコネクタを置くとき、スロットだけでなくパブリケーションも別にする必要があります
  • RDSの場合: AWS RDS上のPostgreSQLでは、データベース自身のシステム表への書き込みが、およそ5分おきにクライアントから見えなくなります。アイドル状態でも、ハートビートと同様に定期的なイベントの発行が解決策になります

レプリケーション遅延の側から原因を探す場合は、レプリケーション遅延を切り分ける手順が参考になります。ディスクを実際に食っているテーブル側の確認は、autovacuumとテーブル肥大の調査で扱っています。

まとめ

Debeziumの設定レビューで価値が出るのは、コネクタJSONの全数横断と、データベースの実状態との突き合わせです。slot.nameの重複、slot.drop.on.stop の値、スナップショットの再起動時挙動、tombstoneと圧縮の整合、ハートビートの有無を、CLAUDE.mdとサブエージェントに固定すれば、点検の観点がぶれません。読み取り専用のデータベースユーザーと、秘密情報へのdenyを前提に置けば、Claude Codeに任せる範囲は安全に広がります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. debezium.io/documentation/reference/stable/connectors/postgresql.html
  2. code.claude.com/docs/en/mcp
  3. code.claude.com/docs/en/permissions
  4. code.claude.com/docs/en/subagents
この記事を共有:XはてブLinkedIn