なぜデータベースデプロイはアプリケーションデプロイより難しいのか
あなたは一週間中アプリケーションコードをデプロイしてきた。新しい機能を入れ、古いプロセスを止め、新しいプロセスを起動する。何か壊れたら、以前のバージョンに戻せばいい。このサイクルは数分で完了する。コードは置き換え可能だから、安全に感じられる。
そこにデータベースマイグレーションがやってくる。新しいカラムを追加する必要がある。テーブル名を変更する必要がある。マイグレーションスクリプトを実行すると、突然本番データベースの構造が変わる。アプリケーションは最初は問題なく動くが、1時間後に誰かがレポートでデータが欠落していると報告する。マイグレーションをロールバックしようとするが、マイグレーション中に変換されたデータはきれいに元に戻らない。一部の行は新しい構造のままだ。古い値は消えている。
この瞬間、多くのチームはデータベースデプロイが単なる「別の種類のもののデプロイ」ではないことに気づく。それは根本的に異なる問題なのだ。
コードは使い捨て可能、データはそうではない
アプリケーション構築を学び始めた頃、アプリケーションはコードの集まりだと考えていただろう。コードを書き、実行し、結果を確認し、何か問題があればコードを変更する。新しいバージョンが動かなければ、以前のバージョンに戻す。このプロセスは軽量に感じられる。長期的な結果が残らないからだ。間違ったコードは捨てたり、置き換えたり、ゼロから書き直したりできる。
しかし、アプリケーションが他の人々に使われ始めると、それとともに別のものが成長する。データだ。ユーザーデータ、トランザクションデータ、注文データ、履歴レコード。このデータは単なるデータベースの中身ではない。データはビジネスで既に起こったことの記録である。テーブルの各行には価値ある何かが保存されている。データが失われても、コードを書き直せば戻ってくるわけではない。失われたデータは再生成できない。
これがアプリケーションコードとデータベースの根本的な違いだ。アプリケーションコードは置き換え可能なアーティファクトとして扱える。プロジェクトフォルダを削除し、リポジトリから再クローンすれば、アプリケーションは以前と全く同じように動作する。しかしデータベースはそうではない。データベースは状態を保存する。状態とはビジネスデータの現在の状態であり、既に行われた操作の蓄積から形成される。データベースを削除すれば、状態は消える。1年間かけて収集した顧客データを復元できるGitクローンは存在しない。
デプロイへの影響
この違いの影響は、デプロイ時にすぐに現れる。アプリケーションの新しいバージョンをデプロイするときは、最新コードをサーバーに送り、古いプロセスを停止し、新しいプロセスを起動する。新しいバージョンに問題があれば、以前のバージョンに戻すことでロールバックできる。アプリケーションは更新前と同じように動作する。
しかし、データベースデプロイがテーブル構造を変更すると、既存のデータに影響が出る。新しいカラムにはデフォルト値が入るかもしれない。データ型が変わるかもしれない。古いデータを変換する必要があるかもしれない。これらの変更が問題を引き起こした場合、ロールバックはコードのバージョンを入れ替えるほど単純ではない。既に変更されたデータは自動的に元の形に戻らない。
カラムを追加してデータを投入する単純なマイグレーションを考えてみよう:
-- フォワードマイグレーション
ALTER TABLE users ADD COLUMN age INT;
UPDATE users SET age = 25 WHERE age IS NULL;
-- ロールバックスクリプト
ALTER TABLE users DROP COLUMN age;
フォワードマイグレーションが実行され、アプリケーションが新しいageの値を書き込み始めた場合、ロールバックでカラムを削除するとそれらの新しい値は失われる。アプリケーションコードとは異なり、古いバージョンを再デプロイするだけで失われたデータを回復することはできない。
このため、多くのチームはデータベースデプロイに慎重になる。アプリケーションのバージョンは1日に何度も素早く入れ替えられるが、デプロイのたびにデータベースマイグレーションを実行することには躊躇する。マイグレーションが技術的に難しいからではなく、結果が異なるからだ。間違ったコードは書き直せば修正できる。間違ったデータは修正に数日かかることもあり、時には以前と全く同じ状態に復元できないこともある。
これがデプロイプロセスをどう変えるか
コードとデータの違いは、デプロイプロセスの設計に影響を与える。アプリケーションの場合、主に新しいコードが実行できることを確認すればよい。データベースの場合、構造変更が既存のデータを壊さず、実行中のアプリケーションを中断させず、何か問題が起きた場合に元に戻せることを保証しなければならない。
実践的な影響は以下の通りだ:
アプリケーションデプロイは追加的である。 新しいコードを追加し、古いコードを削除すれば、システムは継続する。古いバージョンはリポジトリやアーティファクトストレージに残っている。
データベースデプロイは変革的である。 既存のデータの構造を変更する。マイグレーションが実行されると、古い構造は消える。バックアップがあっても、それを復元するとバックアップ後に作成されたデータは失われる。
アプリケーションのロールバックは安価である。 ロードバランサーを古いバージョンに向けるか、古いプロセスを再起動すればよい。アプリケーションの状態は実行中のコードによって決まる。
データベースのロールバックは高コストである。 データを以前の構造に戻す逆マイグレーションを書く必要がある。この逆マイグレーションはテストしなければならない。古い構造に適合しない新しいデータが追加されていると失敗する可能性がある。また、フォワードマイグレーションがデータを不可逆的に変換した場合(例えば、名と姓を一つのカラムに連結するなど)、逆マイグレーションで情報が失われる可能性がある。
本当のリスクは技術面ではない
多くのチームはデータベースマイグレーションを純粋に技術的な問題として扱う。マイグレーションスクリプトを書き、ステージングでテストし、本番で実行する。何か問題が起きると、マイグレーションスクリプトやデータベースツールのせいにする。
しかし、本当のリスクはしばしば組織的なものだ。データベースマイグレーションは実際のユーザーに属するデータに触れる。ミスは顧客レコード、財務データ、コンプライアンスログを破壊する可能性がある。マイグレーションを実行するチームは、そのデータが他のシステムでどのように使われているかを完全に理解していないかもしれない。カラム名の変更は月に一度実行されるレポートクエリを壊すかもしれない。データ型の変更はレガシーサービスを静かに失敗させるかもしれない。
このため、データベースデプロイには異なるレベルの注意が必要となる。技術的な複雑さは管理可能だ。組織的な複雑さがチームの速度を落とさせるのだ。
データベースデプロイの実践的チェックリスト
本番でデータベースマイグレーションを実行する前に、以下のチェックを検討すること:
- マイグレーションは元に戻せるか?フォワードマイグレーションを実行する前にロールバックスクリプトを書き、テストすること。
- マイグレーションは現在のアプリケーションを壊すか?アプリケーションがリネームするカラムから読み取っている場合、アプリケーションも更新されるまで失敗する。
- アプリケーションが稼働中でもマイグレーションを実行できるか?一部のマイグレーションはテーブルをロックし、ダウンタイムを引き起こす可能性がある。
- バックアップはあるか?データを変換するマイグレーションを実行する前に、必ずバックアップを取ること。
- 誰に知らせる必要があるか?レポート、分析、データサイエンスなど、このデータを利用するチームに通知すること。
まとめ
アプリケーションコードは使い捨て可能だ。捨てたり、書き直したり、新しいバージョンをデプロイしても、重要なものを失うことはない。データベースのデータは使い捨てできない。すべてのマイグレーションは、簡単には再作成できない何かを変更する。
データベースデプロイは、本番インシデントと同じ厳格さで扱うこと。ロールバックスクリプトを書くこと。現実的なデータに対してマイグレーションをテストすること。データに依存するチームとコミュニケーションを取ること。ステージングで通ったからといって、マイグレーションが安全だと決して思い込まないこと。
コードとデータの違いは技術的な詳細ではない。データベースデプロイが独自のプロセス、独自のテスト戦略、独自のリスク評価を必要とする理由そのものなのだ。