データレコンシリエーション:マイグレーションの正しさを証明する

データマイグレーションが完了しました。スクリプトはエラーなく終了し、ログもきれいです。チームは次の作業に移ろうとしています。しかし、心の奥底で、こんな不安がよぎっていませんか?「本当にすべての行が正しく移行されたのか?値が静かに壊れていないか?」

ここで多くのチームが気づくのは、「成功したマイグレーション」と「正しいマイグレーション」は別物だということです。スクリプトが終了コード0で終了しても、間違ったデータが生成される可能性があります。フィルターが含むべき行を除外してしまうかもしれません。型変換がエラーを出さずに値を切り詰めてしまうかもしれません。データベースは、こちらから明示的に問い合わせない限り、これらの問題を教えてくれません。

そこでレコンシリエーション(データ整合性検証)の出番です。これは、マイグレーション前後のデータを比較し、データの損失、変更、破損がないことを証明するプロセスです。マイグレーション完了を宣言する前の、最終チェックポイントです。

エラーのないスクリプトだけでは不十分な理由

データマイグレーションに関する厄介な真実は、正しさとエラーのない実行は別物だということです。マイグレーションスクリプトは技術的には完全に動作しても、誤った結果を生み出す可能性があります。

よくあるシナリオを考えてみましょう。非アクティブユーザーを除外するWHERE句を使ってテーブルを移行したとします。スクリプトは実行され、行は正常に挿入され、古いテーブルは非推奨になりました。数週間後、誰かがアクティブユーザーのグループが欠落していることに気づきます。フィルターが厳しすぎたか、条件が間違っていたのです。スクリプトは決して失敗しませんでしたが、データは間違っています。

ログのエラーはこの種の問題を捉えません。データベースエンジンは、行の欠落が間違いであることを認識しません。INSERT文が正常に実行されたことしか認識しません。こうした静かな障害を捉える唯一の方法は、ソースデータとターゲットデータを直接比較することです。

実践的なレコンシリエーションのアプローチ

レコンシリエーションは複雑である必要はありません。最もシンプルで効果的な方法は、チェックサム比較です。ファイルのチェックサムを取る代わりに、データのバッチに対してチェックサムを計算します。

テーブルマイグレーションの場合、以下のように機能します:

  1. ソーステーブルから行のバッチを読み取る。
  2. バッチ全体のハッシュを計算する(例:すべてのカラム値を連結したものに対してMD5やSHA256を使用)。
  3. 同じ順序でターゲットテーブルから同じバッチを読み取り、同じハッシュを計算する。
  4. 2つのハッシュを比較する。

ハッシュが一致すれば、バッチは同一です。一致しなければ、どのバッチに問題があるか正確にわかり、その特定の行範囲を調査できます。

大規模なテーブルの場合、バッチ処理は不可欠です。数百万行を一度にメモリにロードするのは避けるべきです。ほとんどのデータベースでは、1バッチあたり1,000~10,000行が適切なサイズです。複数のバッチを並行して実行することで、処理を高速化できます。

以下のフローチャートは、バッチごとのレコンシリエーションプロセスを示しています:

flowchart TD A[開始] --> B[ソーステーブルからバッチを抽出] B --> C[バッチのハッシュを計算] C --> D[ターゲットテーブルから同じバッチを抽出] D --> E[バッチのハッシュを計算] E --> F{ハッシュを比較} F -- 一致 --> G[バッチを検証済みとしてマーク] F -- 不一致 --> H[調査対象としてバッチにフラグ] G --> I{他にバッチはあるか?} H --> I I -- はい --> B I -- いいえ --> J[行数を比較] J --> K{行数は一致?} K -- はい --> L[レポート生成: マイグレーション検証済み] K -- いいえ --> M[不一致をフラグし調査] L --> N[終了] M --> N

以下は、2つのテーブルに対してこのアプローチを実装する具体的なSQLクエリです:

-- ソーステーブルとターゲットテーブルの行数とチェックサムを比較
WITH source_checksums AS (
    SELECT
        COUNT(*) AS row_count,
        MD5(STRING_AGG(CAST(column1 AS TEXT) || '|' || CAST(column2 AS TEXT) || '|' || CAST(column3 AS TEXT), ',' ORDER BY id)) AS batch_hash
    FROM source_table
),
target_checksums AS (
    SELECT
        COUNT(*) AS row_count,
        MD5(STRING_AGG(CAST(column1 AS TEXT) || '|' || CAST(column2 AS TEXT) || '|' || CAST(column3 AS TEXT), ',' ORDER BY id)) AS batch_hash
    FROM target_table
)
SELECT
    '行数不一致' AS issue
