Feedforce Developer Blog

フィードフォース開発者ブログ

Rust Diesel ORM を利用したアプリケーションで Aurora PostgreSQL の Blue/Green デプロイを行う

こんにちは、 Omni Hub チームの shunten31 です. すっかり寒くなりましたね.

今回は、 最近取り組んできたデータベースの Blue/Green デプロイについて書かせていただきます.

はじめに

データベースの Blue/Green デプロイは、ダウンタイムを最小化しながらデータベースのスキーマ更新等を行える効果的な手法です. しかし、Green 環境へのスイッチオーバーを行う前に、事前に Green 環境に対してスキーマ変更やデータ操作を行う必要があります. これらの操作は基本的に Green 環境に直接接続して、SQL コマンドを打ち込むことで実施します.

一方で、 アプリケーションが ORM を使用している場合、 スキーマ変更はマイグレーションとして記録し、 一貫性を保つ必要があります. しかし、手動で SQL コマンドを実行する方法では、 マイグレーションの記録とデータベースの状態が不整合となり得ることが課題となります.

TL;DR

この課題を解決するため、以下の手法によって、ORM のマイグレーションコマンドを直接 Green 環境に適用できるようにしました.

  1. 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_onlyon に設定されています. この状態では、 マイグレーションを実施することができません. 通常であれば、下記のコマンドを実行することでセッション内で書き込みを可能になります.

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 のパフォーマンス改善や機能追加をスピーディーに実施していきたいと思っています.