本番DBへのサービス無停止スキーマ変更:3年分の決済レコードを抱えたままETC対応した実例
概要¶
バクラクビジネスカードのクレカ専用 DB を ETC 対応に拡張した際、約3年分の決済レコードを抱える本番 DB に対してサービス無停止でスキーマ変更した実例。
詳細¶
なぜ本番 DB のスキーマ変更は難しいか¶
ゼロダウンタイム スキーマ変更のパターン¶
Expand-Contract パターン(推奨)¶
Phase 1: Expand(拡張)
→ 新しいカラムを nullable で追加
→ 既存のコードは古いカラムのまま動作(後方互換)
Phase 2: Migrate(移行)
→ バックグラウンドで既存行の新カラムを埋める
→ バッチで少しずつ(全行を一括更新しない)
Phase 3: Switch(切り替え)
→ アプリコードを新カラムに切り替える
→ 古いカラムへの書き込みを止める
Phase 4: Contract(縮退)
→ 古いカラムを削除
→ (安全を確認してから、別デプロイで実施)
バッチ更新で大テーブルを安全に更新する¶
-- 悪い例: 全行を一括更新 → テーブルロック
UPDATE payments SET payment_type = 'credit_card' WHERE payment_type IS NULL;
-- 良い例: 少しずつ更新(Lock を長時間保持しない)
DO $$
DECLARE
batch_size INT := 1000;
offset_val INT := 0;
rows_updated INT;
BEGIN
LOOP
UPDATE payments SET payment_type = 'credit_card'
WHERE id IN (
SELECT id FROM payments WHERE payment_type IS NULL
ORDER BY id LIMIT batch_size
);
GET DIAGNOSTICS rows_updated = ROW_COUNT;
EXIT WHEN rows_updated = 0;
PERFORM pg_sleep(0.1); -- 少し待つ
END LOOP;
END $$;
インデックスの安全な追加¶
-- 悪い例: テーブルをロック
CREATE INDEX idx_payments_user_id ON payments(user_id);
-- 良い例: 並行ビルド(ロックなし、時間はかかる)
CREATE INDEX CONCURRENTLY idx_payments_user_id ON payments(user_id);
なぜ重要か / いつ使うか¶
- 本番サービスを止めずに DB スキーマを変更する必要があるとき
- 数百万〜数億行のテーブルを安全に変更するとき
- マイグレーションの設計をレビューするとき
- 面接で「大規模 DB のマイグレーション」を聞かれたとき