How to migrate between tables
リファクタリングに伴いテーブル間のデータ移行が必要になる場合があります。 その際の安全な旧テーブルから新テーブルへのデータ移行の手順について整理します。
以下の記事が参考になりました。
目次
新テーブルへのデータ移行の手順
新テーブルへのデータ移行は以下の流れになります。
- 旧テーブルから新テーブルへ同期する
- 同期の完全性を検証する仕組みを導入する
- 読み込み先を新テーブルにする
- 書き込み先を新テーブルにする
旧テーブルから新テーブルへの同期
まずは旧テーブルへの書き込みを新テーブルへ同期する必要があります。 したがって、既存の旧テーブルへの書き込み箇所を特定し、新テーブルへも書き込むように修正します。 そして、既に書き込まれなくなった過去のレコードに関してはタスクなどで新テーブルへ同期する必要があります。
テーブルのリファクタリングが必要になっている頃にはいくつもの書き込み経路が生まれているので、最初から漏れなく書き込み箇所の特定は困難です。 したがって、後述するように同期の完全性を検証する仕組みを考える必要があります。
また、同期する際に新テーブルへの書き込みが失敗した場合のエラーハンドリングの方針も決める必要があります。 主に以下の二つの方針があります。
- 同一トランザクション内で両方に書き込む
- 新テーブルへの書き込みの失敗を許容する
1.同一トランザクション内で両方に書き込む
同一トランザクション内で新旧両方のテーブルに書き込むことで、両方に書き込まれることを担保できます。(トランザクションの原子性) 同一データベースへの書き込みで単純なレコードのINSERT, UPDATEだけであり、新テーブルへの書き込みが失敗する可能性が少ない場合は有効な手段になります。
2.新テーブルへの書き込みの失敗を許容する
一方で、別のデータベースの新テーブルに同期する場合はトランザクションを利用できません。
その場合、旧テーブルに書き込みが成功した後に新テーブルに書き込むようにします。その場合以下の場合が考えられます。
- 両方に書き込み成功: 問題なし
- 旧テーブルにのみ書き込み成功: 成功時の結果だけ返して後から同期する
- 旧テーブルに書き込み失敗: エラーにして新テーブルには同期しない
新テーブルへの書き込みに失敗した場合は、旧テーブルと不整合が起こった状態になります。一時的な失敗の場合もあるので、エラーコードに基づいたリトライを行います。それでも失敗した場合はエラーを通知し、既存の実装に影響がないように旧テーブルへの書き込み結果だけを返します。そして、タスクやバッチ処理により後から不整合を解消する手段を講じます。
同期の完全性の検証
旧テーブルが広範に利用されている場合は、初めから書き込み元を漏れなく特定するのが困難です。したがって、旧テーブルと新テーブルの同期漏れがないか検証する仕組みが必要になります。
手段として以下の二つが考えられます。
- バッチ処理で定期的に新旧テーブルの差分を比較する
- 旧テーブル読み込み時に新テーブルと差分を比較する
バッチ処理の場合は旧テーブルの前回の実行時以降の最終更新日のレコードを取り出し、対応するレコードが新テーブルに同期されているか確認します。 これによって、旧テーブルがもれなく同期されているか検証することができます。しかし、旧テーブルのサイズによってはパフォーマンス上の問題がある場合があります。
また、バッチ処理で差分があったレコードを更新する場合は注意が必要です。なぜなら、前述のようにアプリケーション上でも差分が比較され新テーブルへ書き込まれているので、トランザクションの同時実行制御が必要になるからです。バッチ処理で旧テーブルのレコードを読み取った後にアプリケーション上で同レコードが更新され、新テーブルへ同期されている場合はロストアップデートが起こりえます。
一方で、旧テーブルのレコードが読み込まれた際に差分を比較する方法は、GitHub’s Scientistなどの専用のライブラリを使って実現することができます。 柔軟にモデルごとの比較条件を決めたり、差分があった場合の通知などの後処理も指定することができます。しかし、読み取り時に必ず利用されるオブジェクト(RailsのActive Recordなど)に差分比較の実装をしなければ比較漏れが生じます。また、検証期間中に読み込まれないレコードに関しては検証がされないことになります。
新テーブルからの読み込み・書き込み
一定期間の検証を終えたら、読み込み先・書き込み先を新テーブルに行います。 記事によっては書き込み先を新テーブルに切り替えて、旧テーブルにも逆向きに同期する手順も紹介されていました。 また、まずは読み込み専用のAPIから新テーブルに切り替えていき、後から書き込み先も変える手順もありました。
デプロイ手順
段階的にデータ移行を行う場合は段階的なリリースも必要になります。 例えば以下のような手順になり、リリーススケジュールの調整が必要になります。
- 新旧テーブルの同期の実装やデータ移行タスク
- 差分比較のバッチ処理
- 読み込み先・書き込み先の切り替え