既存データを失わないデータ移行の考え方

中級 | 13分 で読める | 2026.06.27

公式ドキュメント

データ移行はコード変更より戻しにくい

アプリケーションコードは、デプロイを戻せば元に戻せることが多いです。しかし、DBのデータを消したり、上書きしたり、型を変えたりした後は簡単に戻せません。

データ移行では、既存データを失わず、新旧アプリが共存でき、途中失敗しても再開・検証できることが重要です。

特に本番DBでは、移行SQLを一発で流す発想を避けます。

危険な変更

注意が必要な変更です。

変更危険
カラム削除既存アプリやバッチが読めなくなる
カラム名変更新旧アプリが共存できない
型変更変換失敗やロックが起きる
NOT NULL追加既存NULLで失敗、ロックが起きる
UNIQUE追加重複データで失敗
大量UPDATEロック、WAL増加、レプリカ遅延
テーブル分割移行漏れや整合性崩れ

危険な変更ほど、段階を分けます。

expand / contract

安全なDB変更では、expand / contract という考え方を使います。

expand: 互換性を保ったまま新しい構造を追加する
contract: 移行完了後に古い構造を削除する

例: namefirst_namelast_name に分ける

Step 1: 新カラム追加

ALTER TABLE users ADD COLUMN first_name TEXT;
ALTER TABLE users ADD COLUMN last_name TEXT;

Step 2: アプリを新旧両方に対応

旧nameも読む
新first_name/last_nameも書く

Step 3: バックフィル

UPDATE users
SET first_name = split_part(name, ' ', 1),
    last_name = split_part(name, ' ', 2)
WHERE first_name IS NULL;

Step 4: 新アプリへ切り替え

Step 5: 旧カラム削除

ALTER TABLE users DROP COLUMN name;

削除は最後です。

バックフィルは小さく刻む

大量データを一括UPDATEすると、ロックや負荷が大きくなります。

避けたい例:

UPDATE orders
SET status = 'paid'
WHERE paid_at IS NOT NULL;

安全寄りの考え方:

主キー順に1000件ずつ処理
処理済みIDを記録
途中失敗しても再開できる
短いトランザクションで実行

例:

UPDATE orders
SET status = 'paid'
WHERE id > $1
  AND id <= $2
  AND paid_at IS NOT NULL
  AND status IS NULL;

条件に status IS NULL を入れると、再実行しても同じ行を壊しにくくなります。移行処理は冪等に近づけます。

二重書き込み

新旧カラムや新旧テーブルが共存する期間は、二重書き込みが必要になることがあります。

新規作成時:
  old_column に書く
  new_column にも書く

更新時:
  old_column を更新
  new_column も更新

これにより、移行期間中に新しく入ったデータも取りこぼしにくくなります。

注意点:

  • どちらを正とするか決める
  • 書き込み失敗時の扱いを決める
  • バックフィル後の差分を検証する
  • 二重書き込み期間を長くしすぎない

DBトリガーで同期する方法もありますが、アプリ側から見えにくくなるため、運用とチームの理解が必要です。

検証SQLを先に用意する

移行は、実行SQLだけでなく検証SQLが重要です。

例: 件数確認

SELECT COUNT(*) FROM users WHERE name IS NOT NULL;
SELECT COUNT(*) FROM users WHERE first_name IS NOT NULL;

例: 移行漏れ

SELECT id
FROM users
WHERE name IS NOT NULL
  AND first_name IS NULL
LIMIT 100;

例: 重複確認

SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

検証できない移行は危険です。どの状態になったら成功と言えるのかを、移行前に決めます。

ロールバックではなくロールフォワードも考える

DB変更では、単純なロールバックが難しいことがあります。

例:

  • カラム削除後にデータが消えた
  • 型変換で元の文字列が失われた
  • 新旧で書き込みが分岐した

そのため、戻すだけでなく、前に進めて直すロールフォワードも考えます。

問題発生
  -> 旧カラムが残っているなら旧アプリへ戻す
  -> 移行漏れなら追加バックフィル
  -> 不整合なら補正SQL

削除や破壊的変更を最後にするのは、ロールバック余地を残すためです。

本番前に見ること

実行前チェック:

  • バックアップがある
  • リストア手順を確認した
  • ステージングで本番相当データを試した
  • 実行時間を測った
  • ロックの影響を見た
  • レプリカ遅延を想定した
  • アプリの新旧バージョン共存を確認した
  • 検証SQLを用意した
  • 中断と再開手順を用意した
  • 監視項目を決めた

「SQLは正しい」だけでは足りません。本番で安全に流せるかを確認します。

まとめ

データ移行は、既存データを守りながら構造を変える作業です。

  • 削除や破壊的変更を急がない
  • expand / contractで段階的に進める
  • バックフィルは小さく刻む
  • 二重書き込み期間を設計する
  • 検証SQLを先に用意する
  • ロールバックとロールフォワードを考える
  • バックアップと監視を用意する

DB移行がうまいエンジニアは、変更そのものより「失敗してもデータを失わない道筋」を先に作ります。

参考リソース

次に読む記事

← 一覧に戻る
PR
PR
PR
PR