本記事では、Baseline Environment on AWS for Financial Services Institute(BLEA for FSI)の考え方を参考にしつつ、ウォームスタンバイ型のマルチリージョン DRを、AWS マネジメントコンソール中心で組み立てて検証します。 参考:BLEA for FSI
この記事で扱うのは自動切り替えではなく「手動切り替え」です。 CloudWatch Synthetics Canary と CloudWatch Alarm で異常を検知したあと、オペレーターが(大阪リージョン・監視リージョン)の両地点で異常を確認し、Step Functions を手動実行して切り替えるという運用を前提とします。 この記事でわかること ウォームスタンバイ DR の「構成要素」を AWS サービスに落とし込む考え方 Route 53 ARC を使った「安全に切り替えるスイッチ」の作り方 DR 発動時の切り替えを Step Functions から実行する例 切り替え中のアプリ挙動を「共有ステート」で制御する(DynamoDB グローバルテーブル)例 この記事で扱わないこと ALB / ECS / Aurora / Secrets Manager の単一リージョン構築手順(前提として作成済み) 監視結果をトリガーに切り替えまで自動実行する仕組み 切り戻し手順・設計 AWS の公式ドキュメントでは、AWS における DR(Disaster Recovery)戦略は、おおまかに次の 4 つに分類されています。 それぞれは RTO(復旧時間目標)/ RPO(復旧時点目標)とコストのトレードオフで位置づけられます。 バックアップ&リストア パイロットライト ウォームスタンバイ(本記事で採用) マルチサイト / アクティブ-アクティブ 参考:クラウド内での災害対策オプション
金融系ワークロードでは、RTO / RPO をある程度短く保ちたい一方で、常時アクティブ-アクティブほどのコストはかけづらいケースが多いです。 そこで本記事では、以下のようなウォームスタンバイ構成を題材にします。 データ層:Aurora Global Database によるクロスリージョンレプリケーション トラフィック制御:Route 53 ARC による 「安全ルール付きの手動スイッチ」 運用:外形監視(Synthetics)で検知 → 人が判断 → Step Functions を手動実行 今回構築するウォームスタンバイ構成の全体像は、次のとおりです。 本章では、後続の手順を理解しやすくするために、どのリージョンに、どの役割のコンポーネントがあるかを整理します。 本構成では、役割の異なる次の 3 つのリージョンを使用します。 東京リージョン(ap-northeast-1) 大阪リージョン(ap-northeast-3) オレゴンリージョン(us-west-2) 東京・大阪の両リージョンに、同一構成のアプリケーションスタックを配置します。 Application Load Balancer(ALB) ECS on Fargate で動作する API アプリケーション 通常時は東京のみがトラフィックを受け、大阪はウォームスタンバイとして待機します。 データベースには Aurora Global Database(PostgreSQL)を使用します。 東京リージョン :プライマリ DB クラスター 大阪リージョン:セカンダリ DB クラスター 各リージョンのECSタスクは、同一リージョンにある Aurora の「クラスターエンドポイント(タイプ:ライター)」へ接続します。 セカンダリクラスターではこのエンドポイントが ステータス inactive(書き込みを処理しない) と表示されます。これはセカンダリがライターを持たないためで、接続自体はできますが、書き込みはエラーになり、読み取りのみ実行可能です。 ただし、セカンダリだったリージョンがプライマリに昇格すると、同じエンドポイントがライターとして有効になり、書き込みも可能になります。 また、Aurora Global Database では、Global Database ライターエンドポイント(フェイルオーバー/スイッチオーバー後も常にプライマリのライターを指すエンドポイント)も利用できます。 本記事では、グローバルライターエンドポイントは使わず、東京は東京・大阪は大阪の接続先を参照する構成にします。 切り替え時にアプリの動作を安全に制御するため、DynamoDB グローバルテーブルを状態管理用ストアとして利用します。 このテーブルには、主に次のような情報を保持します。 各リージョンの稼働状態(どのリージョンが Active か) 切り替え中にアプリケーションの処理を制御するための 閉塞フラグ この記事の例では、閉塞フラグ(is_closed)を主に扱い、Active の判定は「is_closed=false のリージョン」として扱います(= リクエストを受け付けてよい状態)。 切り替え中は Step Functions が is_closed を更新し、アプリ側はそれを参照して挙動を制御します。 なお、この DynamoDB テーブルは インフラ観点での切り替え判断やトリガーには使用しません。あくまで、 切り替え中の二重書き込み防止 プライマリ/セカンダリのアプリケーション挙動制御 といった アプリケーション制御のための共有ステートとして利用する位置づけです。 外部からのアクセスは、Route 53 を通じて ALB にルーティングされます。 ARC の Routing Control 状態に応じて、東京 ALB / 大阪 ALB どちらに流すかを制御します。 東京リージョンの API に対して、以下のリージョンから外形監視を行います。 2 地点の外形監視結果をもとに、オペレーターが判断して Step Functions を手動実行し、リージョン切り替えを行います。 本記事は DR 構成の設計・切り替えが主題のため、ALB / ECS / Aurora / Secrets Manager などの単一リージョン構成は作成済みという前提で進めます。 ECS で動かしているアプリは、シンプルな Node.js + Express 構成です。 本検証では、東京用/大阪用でコンテナイメージを分け、トップページの表示文言で到達リージョンを判別できるようにしています。 ※本番想定では、エラーメッセージの詳細をレスポンスに返さないなどの配慮が必要です(ここでは検証のためレスポンスに含めています)。 Dockerfile の内容 server.js の内容 アプリケーションコード(server.js)側では、 DB 接続情報はすべて 環境変数から取得 SSL 用の CA バンドルのパスも環境変数で上書き可能 という形にします。 DB 接続情報(ユーザー名・パスワードなど)は、 ECS タスク定義から Secrets Manager を参照する形で設定します。 タスク定義のコンテナ設定では、以下の環境変数を定義しました。 このうち、PGDATABASE を除く項目については、値を直接記述するのではなく、Secrets Manager に保存したシークレットを参照します。 ECS から Secrets Manager の値を環境変数として取得するには、タスク定義の環境変数設定で 値のタイプを「ValueFrom」 に指定し、以下の形式でシークレットを参照します。 参考:Amazon ECS 環境変数経由で Secrets Manager シークレットを伝達する
Secrets Manager では、シークレット情報を 他リージョンへレプリケートすることができます。 ただし今回の構成では、上述のとおり「リージョンごとに接続先が異なる」ため、東京用/大阪用でシークレットを分ける設計にします。 ちなみに、Secrets Manager のシークレットは以下のような形式で登録します。 本章では、リージョン切り替えに関わる DR リソースのみを対象に、AWS マネジメントコンソールから順に作成します。 リージョン切り替え時に利用する アプリケーション制御フラグを保持するため、DynamoDB テーブルを作成します。 テーブルは「リージョンごとに 1 レコードを持つ」前提のため、テーブル名とパーティションキーのみを指定し、その他の設定はすべてデフォルトのまま作成しました。 作成後、以下の 2 レコードを登録します。 東京リージョン 大阪リージョン 作成した DynamoDB テーブルに 大阪リージョンのレプリカを追加し、グローバルテーブル化します。
数分後、大阪リージョンにレプリカが作成されていることを確認できます。
ここでは、東京リージョンに作成済みの Aurora PostgreSQL クラスターを Aurora Global Database に拡張し、大阪リージョン側にセカンダリクラスター(クロスリージョンレプリカ)を追加します。 まず、RDS コンソールで対象クラスターを選択し、[アクション] → [AWS リージョンを追加]をクリックします。
なお、DB クラスターで Secrets Manager の統合を有効化している場合、Aurora Global Database は Secrets Manager 統合をサポートしていないので、グローバルデータベース作成時にエラーになります。
エラーが発生した場合は、 Secrets Manager 統合をオフにしてから進めます。 追加先リージョンには Asia Pacific(Osaka) を選択します。
あとは DB クラスターのクラスやサブネットグループなど、必要な項目を指定して作成を進めます(詳細パラメータは本記事では割愛)。 作成完了後、RDS コンソール上で 大阪リージョン側にセカンダリクラスターが追加されたことを確認できました。
Aurora Global Database を作成すると、大阪リージョン側(セカンダリクラスター)のライターエンドポイント(inactive)を確認できます。 この ライターエンドポイント(inactive)と DB クラスター識別子を、大阪リージョン側 Secrets Manager の以下のキーに登録します。 そのほかの値(username、password、engine、port)は、東京リージョン側と同一の内容にします。 Aurora Global Database では、接続先として グローバルライターエンドポイントを使う選択肢があります。
これはフェイルオーバー/スイッチオーバー後もライター側へ追従するため、接続先の切り替え手間を減らせます。 しかし、グローバルライターエンドポイントを使う場合、各リージョンのアプリ(ECS)から どちらのリージョンの DB にも到達できるネットワーク疎通が必要です(例:VPC ピアリング / Transit Gateway など)。 参考:グローバルライターエンドポイントの使用に関する考慮事項
今回は検証用に 東京 VPC と大阪 VPC を同一 CIDR で作成してしまい、VPC ピアリング等でリージョン間接続を組めませんでした。 そのため 東京は東京の DB エンドポイント/大阪は大阪の DB エンドポイントを参照する構成とし、Secrets Manager も東京用・大阪用で分けています。 Application Recovery Controller(ARC)は、リージョン切り替えの「スイッチ」を安全に運用するための仕組みです。 ARC の ルーティングコントロール を使って「東京か大阪のどちらに向けるか」を切り替えられるようにし、安全ルール で「同時に ON できるのは 1 つだけ」という制約をかけます。 本記事で作成するリソースの関係は、次のとおりです。
それでは、順に作成していきます。 まず ARC コンソールを開き、左メニュー [クラスター] → [作成] からクラスターを作成します(クラスター名は任意)。 続けて、作成したクラスター内で [コントロールパネルを作成] をクリックします。 次に、コントロールパネル内の [ルーティングコントロールを追加] から、東京/大阪向けのルーティングコントロールを 2 つ作成します。
東京リージョン用(rc-tokyo)。
大阪リージョン用(rc-osaka)。
続いて、コントロールパネル内の [安全ルールを追加] から安全ルールを作成します。
ルールタイプに アサーションルールを選び、任意の名前を入力したうえで、作成した 2 つのルーティングコントロール(rc-tokyo, rc-osaka)を対象に指定します。
rc-tokyo か rc-osaka のどちらか1つしかオンにできない条件になるように、キャプチャのとおり設定しました。
最後に、Route 53 レコードと紐づけるための ルーティングコントロールヘルスチェックを作成します。
東京用ヘルスチェック(rc-tokyo) 大阪用ヘルスチェック(rc-osaka)
次に Route 53(ホストゾーン)で、同じ名前の A レコードを 2 本(プライマリ/セカンダリ)作成します。 ARC のヘルスチェックに応じて、東京 ALB(プライマリ)/ 大阪 ALB(セカンダリ)へ振り分ける設定です。 ARC の切り替えを主軸にしたいため、「ターゲットのヘルスを評価」は「いいえ」にします。 なお「ヘルスチェック」には、プルダウンから rc-tokyo 用のルーティングコントロールヘルスチェックを選択します。 Route 53 のフェイルオーバールーティングは、同じ名前 / 同じタイプのレコードを プライマリとセカンダリの 2 本で作るのが前提です。 東京と同じレコード名で作成しつつ、次の点だけ変更します。 大阪リージョンと監視リージョン(オレゴン)の CloudWatch Synthetics Canary から、Route 53 で公開している DNS 名 に対して HTTP リクエストを定期実行し、ユーザーが実際に到達する経路(Route 53 → ALB → ECS → DB)の正常性を監視します。 まずは 大阪リージョンの CloudWatch コンソールで、左メニューの Synthetics Canaries をクリックし、[Canary を作成] をクリックします。 テンプレートは 「ハートビートのモニタリング」 を選択します。 次に、Canary の 名前と、監視対象 URL(例:http:// 続いて、Canary の実行スケジュールを設定します。 また、作成画面の CloudWatch アラーム(オプション)でアラーム設定を有効にしておくと、Canary のメトリクスを元に CloudWatch Alarm を自動作成できます。 作成後、CloudWatch のアラーム画面に移動すると、Alarm が自動生成されていることを確認できます。必要に応じて、評価期間やしきい値は後から調整します。 同様の手順で オレゴンリージョンにも Canary と Alarm を作成し、監視対象は同じく Route 53 の DNS 名(/accounts) を指定します。 大阪リージョン(ap-northeast-3)に、切り替え(スイッチオーバー)を実行する Step Functions を作成します。 ステートマシンでは、切り替え中の二重更新を避けるために DynamoDB の閉塞フラグを更新しつつ、ARC の routing control を切り替え、Aurora Global Database のスイッチオーバー完了(available)を確認してから大阪側をオープンする流れにします。 グラフビューにすると、こんな感じです。
ARC の routing control 更新は Recovery Cluster API(データプレーン)を呼び出します。 この API は クラスターのリージョナルエンドポイント(用意された複数リージョンのうちいずれか)を指定して呼び出す必要があります。 そのため本記事では、Step Functions の AWS SDK 統合だけで完結させるのではなく、Lambda 側で endpoint_url を指定する構成にしました。 Recovery Cluster API のエンドポイントが用意されるリージョンは、 2026/01/21 時点では次の 5 つです。 変更される可能性があるため、最新は公式ドキュメント/マネジメントコンソールで確認してください。 切り替え用 Lambda は次のようなシンプルな実装にしています(複数エンドポイントを順に試し、成功したら終了)。 環境変数は次を設定します。 ARC_CLUSTER_ENDPOINTS は、ARC コンソールで作成したクラスター画面から確認できます。
ここでは、前章までに作成した構成を使って 東京 → 大阪へのスイッチオーバーを実行し、切り替え前後の挙動を確認します。 今回の検証では、構成の動作確認を優先し、実際の障害注入や Canary / Alarm の発報は行わず、Step Functions を手動実行します。 一方で、運用では CloudWatch Synthetics Canary を大阪リージョンとオレゴンリージョンの両方で実行し、両地点の結果をもとにオペレーターが判断して Step Functions を実行する流れを想定しています。 今回の検証は、次の流れで行いました。 スイッチオーバー前に、5.6 で作成した Route 53 の ALB へのルーティング用 A レコードへアクセスします。 Step Functions を手動で実行します。 実行が成功しました。
今回の検証では およそ 1 分程度で完了しました。
スイッチオーバー後に同じ DNS 名へアクセスすると、Core Banking Sample Osaka が表示され、大阪リージョン側へ切り替わったことが確認できます。 Aurora Global Database(ライターの移動) DynamoDB(閉塞フラグ) 本記事では、BLEA for FSI の考え方を参考にしつつ、ウォームスタンバイ構成の DR をコンソール操作のみで組み立て、Step Functions を手動実行して切り替えの一連の動作を確認しました。 今回の記事で確認したのは、主に次の 3 点です。 なお今回は検証をシンプルにするため、障害注入や Canary / Alarm の発報までは行わず、Step Functions を手動実行します。 ただし運用では、大阪リージョンとオレゴンリージョンの Canary で検知し、両方の結果をもとにオペレーターが判断して切り替える流れを想定しています。 「ウォームスタンバイの DR は、作って終わりではなく、切り替え・戻し・監視・運用判断まで含めて初めて“使える設計”になる」——その入口として、今回の手作業検証が何かの参考になれば幸いです。
1. はじめに
2. AWS における 4 つの DR 戦略とウォームスタンバイ
2.1 4 つの DR 戦略の概要
2.2 今回の構成でウォームスタンバイを選んだ理由
3. アーキテクチャ概要

3.1 リージョン構成と役割
通常時にトラフィックを受けるプライマリ。ALB / ECS と Aurora Global Database のプライマリ DB クラスター(ライター)が稼働します。
障害時に切り替えるウォームスタンバイ。縮小キャパシティの ALB / ECS と、Aurora Global Database のセカンダリ DB クラスター(リーダー)が常時稼働します。
監視専用リージョン。Synthetics Canary を配置し、リージョン固有障害と監視側障害の切り分け(第三地点)に使います。
3.2 アプリケーションレイヤー
(例:残高照会・取引・件数管理などの勘定系 API)
3.3 データベースレイヤー

3.4 ステート管理(DR 制御用)
3.5 トラフィック制御
3.6 監視と切り替えの位置づけ
4. 前提:DR 検証で利用したアプリケーションの構成
4.1 ECS で利用した Dockerfile / server.js の概要
# シンプルな Node.js 実行環境
FROM node:18-alpine
WORKDIR /usr/src/app
# CA証明書ストア + curl を追加し、RDS の CA バンドル(グローバル)を取得
RUN apk add --no-cache ca-certificates curl \
&& update-ca-certificates \
&& curl -fsSL https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem \
-o /usr/src/app/rds-ca-bundle.pem
# 依存関係だけ先にコピーして npm install
COPY package*.json ./
RUN npm install --only=production
# アプリ本体をコピー
COPY . .
# Express の待ち受けポート
EXPOSE 3000
CMD ["node", "server.js"]
// server.js
const express = require('express');
const { Pool } = require('pg');
const fs = require('fs');
const app = express();
// RDS CA バンドルのパス
const caPath = process.env.RDS_CA_BUNDLE_PATH || '/usr/src/app/rds-ca-bundle.pem';
// ECS タスク定義から渡される環境変数で接続先を切り替えられるようにする
const pool = new Pool({
host: process.env.PGHOST,
port: Number(process.env.PGPORT) || 5432,
user: process.env.PGUSER,
password: process.env.PGPASSWORD,
database: process.env.PGDATABASE,
// SSL/TLS(CA検証あり)
ssl: {
ca: fs.readFileSync(caPath, 'utf8'),
rejectUnauthorized: true,
},
});
// シンプルなトップページ
app.get('/', (_req, res) => {
res.send('<h1>Core Banking Sample Tokyo</h1><p><a href="/accounts">口座一覧を見る</a></p>');
});
// /accounts で Aurora PostgreSQL の accounts テーブルを HTML テーブル表示
app.get('/accounts', async (_req, res) => {
try {
const result = await pool.query(`
SELECT
account_number,
holder_name,
balance,
updated_at
FROM accounts
ORDER BY id;
`);
const rows = result.rows;
// すごくシンプルな HTML テーブルに整形
const htmlRows = rows
.map(
(r) => `
<tr>
<td>${r.account_number}</td>
<td>${r.holder_name}</td>
<td style="text-align:right">${Number(r.balance).toLocaleString()}</td>
<td>${r.updated_at}</td>
</tr>`
)
.join('');
const html = `
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>口座一覧</title>
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #ccc; padding: 4px 8px; }
th { background: #f5f5f5; }
</style>
</head>
<body>
<h1>口座一覧 (/accounts)</h1>
<table>
<thead>
<tr>
<th>口座番号</th>
<th>名義</th>
<th>残高</th>
<th>更新日時</th>
</tr>
</thead>
<tbody>
${htmlRows}
</tbody>
</table>
<p><a href="/">TOPに戻る</a></p>
</body>
</html>
`;
res.send(html);
} catch (err) {
console.error('DB error', err);
res.status(500).send('<h1>DB error</h1><pre>' + err.message + '</pre>');
}
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`listening on ${port}`);
});
4.2 ECS タスク定義での Secrets Manager 参照

