なぜデータベースに専用のCI/CDパイプラインが必要なのか
あなたは、実際のユーザーが毎日使うアプリケーションを開発しているとします。新しい機能をリリースするときは、コードを変更してパイプラインにプッシュし、新しいバージョンを本番環境にデプロイします。何か問題が起きれば、以前のバージョンにロールバックして、数分で通常状態に戻せます。このプロセスがスムーズに進むのは、アプリケーションのコード自体に重要なデータが含まれていないからです。
では、ユーザーテーブルにカラムを1つ追加する必要が出てきたと想像してください。ローカルマシンなら ALTER TABLE user ADD COLUMN ... を実行して終わりです。しかし本番環境では、そのテーブルには数百万行の実際のユーザーデータが格納されています。アプリケーションは毎秒読み書きを行っています。新しいカラムがインデックス構造を変えると、一部のクエリが壊れるかもしれません。ALTER操作に時間がかかりすぎると、データベース接続がタイムアウトする可能性もあります。
これこそが、データベースの変更をアプリケーションの変更と根本的に異なるものにしている本質的な違いです。そして、データベースマイグレーションを通常のコードデプロイと同じように扱うと、いつか必ず深刻な問題を引き起こす理由でもあります。
ステートレス vs ステートフル:核心的な違い
アプリケーションはステートレスです。インスタンスを停止し、新しいものに置き換え、古いバージョンにロールバックしても、何も失われません。コードは以前誰が使ったかを気にしません。各デプロイはクリーンなスワップです。
データベースはステートフルです。データは完全で、一貫性があり、アクティブなユーザーがアクセスできる状態を保たなければなりません。スキーマはアプリケーションとデータの間の契約です。その契約が突然、または誤って変更されると、データが破損したり、アプリケーションがエラーを吐き出したり、ユーザーがアクセスできなくなったりする可能性があります。
この違いが、変更の扱い方をすべて変えます。典型的なアプリケーションパイプラインは、コードがビルドできるか、テストが通るかを確認し、デプロイします。データベースパイプラインは、より難しい問いに答える必要があります。
- このスキーマ変更は、現在実行中のアプリケーションバージョンと互換性があるか?
- この操作はテーブルを長時間ロックするか?
- 変更後も既存のデータは読み取り可能か?
- 途中で失敗した場合、データを失わずに安全な状態に戻すにはどうするか?
タイミングの問題
アプリケーションパイプラインはいつでも、1日に複数回でも実行できます。コードをプッシュし、パイプラインが実行され、新しいバージョンが稼働します。プロセス全体は数分で完了します。
データベースパイプラインは、特定の時間に実行する必要があることがよくあります。トラフィックが少ない時間帯、長時間実行中のトランザクションがないことを確認した後、本番ワークロードを理解している人の手動承認があった後などです。
アプリケーションパイプラインが失敗したら、以前のバージョンを再デプロイするだけです。単純です。データベースパイプラインがマイグレーションの途中で失敗すると、部分的なマイグレーションが残ります。変更の半分は適用され、半分はされていない状態です。これは最も扱いにくい問題の一つであり、パイプラインを実行する前に慎重な計画が必要です。
互換性は必須条件
アプリケーションの新しいバージョンをデプロイするとき、それは特定のデータベーススキーマを期待します。マイグレーションを実行すると、そのスキーマが変更されます。この2つが一致していなければ、問題が発生します。
難しいのは、デプロイ中はアプリケーションの新旧両方のバージョンが同時に実行されている可能性があることです。ブルーグリーンデプロイ、カナリアリリース、ローリングアップデートはすべて、複数のアプリケーションバージョンがアクティブな期間を生み出します。データベーススキーマは、それらすべてと同時に互換性がなければなりません。
つまり、カラムを追加してすぐに同じデプロイで使い始めることはできません。マルチステップのアプローチが必要です。まずカラムを使用せずに追加し、アプリケーションをデプロイし、その後で後のデプロイでカラムを使い始めます。この後方互換性のあるマイグレーションパターンは、ゼロダウンタイムデプロイに不可欠であり、アプリケーションパイプラインとデータベースパイプラインの間の調整が必要です。
なぜパイプラインを分離するのか
アプリケーションパイプラインの中にデータベースマイグレーションをステップとして含めたくなるかもしれません。結局、どちらも機能を提供する一部です。しかし、それらを混在させると、いくつかの問題が発生します。
次のフローチャートは、2つのパイプラインが複雑さと安全チェックの点でどのように分岐するかを示しています。
アプリケーションパイプラインはコード変更のたびに実行されます。データベースマイグレーションは、スキーマが実際に変更されたときだけ実行すべきです。不要なマイグレーションを実行するとリスクが増え、デプロイが遅くなります。
アプリケーションのロールバックは簡単です。データベースのロールバックはそうではありません。パイプラインが両方をバンドルしている場合、アプリケーションをロールバックすると、データ損失を引き起こすデータベース変更もロールバックしてしまう可能性があります。
アプリケーションパイプラインは高速です。データベースマイグレーションは、特に大規模なテーブルでは低速になる可能性があります。低速なマイグレーションはデプロイパイプライン全体をブロックし、データベースにまったく触れない他の変更を保留にする可能性があります。
アプリケーションパイプラインはステートレスな環境を前提としています。データベースパイプラインは、スキーマの現在の状態、データ量、本番ワークロードを理解する必要があります。これらは根本的に異なる関心事です。
優れたデータベースパイプラインの姿
適切に設計されたデータベースパイプラインは複雑である必要はありません。ステートフルシステムの性質を尊重するだけで十分です。以下を実行する必要があります。
すべてのマイグレーションを、レビュー、テスト、一貫した実行が可能な反復可能なスクリプトとして実行します。各マイグレーションは、明確なバージョン番号を持つ単一のファイルで、前方ステップと後方ステップの両方を含む必要があります。
本番に近いデータベースに対してマイグレーションをテストします。スキーマだけでなく、データ量と構造も同様です。空のテストデータベースでは1秒で実行されるマイグレーションが、本番では20分かかる可能性があります。
制御された順序で、一度に1つずつマイグレーションを実行します。複数のスキーマ変更を1つの操作にまとめてはいけません。各マイグレーションは小さく、焦点が絞られ、元に戻せるものであるべきです。
各マイグレーションの後に結果を検証します。スキーマが期待通りであること、インデックスが適切に設定されていること、既存のデータが無傷であることを確認します。
問題が発生した場合の明確な前進または後退のパスを提供します。つまり、テスト済みのロールバックスクリプトを持ち、それにかかる時間を把握し、どのデータが影響を受ける可能性があるかを理解していることを意味します。
データベースパイプラインの実用的なチェックリスト
データベースパイプラインを設定する前に、次の質問に答えられることを確認してください。
- 各マイグレーションは独立して実行できますか、それとも他のマイグレーションが先に実行されることに依存していますか?
- 各マイグレーションは元に戻せますか?ロールバックをテストしましたか?
- マイグレーションは現在のアプリケーションバージョンと次のバージョンの両方で動作しますか?
- 本番のデータ量でマイグレーションにはどのくらい時間がかかりますか?
- マイグレーションはテーブルをロックしますか?ロックする場合、どのくらいの時間ですか?
- マイグレーションが途中で失敗したらどうなりますか?
- 本番でこのマイグレーションを実行するには誰の承認が必要ですか?
- このマイグレーションは1日のうち何時に実行すべきですか?
- マイグレーションが成功したことをどのように確認しますか?
まとめ
データベースの変更はコードの変更ではありません。それらはライブデータに対して動作し、アクティブなユーザーに影響を与え、実際のリスクを伴います。それらをアプリケーションコードのように扱うと、いつかダウンタイムやデータ問題、あるいはその両方を引き起こします。データベース変更のための独立したパイプラインは、必要な制御、安全性、予測可能性を提供します。複雑である必要はありません。管理対象である、人々が依存するステートフルで長期間存続するデータのために設計されていればよいのです。