取り消せるスキーマ変更
一言でいうと
可逆性はコードの属性ではなく、コードとデータと時間の組み合わせです。同じ変更でも、誰も使っていなければ元に戻せますが、100万件が溜まったあとには元に戻せません。
なぜこれが問題なのか
「downスクリプトがあるから安全だ」という言葉が危険なのは、ここに理由があります。DROP COLUMNを元に戻すdownは、カラムの構造を復活させるだけで、その中にあった値は復活させられません。スキーマは元に戻っても、データは戻ってきません。
そのため、ロールバック計画を立てるときに問うべきなのは、「元に戻すスクリプトがあるか」ではなく、「元に戻したあとに失うものは何か」です。この問いをデプロイ前に立てると、たいていは拡張→移行→縮小に分けて進める方を選ぶことになります。
可逆性はコードの属性ではありません
「この変更はロールバック可能か」という質問に対する正確な答えは、次のとおりです。
可逆性は、コードとデータと時間の組み合わせです。 同じコード変更でも、まだ誰も使っていなければ可逆で、 100万件が溜まったあとは不可逆です。
カラムを1つ追加しました。元に戻せるでしょうか。昨日デプロイして、誰も使っていなければ、戻せます。1週間のあいだに100万件がそのカラムに値を入れたなら、DROPはその100万件を捨てることになります。スキーマは元に戻りますが、データは戻ってきません。
「downスクリプトがあるから安全だ」という思い込みは、ここから生まれます。DROP COLUMNを元に戻すdownは、カラムの構造を復活させるだけで、値は復活させられません。そのため、こんな言葉があります。ダウンマイグレーションはたいてい嘘です。
危険なDDLの一覧
| DDL | なぜ危険か |
|---|---|
NOT NULLの追加 |
全行を検査するため、長時間ロックされます。既存のNULLがあると失敗します |
| カラム名の変更 | アトミックに見えますが、実際には削除+追加です。旧バージョンのコードがすぐに壊れます |
| 型の変更 | DBMSによってはテーブルが再作成されます。大容量だと非常に長くかかります |
| インデックスの作成 | 大容量のテーブルでロックまたは負荷が生じます。オンラインオプションの確認が必要です |
| 大量バックフィルのUPDATE | 巨大なトランザクションにより、ロックとレプリケーションラグが発生します。リードレプリカが崩壊します |
DROP COLUMN |
データが消滅します。元に戻せません |
特に大量バックフィルには注意が必要です。500万行を1つのトランザクションでUPDATEすると、トランザクションログが急増してレプリケーションラグが発生します。照会トラフィックがレプリカに向かう構成なら、この瞬間に照会サービスが古いデータを表示します。そのため、バックフィルは必ずバッチに分割します。
-- 나쁜 예
UPDATE ORDERS SET DLVR_STS = '01' WHERE DLVR_STS IS NULL;
-- 좋은 예: 1000건씩, 사이에 잠깐 쉬면서
UPDATE ORDERS SET DLVR_STS = '01'
WHERE ORD_NO IN (SELECT ORD_NO FROM ORDERS WHERE DLVR_STS IS NULL LIMIT 1000);
-- 영향 행이 0이 될 때까지 반복
拡張→移行→縮小(Expand / Migrate / Contract)
可逆なスキーマ変更の標準パターンです。 核心のルールは、1回のデプロイに2つの段階を一緒に入れないことです。
[1차 배포 — 확장]
새 컬럼 추가 (NULL 허용, 기본값 있음)
애플리케이션: 새 컬럼과 옛 컬럼에 모두 쓰고, 읽기는 옛 컬럼
→ 이 시점에서 롤백하면? 새 컬럼을 아무도 안 읽으니 안전
[2차 — 이관]
기존 데이터 백필 (배치로 쪼개서)
검증: 두 컬럼 값이 일치하는가
[3차 배포 — 전환]
애플리케이션: 읽기를 새 컬럼으로
→ 롤백하면 옛 컬럼을 읽는데, 계속 써 왔으니 값이 있다. 안전
[4차 배포 — 축소]
애플리케이션: 옛 컬럼 쓰기 중단
충분한 관찰 기간 후 옛 컬럼 DROP
→ 이 시점에서야 비가역이 된다
このコードブロックの韓国語は、4回に分けたデプロイ(拡張・移行・切り替え・縮小)のそれぞれの時点でロールバックしても安全であることと、最後の縮小まで進んで初めて不可逆になることを説明しています。
遅く見えますが、各段階で元に戻せることがこのパターンのすべてです。カラム名を一度に変えるのは30秒で済みますが、失敗するとサービスが止まります。このパターンは2週間かかりますが、どの時点でも止まりません。
変更管理台帳
SIプロジェクトでは、DDLは開発者が勝手に実行するものではありません。変更管理台帳を置き、本番反映は承認を経ます。
| 項目 | なぜ必要か |
|---|---|
| 変更ID / 日付 | 追跡の単位 |
| 対象オブジェクト | 影響範囲を把握する出発点 |
| DDLスクリプトファイル | 実際に実行するもの |
| ロールバックスクリプトファイル | なければ承認されません |
| 想定所要時間 | サービス停止時間の見積もり |
| 影響システム | 連携相手に通知すべき対象 |
| 承認者 | 責任 |
ロールバックスクリプトを要求することがこの台帳の核心的な価値です。ロールバックを書いているうちに、「これは元に戻せないな」とデプロイ前に気づきます。その気づきが、設計を拡張→移行→縮小へと変えさせます。
スキーマ変更とアプリケーションのデプロイの順序
これもミスが多いところです。
- カラムの追加: DBが先、アプリケーションはあと。(アプリが存在しないカラムを使うと困るため)
- カラムの削除: アプリケーションが先、DBはあと。(アプリが使わないようにしたあとで削除します)
一文でいうと、加えるものはDBが先、引くものはアプリが先です。
そして、ローリングデプロイ中は旧バージョンと新バージョンが同時に動きます。そのため、スキーマは常に両方のバージョンが動作する状態でなければなりません。これが、拡張→移行→縮小が必要な根本的な理由です。
ブルーグリーンはスキーマを解決しません
無停止デプロイの戦略を語るときに、必ず押さえるべき点があります。
コンピュートは複製しやすいですが、データベースはたいてい共有します。 そのため、ブルーグリーンはアプリケーションのロールバックを秒単位にしてくれますが、 スキーマの問題はまったく解決しません。
ブルーとグリーンが同じDBを参照するなら、スキーマは両方のアプリケーションバージョンと互換でなければなりません。結局、同じ話に戻ってきます。
マイグレーションの検証
適用したら、確認する必要があります。4つの段階で見ます。
1. 스키마 컬럼·타입·제약·인덱스가 의도대로인가
2. 데이터 행 수 · 합계 · NULL 개수 · 체크섬이 보존됐는가
3. 성능 주요 쿼리의 실행계획과 응답시간이 나빠지지 않았는가
4. 앱 핵심 기능 스모크 테스트
このコードブロックの韓国語は、検証の4項目を示しています。スキーマ(カラム・型・制約・インデックスが意図どおりか)、データ(行数・合計・NULLの個数・チェックサムが保たれているか)、性能(主要クエリの実行計画と応答時間が悪化していないか)、アプリ(主要機能のスモークテスト)です。
項目2のチェックサムが特に有用です。行数と合計だけでは、「1件が抜けて別の1件が2倍になった」状況を検出できません。
-- 이관 전 기준선을 만들어 둔다
CREATE TABLE MIG_BASELINE AS
SELECT COUNT(*) AS CNT, SUM(ORD_AMT) AS AMT,
COUNT(DISTINCT CUST_ID) AS CUSTS FROM ORDERS;
-- 이관 후 같은 쿼리로 비교
作業前にベースラインを作っておくことがコツです。作業後に「もともと何件でしたか」と聞くことになったら、もう遅いです。
現場での姿
スキーマ変更が事故に発展する経路は、たいてい2つです。
1つ目はロックです。NOT NULLを追加すると、全行を検査するためテーブルがロックされ、その間そのテーブルを使うすべてのリクエストが待たされます。開発DBで0.2秒だったものが、1000万件の本番テーブルでは数分になり、その数分はサービス停止と区別がつきません。
2つ目は順序です。アプリケーションを先にデプロイすると、まだ存在しないカラムを読んで落ち、スキーマを先に変えると、古いアプリケーションが新しい制約に違反します。だから「両方を同時にデプロイ」は計画ではありません。どちらが先でも耐えられるように作っておくことが計画です。
ブルーグリーンでこれを解決しようとする試みもよく見ますが、2つの色が同じDBを参照しているという事実は変わりません。アプリケーションは無停止で切り替わっても、スキーマは1つのままです。