はじめに
※ 本記事に登場するシステム名、リソース名、IPアドレスなどはすべて仮称およびダミーデータであり、実際の運用環境を示すものではありません。
ミッションクリティカルな社会インフラシステムにおいて、「リージョン障害(巨大災害や広域ネットワーク障害)」は決して無視できないリスクです。
本連載では、自身の備忘録として、AWSの「東京リージョン(稼働系)」から「大阪リージョン(待機系)」への切り替え(フェイルオーバー)を、AWS Step FunctionsとAWS Lambdaでオーケストレーションし、心理的負担の少ない安全な自動・半自動切り替えを実現した実践ノウハウを記録しますので、ご活用いただけたら幸いです。
連載第1回となる本記事では、システム全体のDR設計思想、RPO/RTOの要件、DB同期の仕組み、そして事故を防ぐ4フェーズ構成について記述します。
全体アーキテクチャと基本方針
1. データ同期と復旧目標(RPO / RTO)
DBの特性やコスト要件に合わせて、異なる2種類の同期方式を採用していました。
| 対象リソース | データ同期・転送方法 | 同期頻度 | 復旧地点 (RPO) | 復旧時間 (RTO) |
|---|---|---|---|---|
| DB (PostgreSQL) | クロスリージョン・リードレプリカ | 随時(準リアルタイム) | 直前(数秒〜数分前) | 数分(昇格のみ) |
| DB (Oracle) | クロスリージョン・バックアップ | 随時(S3へスナップショット/LOG転送) | 直前(約15分前) | 最大1時間(PITRリストア) |
| AP (EC2) | 同期なし(大阪側で直接起動・設定) | - | 設定時点 | 数分 |
💡 コスト最適化の工夫:待機系リソースの最小化
マルチリージョンDRで常に課題となるのが「待機系(大阪)の維持コスト」です。
本構成では、平常時の大阪リージョンのリードレプリカを最小インスタンスクラス(最小限のスペック)で稼働させてランニングコストを大幅に抑えています。
いざDRを発動する際、後述の第1フェーズの自動化フローの中で「本番稼働用のインスタンスクラスへのスケールアップ(Modify)」を組み込むことで、コストとパフォーマンスの両立を実現を行いました。
DRインフラ設計の3つのポイント
① Route 53を用いた「エンドポイントの完全隠蔽」
RDSのインスタンスやRDS Proxy、SESのエンドポイントは、再作成やリージョン変更のたびにドメイン内のハッシュ値が変わってしまいます。
これらをアプリケーション(APサーバ)の設定ファイルに直接記述すると、DR切り替え時にアプリの再デプロイや設定変更が必要になり、RTOが悪化します。
そのため、接続先はすべてRoute 53のカスタムFQDN(CNAME/Aレコード)で抽象化しました。
-
diaap.example-infra.net(AP接続用 Aレコード) -
diards.example-infra.net(PostgreSQL接続用 CNAME) -
unkrds.example-infra.net(Oracle接続用 CNAME) -
ses.example-infra.net(メール送信接続用 CNAME)
DR切り替え時は、Route 53のレコード(値)を上書きするだけで、アプリケーション側の修正・再起動を一切不要にする設計にしました。
② S3からの「遅延ロード」回避策
OracleデータベースをS3上のバックアップからPITRリストアして起動した場合、インスタンス自体はすぐに available になります。
しかし、実際のデータブロックはS3上に残っており、アクセスされたブロックから順次ストレージにロードされる「遅延ロード」が発生します。この状態でサービスを公開すると、初回アクセス時に極端なレイテンシーが発生し、アプリがタイムアウトします。
これを回避するため、本設計では「切り替えフローの途中で、主要テーブルに対して SELECT * 等の全表スキャンクエリを意図的に実行し、データを一括ウォームアップする」という運用ステップを明記・組み込みました。
③ 判断と実行を分離する「4フェーズ切り替えフロー」
一発の全自動スクリプトで切り替えを行うと、途中で予期せぬエラーが起きた際に「どこまで処理が進んだか分からない」という状態に陥ります。
そのため、DR手順を役割ごとに「4つのフェーズ」に分割しました。
DR切り替えの4つのフェーズ概要
第1フェーズ:DBプロビジョニング&環境有効化(Step Functions)
- 実行場所: 大阪リージョン
-
概要:
PostgreSQLレプリカの「Promote(昇格)」と、Oracleの「PITR(リストア)」をParallelステートで同時実行します。
Lambdaの15分制限に阻まれないよう、ポーリングロジック(Wait -> Poller -> Choice)をStep Functions上に構築。完了後にCloudWatchアラームやEventBridgeルールを有効化します。
第2フェーズ:DNSルーティング切り替え(Lambda)
- 実行場所: 大阪リージョン(Route 53操作)
-
概要:
Route 53の各種レコード(diaap...,diards...,unkrds...,ses...)を一括で大阪リージョンのIP/エンドポイントへUPSERT変更。
変更後はget_changeAPIを用いて、全エッジロケーションへの伝播(INSYNC)をチェック・待機します。
第3フェーズ:旧環境(東京)のクリーンアップ(Lambda)
- 実行場所: 大阪から「東京」を操作
-
概要:
二重課金や誤動作を防ぐため、東京の旧DB(app-db-pg-prd,app-db-ora-prd)の削除保護を解除し、最終スナップショットを取得した上で削除します。また、東京側の無駄なCloudWatchアラームやスケジュール実行を停止します。
第4フェーズ:切り戻し(フェイルバック)準備(Lambda)
- 実行場所: 大阪から「東京」を操作
-
概要:
将来的に東京リージョンが復旧した際、再び稼働系を東京へ戻せるよう、「大阪(新本番)から東京(新待機系)」へのクロスリージョン・リードレプリカ作成を非同期でキックします。
まとめと次回予告
マルチリージョンDRを成功させる鍵は、単にスクリプトを書くことではなく、「データ保護の仕組み(レプリカ/バックアップ)」「ネットワークの抽象化(Route 53)」「安全な段階的実行(4フェーズ分割)」を綺麗に組み合わせる設計力にあるのかなと思います...
次回は、このDR切り替えの心臓部である「第2回:Step Functionsによる第一フェーズ(異種DBの並列復旧と状態通知)の実装」について、実際のASL(JSON)やPythonコードを交えて深掘りします!
連載インデックス
- 第1回:【全体設計・アーキテクチャ編】(本記事)
- 第2回:【Step Functions実装編(第1フェーズ)】
- 第3回:【DNS切替 & クリーンアップ編(第2〜4フェーズ)】