4.3 Secrets Manager の設計方針

キー
登録する値(例:マスク済み)
username
< Aurora に接続する DB ユーザ名 >
password
< 上記ユーザーの DB パスワード >
engine
postgres
host
< 接続先のDB エンドポイント >
port
5432
dbClusterIdentifier
< 対象 Aurora の DB クラスター識別子 >
5. 構築してみた:リージョン切り替えのためのセットアップ
5.1 DynamoDB の構築
region(String)


5.2 DynamoDB グローバルテーブルの作成


5.3 Aurora Global Database の作成




5.4 大阪セカンダリの DB 接続情報を Secrets Manager に登録



5.5 Route 53 ARC のセットアップ


クラスター名に先ほどのクラスターを選び、任意のコントロールパネル名を入力して作成します。










5.6 Route 53 のALB へのルーティング用 A レコード作成
東京 ALB 用(プライマリ)


大阪 ALB 用(セカンダリ)

5.7 (大阪・オレゴンリージョン)Canary で Route 53 を監視する






5.8 Step Functions の作成
{
"Comment": "Switchover: Close Tokyo -> Switch ARC (Lambda) -> Switchover Aurora Global -> Wait -> Open Osaka",
"StartAt": "Init",
"States": {
"Init": {
"Type": "Pass",
"Parameters": {
"now.$": "$$.State.EnteredTime"
},
"Next": "CloseTokyo"
},
"CloseTokyo": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:dynamodb:updateItem",
"Parameters": {
"TableName": "<DynamoDBテーブル名>",
"Key": {
"region": {
"S": "ap-northeast-1"
}
},
"UpdateExpression": "SET is_closed = :t, updated_at = :now",
"ExpressionAttributeValues": {
":t": {
"BOOL": true
},
":now": {
"S.$": "$.now"
}
}
},
"Retry": [
{
"ErrorEquals": [
"DynamoDB.ProvisionedThroughputExceededException",
"DynamoDB.ThrottlingException",
"States.TaskFailed"
],
"IntervalSeconds": 2,
"BackoffRate": 2,
"MaxAttempts": 6
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "Fail"
}
],
"Next": "SwitchArc"
},
"SwitchArc": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "arn:aws:lambda:ap-northeast-3:<AWSアカウントID>:function:<Lambda名>",
"Payload": {
"UpdateRoutingControlStateEntries": [
{
"RoutingControlArn": "arn:aws:route53-recovery-control::<AWSアカウントID>:controlpanel/<コントロールパネルID>/routingcontrol/<ルーティングコントロールID_東京>",
"RoutingControlState": "Off"
},
{
"RoutingControlArn": "arn:aws:route53-recovery-control::<AWSアカウントID>:controlpanel/<コントロールパネルID>/routingcontrol/<ルーティングコントロールID_大阪>",
"RoutingControlState": "On"
}
]
}
},
"ResultPath": "$.arc",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException",
"States.TaskFailed"
],
"IntervalSeconds": 2,
"BackoffRate": 2,
"MaxAttempts": 6
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "Fail"
}
],
"Next": "SwitchoverAurora"
},
"SwitchoverAurora": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:rds:switchoverGlobalCluster",
"Parameters": {
"GlobalClusterIdentifier": "<Auroraグローバルクラスター識別子>",
"TargetDbClusterIdentifier": "arn:aws:rds:ap-northeast-3:<AWSアカウントID>:cluster:<大阪DBクラスター識別子>"
},
"ResultPath": "$.rds",
"Retry": [
{
"ErrorEquals": [
"RDS.ThrottlingException",
"RDS.ServiceUnavailableException",
"States.TaskFailed"
],
"IntervalSeconds": 5,
"BackoffRate": 2,
"MaxAttempts": 6
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "Fail"
}
],
"Next": "WaitAurora"
},
"WaitAurora": {
"Type": "Wait",
"Seconds": 15,
"Next": "DescribeGlobalCluster"
},
"DescribeGlobalCluster": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:rds:describeGlobalClusters",
"Parameters": {
"GlobalClusterIdentifier": "<Auroraグローバルクラスター識別子>"
},
"ResultPath": "$.rdsDescribe",
"Retry": [
{
"ErrorEquals": [
"RDS.ThrottlingException",
"RDS.ServiceUnavailableException",
"States.TaskFailed"
],
"IntervalSeconds": 5,
"BackoffRate": 2,
"MaxAttempts": 10
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "Fail"
}
],
"Next": "CheckAuroraStatus"
},
"CheckAuroraStatus": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.rdsDescribe.GlobalClusters[0].Status",
"StringEquals": "available",
"Next": "OpenOsaka"
}
],
"Default": "WaitAurora"
},
"OpenOsaka": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:dynamodb:updateItem",
"Parameters": {
"TableName": "<DynamoDBテーブル名>",
"Key": {
"region": {
"S": "ap-northeast-3"
}
},
"UpdateExpression": "SET is_closed = :f, updated_at = :now",
"ExpressionAttributeValues": {
":f": {
"BOOL": false
},
":now": {
"S.$": "$$.State.EnteredTime"
}
}
},
"Retry": [
{
"ErrorEquals": [
"DynamoDB.ProvisionedThroughputExceededException",
"DynamoDB.ThrottlingException",
"States.TaskFailed"
],
"IntervalSeconds": 2,
"BackoffRate": 2,
"MaxAttempts": 6
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "Fail"
}
],
"End": true
},
"Fail": {
"Type": "Fail",
"Cause": "Switchover workflow failed"
}
}
}

