データパイプラインは静かに壊れます。ダッシュボードに昨日の数字が表示されたり、上流で列の型が変わってジョブがエラーも出さずに行を落としたり、誰も覚えていないレプリケーションスロットが、ディスクがいっぱいになるまで本番データベースに先行書き込みログを溜め込んだりします。どれも、誰も図に描かなかったホップか契約です。
データパイプラインのアーキテクチャ図は、データがどこから来て、どう移動し、各段階でどこに保存され、誰がいつ変換し、それが新しく正しいことを誰もがどう確認できるかを示します。この記事では、よくある構成である、PostgreSQL からの変更データキャプチャ(CDC)を Kafka 経由でレイクとデータウェアハウスに流す例を図にし、各ホップに何を書くべきかを説明します。
バッチ抽出、CDC、イベント
運用データベースからデータを取り出す一般的な方法は 3 つあります。
| 方式 | 読み取り方 | 一般的な鮮度 | 注意点 |
|---|---|---|---|
| バッチ抽出 | 定期実行のクエリ。たとえば前回の実行以降に updated_at が更新された行 |
数時間 | ソースへの負荷。物理削除は決して現れない |
| ログベースの CDC | データベースのトランザクションログを読む | 数秒から数分 | 読み手のために保持されるログと、テーブルのスキーマへの結合 |
| アプリケーションイベント | サービスがドメインイベントを発行する。多くは Outbox テーブル経由 | 数秒 | コードの変更が必要。イベントは利用者に向けて設計する必要がある |
ログベースの変更データキャプチャ(CDC)は、アプリケーションを変更せずに済み、削除も捉えられ、クエリの負荷もほとんど増えないため、分析用途でよく使われます。そのコストは運用面にあり、図に描くべきなのはそこです。
PostgreSQL でのログベース CDC の仕組み
PostgreSQL は論理デコーディングで変更を公開します。論理デコーディングは先行書き込みログ(WAL)を行の変更のストリームに変換する仕組みで、wal_level = logical、出力プラグイン、レプリケーションスロットが必要です。Debezium の PostgreSQL コネクターは、PostgreSQL 10 以降に標準で含まれる pgoutput プラグインを使います。初回の起動時にテーブルの一貫したスナップショットを取り、その後はコミットされたすべての挿入、更新、削除をストリーミングします。既定では、テーブルごとに topicPrefix.schemaName.tableName という名前の Kafka トピックに送られます。
厄介なのはレプリケーションスロットです。PostgreSQL のドキュメントによると、スロットは、何も接続していないときでも、利用者がまだ必要とする WAL の削除を防ぎます。コネクターが週末のあいだ止まっていると、プライマリーの WAL は増え続けます。max_slot_wal_keep_size という設定はスロットが保持できる WAL の量に上限を設けますが、既定値の -1 は無制限を意味します。上限を設けるとデータベースは守れますが、その代わりにスロットが無効になり、スナップショットの取り直しが必要になります。いずれにしても、スロットの遅延を監視し、スロットを図に描きましょう。
レイクの層とデータウェアハウス
変更が Kafka に入ったら、シンクがそれをストレージに書き込み、変換によって使える形にします。各段階の呼び方としては、Databricks が広めたメダリオンアーキテクチャがよく使われます。
- ブロンズ には、届いたままの生データをロードのメタデータとともに保持します。ソースを読み直さずに再処理できるようにするためです。
- シルバー には、クレンジング、重複排除、標準化を済ませたレコードを保持します。注文ごとに最新の 1 行があり、型とキーがそろった状態です。
- ゴールド には、ダッシュボードやモデルが読む業務レベルのテーブルと集計を保持します。
各層をどこに置くかは設計上の判断です。Apache Iceberg のようなオープンなテーブル形式を使ったオブジェクトストレージにすることも、データウェアハウス自体にすることもできます。実際に置いている場所を描きましょう。コスト、アクセス制御、誰がクエリできるかは、それで決まるからです。
オーケストレーションと契約
ブロンズからゴールドへの変換は、スケジュールに従って、あるいは新しいデータが届いたときに実行されます。それを管理するのは Airflow や Dagster のようなオーケストレーターで、多くの場合 dbt の SQL モデルを実行します。パイプラインが資産になるか負債になるかは、次の 2 点で決まります。
- 契約。 変更イベントのスキーマを互換性ルール付きでスキーマレジストリに登録します。そうすれば、ソーステーブルの互換性のない変更は、レポートの中で静かに壊れるのではなく、プロデューサー側ではっきりと失敗します。
- チェック。 各ステップの後に、鮮度、行数、一意性、NULL のチェックを実行し、失敗したらダッシュボードが間違う前に担当者を呼び出します。
例:オンラインショップ
ショップの PostgreSQL データベースには、注文、支払い、顧客のデータがあります。Debezium は Kafka Connect 上で動き、レプリケーションスロットを通じて WAL を読み、shop.public.orders、shop.public.payments、shop.public.customers を発行します。シンクコネクターは変更イベントをブロンズのテーブルに追記します。オーケストレーターは 15 分ごとに dbt モデルを実行してシルバーとゴールドを作り、続いてデータチェックを実行します。ダッシュボードはゴールドを読みます。
ダイアグラムの内容
- shop DB
- Debezium
- Kafka
- Sink connector
- Schema registry
- Lake and warehouse
- Gold: marts
- Silver: cleaned
- Bronze: raw changes
- BI dashboards
- Orchestrator: dbt
- Data checks
- WAL via slot
- change events
- register schemas
- topic per table
- append raw changes
- merge, dedupe
- aggregate
- SQL
- runs models
- every 15 min
- then checks
図の読み方は次のとおりです。
- 上段は取り込み。 変更はレプリケーションスロットを通じて PostgreSQL を出て、スキーマを登録する Debezium で変更イベントになり、テーブルごとの Kafka トピックを通ってシンクコネクターに届きます。
- 下段は保存で、右から左へ進みます。 シンクが生の変更をブロンズに追記します。変換がそれをシルバーにまとめ、シルバーをゴールドに集計し、ダッシュボードがゴールドを参照します。
- その下は制御。 オーケストレーターが変換とチェックを実行し、チェックが失敗するとアラートが上がります。
- Markdown ブロック には、スロットとスキーマ変更に関する運用ルールを書いています。
ラベルに書くこと
- 最初のエッジには スロットとその上限 を書き、遅延したときに誰に通知されるかも書きます。
- トピック名 か命名ルールを書き、利用者が何を購読すればよいか分かるようにします。
- 変換のエッジには、「15 分ごと、鮮度は 30 分以内」のように スケジュールと鮮度の目標 を書きます。
- 層ごとの保持期間。 多くの場合、履歴を最も長く残すのはブロンズで、再処理のためのバッファーになります。
- 各層と各チェックの 担当者 を書きます。
- 個人データ。 どこに保存され、どこでマスクまたは削除されるかを示しましょう。GDPR のデータマッピング図も参考になります。
よくある間違い
- データベースからデータウェアハウスへの矢印が 1 本だけ。 スロットやスキーマ、そしてデータが失われたり遅れたりしうる場所がすべて隠れてしまいます。
- プライマリーで分析クエリを実行する。 本番のプライマリーに対するバッチ抽出は、顧客のトラフィックと競合します。CDC かレプリカを使い、どちらを使うかを描きましょう。
- 再処理の経路がない。 ブロンズを残していないと、変換にバグがあったときにソースを読み直すことになります。
- 生のテーブルを公開インターフェースにする。 内部テーブルの変更イベントを読む利用者は、スキーマが変わると壊れます。ほかのチーム向けのイベントは、利用者向けに設計したイベントとして発行しましょう。その側面はイベント駆動アーキテクチャの図で扱っています。
- ログに残すだけのチェック。 誰にも通知せずに失敗するテストは、ただのコメントです。
同じシステムのデータベース側については、データベースのレプリケーション図と ER 図の例を参照してください。
Archboard で作る
- Google または GitHub でサインインし、図を作成します。
- データパックから、ソースデータベース用の Postgres ノードと、トピック用の Kafka ノードを追加します。
- C4 モデルパックから、Debezium、スキーマレジストリ、シンクコネクター、ダッシュボード用の コンテナ ノードを追加します。
- 基本パックの フレーム を追加して Lake and warehouse という名前を付け、その中にデータパックの オブジェクトストア ノードを 2 つ(ブロンズ用とシルバー用)と、ゴールド用の データウェアハウス を置きます。
- DevOps パックから、オーケストレーター用の パイプライン と、データチェック用の アラート を追加します。制御のエッジは破線にします。
- スロットとスキーマのルールを書いた基本パックの Markdown ブロックを追加します。
この例で使うダイアグラムノードは 13 個で、Free プランの範囲に収まります。ソースのスキーマを SQL で管理しているなら、PostgreSQL から ER 図を作るツールで、トピックの背後にあるテーブルを描けます。
よくある質問
CDC はバッチ抽出より優れていますか? 鮮度、削除の扱い、ソースへの負荷の点では、たいていそうです。バッチのほうが運用はシンプルで、数時間の遅れが許容でき、行が物理削除されないなら十分です。
CDC に Kafka は必要ですか? Debezium は Kafka Connect 上で動かすのが最も一般的で、Kafka があれば再処理も、多数の利用者への配信もできます。ほかの構成もあります。実際に運用している転送手段とその保持期間を描きましょう。
スキーマの変更はどう扱えばよいですか? 互換性ルール付きでスキーマを登録し、列は変更せずに追加し、下流のモデルが新しい列を許容できるようにします。契約が置かれる場所なので、レジストリを図に描きましょう。
データ品質のチェックはどこで実行すべきですか? オーケストレーションされた実行の一部として各層の後に実行し、担当者にアラートを送ります。ゴールドの鮮度チェックで、静かな障害の大半を捉えられます。
パイプラインの図を最新に保つにはどうすればよいですか? コネクターとオーケストレーターの設定と同じプルリクエストで図も変更しましょう。古びないアーキテクチャ図も参照してください。