FROM source_checksums, target_checksums
WHERE source_checksums.row_count <> target_checksums.row_count
UNION ALL
SELECT
    'チェックサム不一致' AS issue
FROM source_checksums, target_checksums
WHERE source_checksums.batch_hash <> target_checksums.batch_hash;

このクエリは、各テーブルの全行に対して単一のチェックサムを計算し(安定した順序を使用)、行数とハッシュの両方を比較します。いずれかが異なる場合、クエリは何が問題だったかを明確に示します。

チェックサム以外に確認すべきこと

チェックサムはほとんどの問題を捉えますが、確認すべき項目はそれだけではありません。いくつかの追加チェックを行うことで、それほど手間をかけずに確信を深められます。

行数。 これは最もシンプルなチェックです。ターゲットテーブルの行数はソーステーブルの行数と一致する必要があります。数が異なる場合、マイグレーションロジックに何か問題があります。

NULL値。 マイグレーションによって、NULLカラムがデフォルト値に変更されたり、その逆が発生することがあります。カラムごとのNULLの数をソースとターゲットで比較してください。不一致がある場合、型変換の問題やデフォルト値制約の誤った適用が原因であることがよくあります。

値の分布。 いくつかの重要なカラムを選び、その値の分布を比較します。例えば、ソーステーブルにステータスが「アクティブ」のユーザーが1,000人、「非アクティブ」のユーザーが500人いる場合、ターゲットテーブルも同じ数値である必要があります。大きな差がある場合は、マイグレーションのフィルターや変換ロジックにバグがあることを示唆しています。

エッジケース。 特殊文字、非常に長い文字列、境界付近の日付、負の数など、扱いが難しいとわかっている特定の行をテストします。マイグレーションがこれらを正しく処理できていれば、全体的なロジックが健全である良い兆候です。

レコンシリエーションをパイプラインの一部にする

レコンシリエーションは、深夜のマイグレーション後に誰かが思い出して実行するような、一回限りの手動タスクであってはなりません。自動化し、デプロイメントパイプラインに統合する必要があります。

マイグレーションとバックフィルのステップが完了した後に実行されるレコンシリエーションスクリプトを作成します。そのスクリプトは以下のことを行うべきです:

  • ソースデータベースとターゲットデータベースの両方に接続する。
  • バッチチェックサム比較を実行する。
  • 行数、NULL数、値の分布をチェックする。
  • 不一致の詳細なレポートを生成する。
  • 結果を通知する(メール、Slack、チームが使用するもの)。

レコンシリエーションが成功すれば、パイプラインは次のステップに進めます。失敗した場合、パイプラインは停止し、チームに警告を出すべきです。これにより、誰も気づかないうちに誤ったデータが本番環境に到達するのを防ぎます。

レコンシリエーションを自動化することで、繰り返し可能にもなります。すべてのマイグレーションが同じ検証プロセスを通過します。誰かがスクリプトを実行するのを覚えているか、正しい項目をチェックしているかに依存する必要がありません。パイプラインがそれを強制します。

レコンシリエーションではないもの

レコンシリエーションは、ドライランやバックフィル戦略の代わりにはなりません。各ステップには異なる目的があります。

  • ドライランは、本番データに影響を与えずにマイグレーションロジックが機能することを検証します。
  • バックフィルは、影響を最小限に抑えるために、管理可能なチャンクで実際のデータ転送を処理します。
  • レコンシリエーションは、バックフィルが正しい結果を生成したことを証明します。

レコンシリエーションは最終的な品質ゲートと考えてください。これにより、以前のすべてのステップが意図したとおりに機能したことが確認されます。レコンシリエーションが成功すれば、データの準備が整ったと確信できます。失敗した場合は、問題を修正し、マイグレーションを最初から再実行します。

実践的なレコンシリエーションチェックリスト

次のマイグレーションのためにレコンシリエーションを設定する際の、簡潔なチェックリストです:

  • バッチごとのチェックサム比較(1バッチあたり1,000~10,000行)
  • ソースとターゲットの行数一致
  • カラムごとのNULL数一致
  • 主要カラムの値分布一致
  • エッジケースの検証(特殊文字、境界値)
  • パイプラインに統合された自動化スクリプト
  • 失敗時の通知と詳細な不一致レポート

まとめ

マイグレーションは、データが正しいことを証明するまで完了しません。エラーのない実行だけでは不十分です。レコンシリエーションは、ソースデータとターゲットデータを直接比較することで、その証明を提供します。自動化し、毎回実行し、レコンシリエーションの失敗をテストの失敗と同じように扱ってください:停止し、調査し、修正してから次に進みます。