こんにちは、 Omni Hub チームの shunten31 です. すっかり寒くなりましたね.
今回は、 最近取り組んできたデータベースの Blue/Green デプロイについて書かせていただきます.
はじめに
データベースの Blue/Green デプロイは、ダウンタイムを最小化しながらデータベースのスキーマ更新等を行える効果的な手法です. しかし、Green 環境へのスイッチオーバーを行う前に、事前に Green 環境に対してスキーマ変更やデータ操作を行う必要があります. これらの操作は基本的に Green 環境に直接接続して、SQL コマンドを打ち込むことで実施します.
一方で、 アプリケーションが ORM を使用している場合、 スキーマ変更はマイグレーションとして記録し、 一貫性を保つ必要があります. しかし、手動で SQL コマンドを実行する方法では、 マイグレーションの記録とデータベースの状態が不整合となり得ることが課題となります.
TL;DR
この課題を解決するため、以下の手法によって、ORM のマイグレーションコマンドを直接 Green 環境に適用できるようにしました.
- Green インスタンスに対して ORM ツールのコマンドを使用してマイグレーションを実施する GitHub Actions ワークフローを作成.
- AWS Systems Manager を活用してリモートホストへのポートフォワーディングを行い、DB と同じプライベートサブネット内の EC2 インスタンスを経由して接続
- この際、 Green インスタンスの default_transaction_read_only 設定を解除するため、PG_OPTIONS 環境変数を使用
他の解決方法として、 空のマイグレーションファイルを作成の上、マイグレーションを実施することで履歴をデータベースに記録し、 後からマイグレーションファイルを改ざんする という方法があるようです. *1 *2 しかし、 オペレーションの手間が少なく、マイグレーションの実施履歴の不変性を保てることから、今回実施した方法を採用しました.
背景
Omni Hub では、 Rust でアプリケーション開発をしており、 データベースへのアクセスや マイグレーションの管理には、 Rust の ORM である Diesel を利用しています. Diesel では、 マイグレーションをSQL で記載して管理し、コマンド (diesel migration run
) で マイグレーション を実行することができます.
Omni Hub では、データベースに関連して、以下の状況にありました.
- 事業者様や、 その顧客規模が拡大していくにつれ、データベースのレコード数が増えており、クエリのパフォーマンスが低下していた.
- 新機能開発に伴い、テーブルの作成だけではなく、既存テーブルの分割等のスキーマ修正が必要になっていた.
このため、 いくつかマイグレーションを作成して、テーブルの分割などのスキーマ変更、 インデックスの追加等を実施していました.
しかし、 稼働中の本番データベースに対して直接これらのマイグレーションを適用すると、 データベースの負荷が増大し、 本番アプリケーションのクエリ性能が悪化する問題が発生してしまいました. これを防ぐため、 Aurora PostgreSQL の Blue/Green デプロイを導入することにしました.
Blue/Green デプロイを実施する上での課題
Blue/Green デプロイでは、 Green 環境に対して、 新たにリリースしたいテーブルのスキーマ変更やデータ操作等を適用してから Green 環境へのスイッチオーバーを実施が必要です.
現行のデプロイでは、 アプリケーションサーバーの起動時に、ORM によるマイグレーションが一緒に実施される方法となっていました. この方法では以下の問題がありました.
- マイグレーションを単体で実行できない
- マイグレーションを Green 環境に対して実行できない
マイグレーションをアプリケーションのデプロイかから分離し、対象のデータベースも自由に選べるようにするため、データベースに接続して、マイグレーションのみを実施する GitHub Actions ワークフロー を作成しました.
Green 環境に対してマイグレーションを実施する GitHub Actions ワークフロー
作成したGitHub Actions ワークフローは、主に以下の手法を使っています.
VPC 内の データベースへ、ポートフォワーディングを行う
我々の環境では、Aurora PostgreSQL のインスタンスは、 VPC 内のプライベートサブネットに配置されています. そのため、同じ VPC 内の EC2 インスタンスを経由してポートフォワーディングを行います. AWS Systems Manager が提供する リモートホストへのポートフォワード (AWS-StartPortForwardingSessionToRemoteHost
)を使用します. (公式ドキュメント)
下記の actions のソースコード例では、 ローカルホストの 15432 ポートを DB の 5432 ポートに対してフォーワーディングしています.
Green 環境の default_transaction_read_only を off に設定する
Aurora PostgreSQL で作成される Green 環境は、意図しない書き込みを防ぐため、 default_transaction_read_only
が on
に設定されています. この状態では、 マイグレーションを実施することができません.
通常であれば、下記のコマンドを実行することでセッション内で書き込みを可能になります.
set default_transaction_read_only = off;
ただし, Diesel のマイグレーションコマンドではセッション管理が Diesel 内で行われるため、 このコマンドを直接実行できません.
そのため、環境変数 PGOPTIONS
の値を以下のように設定することで、 セッション開始時に default_transaction_read_only
を無効化しています.
PGOPTIONS="-c default_transaction_read_only=off"
コード例
上記を踏まえた、 GitHub Actions のコード(抜粋) は以下になります.
jobs: migration: - name: Start Port Forwarding Session run: | aws ssm start-session --target=${{ env.INSTANCE_ID }} --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters host=${{ inputs.db_endpoint }},portNumber=5432,localPortNumber=15432 & sleep 5 # 接続待ち - name: Run Migration env: DATABASE_URL: postgres://postgres:${{ env.DB_PASSWORD }}@localhost:15432/postgres PGOPTIONS: "-c default_transaction_read_only=off" run: diesel migration run
※ ${{ env.INSTANCE_ID }}
: EC2 インスタンスの instance id
まとめ
今回は、 データベースの Blue/Green デプロイ時に、Green 環境にマイグレーションを適用して、マイグレーション記録を一貫したものとする方法を紹介しました.
今回の取り組みによって、 Blue/Green デプロイ時のスキーマ変更をより安全かつ効率的に実施する基盤を確立しました.
Blue/Green デプロイ実施のハードルが下がったことで、 Omni Hub のパフォーマンス改善や機能追加をスピーディーに実施していきたいと思っています.