import os
import boto3
def lambda_handler(event, context):
entries = event.get("UpdateRoutingControlStateEntries")
if not entries:
raise ValueError("UpdateRoutingControlStateEntries is required")
api_region = os.getenv("ARC_API_REGION")
endpoints = [e.strip() for e in os.getenv("ARC_CLUSTER_ENDPOINTS", "").split(",") if e.strip()]
if not endpoints:
raise ValueError("ARC_CLUSTER_ENDPOINTS is required (comma-separated endpoints)")
last_err = None
for endpoint_url in endpoints:
try:
client = boto3.client(
"route53-recovery-cluster",
region_name=api_region,
endpoint_url=endpoint_url
)
resp = client.update_routing_control_states(
UpdateRoutingControlStateEntries=entries
)
return {"ok": True, "endpoint_used": endpoint_url, "response": resp}
except Exception as e:
last_err = e
raise last_err

6. 切り替えてみた:スイッチオーバーの挙動
6.1 今回の検証手順
6.2 切り替え前:東京に向いていることを確認
レスポンスには Core Banking Sample Tokyo が表示され、東京リージョン側にルーティングされていることが分かります。

6.3 Step Functions を手動実行



6.4 切り替え後:大阪に向いていることを確認
(DNS キャッシュの影響を受ける場合があるため、反映までの時間は TTL やクライアントの挙動に依存します)
6.5 Aurora / DynamoDB の状態確認
大阪リージョン側がプライマリ(ライター)になっていることを確認できます。
Step Functions 側では switching-over が完了して available になるまで待機してから後続へ進むため、切り替え途中の状態で処理が進みにくい構成にします。
東京(ap-northeast-1)の is_closed が true になっており、切り替えに合わせてステートが更新されることを確認できます。
updated_at に更新時刻も記録されます。
7. まとめ