dr-work-record PG16 本番 実行手順(当日オペ用・自己完結)
🏭 本番当日はこのファイルを上から順に実行する(コピペ可・実識別子入り)。 「なぜ・調査・consumer・設計判断」は
preflight.md(リファレンス)を参照。汎用手順の背景は../procedure-production.md。⚠️ 停止を伴うのは §3②の瞬断(事前枠・数十秒)と §7④の書込断(当日枠・数秒)のみ。他は無停止想定。 ⚠️ 破壊的操作(Global削除・reboot・Switchover・旧blue削除)は指差し確認。各節の GO 条件を満たしてから進む。 ⚠️ verify script / terraform 実行は
terraform_for_awsリポジトリ(develop) のルートから。psql は §0-1 のpgtunnelを先に読み込む。 ⚠️ AWS プロファイル(★2026-08-26 実機で訂正。以前の記述は参照/変更が逆だった):
用途 プロファイル 実体( ~/.aws/config)参照(read-only) productionread-only-role変更・削除(本手順の大半) production-adminfd-platform-admin-roleterraform production-terraformproduction-adminの credential_process→
productionではmodify-db-cluster/reboot-db-instance/delete-global-cluster等は実行できない。変更系は必ずproduction-adminを使う。
0. 前提・変数(DoR)
- [ ] preflight 完了(read-only・blocker なし)/ consumer 確認(app=cluster ep✅ / Retool の Host 確認済 / IP直なし)
- [ ] target/source CPG が Terraform で作成済:
dr-work-record-pg16(PG16・cluster/instance)/dr-work-record-pg13-bg(PG13)。両CPGともrds.logical_replication=1・wal_sender_timeout=0・shared_preload_libraries=pgaudit,pg_stat_statements・pgaudit.log=allを含む(preflight.md§3・pgaudit ドリフト是正) - [ ] ★pgaudit を有効化する:本番は現状 default PG で pgaudit 未適用(TF からドリフト)。本移行で pgaudit.log=all を有効化(他サービスは既に有効で整合的・ドリフト是正)。監査ログ量が増える点は関係者へ周知しておく。
- [ ] メンテ枠・関係者(在宅事業部=医師アプリ実機E2E)・ロールバック担当 待機
- [ ] 監視 mute / DNS TTL≤5s
# 変数(本番・us-east-1)
export AWS_PROFILE=production-admin # ★参照・変更ともこれ(fd-platform-admin-role)。terraform のみ production-terraform
R=us-east-1
CL=dr-work-record
CL_ARN=arn:aws:rds:us-east-1:967691968827:cluster:dr-work-record
WRITER=dr-work-record-1 # ★実行直前に describe で Writer を再確認
READER=dr-work-record-0
BASTION=i-09384db1d690bcfd0 # fd-platform-bastion(us-east-1)
SECRET=dr-work-record
# Writer/Reader を実行直前に再確認(入れ替わり防止)
aws rds describe-db-clusters --region $R --db-cluster-identifier $CL \
--query 'DBClusters[0].DBClusterMembers[].{id:DBInstanceIdentifier,writer:IsClusterWriter}' --output json0-1. 踏み台 psql ヘルパ(本手順の psql 系で共通利用・最初に読み込む)
SSM port-forward + Secret 取得を関数化。
pgtunnel <db-host> <local-port>後にpsqlが使える。終了はpgclose <local-port>。
pgtunnel(){ HOST=$1; LPORT=$2;
nohup aws ssm start-session --region $R --target $BASTION \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters "{\"host\":[\"$HOST\"],\"portNumber\":[\"5432\"],\"localPortNumber\":[\"$LPORT\"]}" > /tmp/pgt-$LPORT.log 2>&1 &
for i in $(seq 1 15); do grep -q "Waiting for connections" /tmp/pgt-$LPORT.log 2>/dev/null && break; sleep 1; done
SEC=$(aws secretsmanager get-secret-value --region $R --secret-id $SECRET --query SecretString --output text)
export PGUSER=$(echo "$SEC" | python3 -c 'import sys,json;print(json.load(sys.stdin)["DB_USERNAME"])')
export PGPASSWORD=$(echo "$SEC" | python3 -c 'import sys,json;print(json.load(sys.stdin)["DB_PASSWORD"])')
export PGDATABASE=$(echo "$SEC" | python3 -c 'import sys,json,urllib.parse as u;print(u.urlparse(json.load(sys.stdin)["DATABASE_URL"]).path.lstrip("/"))')
export PGHOST=127.0.0.1 PGPORT=$LPORT; echo "psql ready: $PGDATABASE @ $HOST (local:$LPORT)"; }
pgclose(){ pkill -f "localPortNumber.*$1" 2>/dev/null; }
# よく使う blue(現primary) / reader エンドポイント
BLUE_EP=$(aws rds describe-db-clusters --region $R --db-cluster-identifier $CL --query 'DBClusters[0].Endpoint' --output text)
READER_EP=$(aws rds describe-db-clusters --region $R --db-cluster-identifier $CL --query 'DBClusters[0].ReaderEndpoint' --output text)1. D-0 保険スナップショット(ロールバック起点)
SNAP=dr-work-record-pre-pg16-$(date +%Y%m%d%H%M)
aws rds create-db-cluster-snapshot --region $R \
--db-cluster-identifier $CL --db-cluster-snapshot-identifier $SNAP
aws rds wait db-cluster-snapshot-available --region $R --db-cluster-snapshot-identifier $SNAP
aws rds describe-db-cluster-snapshots --region $R --db-cluster-snapshot-identifier $SNAP \
--query 'DBClusterSnapshots[0].{id:DBClusterSnapshotIdentifier,engine:EngineVersion,status:Status}' --output json # engine=13.23 / available- [ ] スナップショット
available・engine=13.23
2. ① Global 削除(standalone 化・無停止想定)
# 削除前: 単一リージョン(writer 1件・secondary/reader 無し)を再確認
aws rds describe-global-clusters --region $R --global-cluster-identifier $CL \
--query 'GlobalClusters[0].GlobalClusterMembers[].{arn:DBClusterArn,writer:IsWriter,readers:Readers}' --output json
# detach → 空 global 削除
aws rds remove-from-global-cluster --region $R --global-cluster-identifier $CL --db-cluster-identifier "$CL_ARN"
aws rds wait db-cluster-available --region $R --db-cluster-identifier $CL
# ★削除保護が有効だと delete-global-cluster は失敗する(mental-appointment 本番で実測 2026-08-26):
# InvalidParameterCombination: Cannot delete protected Global Cluster <name>, please disable deletion protection and try again.
aws rds describe-global-clusters --region $R --global-cluster-identifier $CL \
--query 'GlobalClusters[0].{protection:DeletionProtection,members:length(GlobalClusterMembers)}' --output json
aws rds modify-global-cluster --region $R --global-cluster-identifier $CL --no-deletion-protection
aws rds delete-global-cluster --region $R --global-cluster-identifier $CL
# 確認: standalone(global=null)・稼働継続
aws rds describe-db-clusters --region $R --db-cluster-identifier $CL \
--query 'DBClusters[0].{global:GlobalClusterIdentifier,status:Status}' --output json # global=null / available- [ ]
GlobalClusterIdentifier=nullかつStatus=available
3. ② source CPG(logical=1・pgaudit込み) 付替 → Reader→Writer reboot(★瞬断・事前枠)
★この付替+reboot で blue(PG13) に pgaudit も同時に有効化される(現状 default PG=pgaudit 未適用からのドリフト是正)。監査ログ量が増える点は §0 で関係者へ周知済であること。
# 本番は default CPG のため、logical=1 の custom CPG(dr-work-record-pg13-bg) へ付替
aws rds modify-db-cluster --region $R --db-cluster-identifier $CL \
--db-cluster-parameter-group-name dr-work-record-pg13-bg --apply-immediately
aws rds wait db-cluster-available --region $R --db-cluster-identifier $CL
# pending-reboot を適用(★Reader→Writer の順=AWS推奨・安全側)
# ※通常の計画 reboot は failover しないため Writer-first でも動作するが、万一 Writer 再起動中に
# 計画外 failover が起きると params 未適用の Reader が昇格し logical=on にならない恐れ。
# 安全側で Reader を先に済ませてから Writer(=瞬断) を reboot する。
aws rds reboot-db-instance --region $R --db-instance-identifier $READER
aws rds wait db-instance-available --region $R --db-instance-identifier $READER
aws rds reboot-db-instance --region $R --db-instance-identifier $WRITER # ★これが瞬断
aws rds wait db-instance-available --region $R --db-instance-identifier $WRITER
# ★所要時間の実測(dr-work-record 本番 2026-08-26 JST 02:09〜02:13):
# Reader: CLI 63秒 / RDS event shutdown→restarted 13秒
# Writer: CLI 65秒 / RDS event shutdown→restarted 13秒 = 実際の瞬断は約13秒
# Writer 再起動直後に Reader は自動復帰(event: Read replica has been disconnected from the writer instance and reconnected.)
# ★このとき RDS が出す下記2種のイベントは異常ではない(いずれも Aurora の仕様上の自動調整):
# - rds.logical_replication was set to a value incompatible with replication. adjusted from 1 to 0.(Reader のみ)
# - max_wal_senders ... adjusted from 10 to 20.(両機・論理レプリケーション用の引き上げ)
# 踏み台 psql で Writer(cluster ep) と Reader(reader ep) 双方を確認:
pgtunnel "$BLUE_EP" 15432 # Writer
psql -tAc "SELECT current_setting('rds.logical_replication') AS logical, current_setting('wal_sender_timeout') AS wst, current_setting('shared_preload_libraries') AS spl, current_setting('pgaudit.log') AS pgaudit;"
pgclose 15432
pgtunnel "$READER_EP" 15432 # Reader
psql -tAc "SELECT current_setting('rds.logical_replication') AS logical, current_setting('shared_preload_libraries') AS spl, current_setting('pgaudit.log') AS pgaudit;"
pgclose 15432
# 期待: Writer = logical=on / wal_level=logical / wst=0 / spl に pgaudit,pg_stat_statements / pgaudit=all
# Reader = logical=off / wal_level=replica(★これが正常)/ wst=0 / spl に pgaudit / pgaudit=all- [ ] Writer が
rds.logical_replication=on/wal_level=logical(★BG の前提はこれ) - [ ] Writer/Reader とも
shared_preload_librariesに pgaudit を含む・pgaudit.log=all・wal_sender_timeout=0(ドリフト是正の反映確認) - [ ] ★Reader は
rds.logical_replication=off/wal_level=replicaが正常(writer 専用パラメータ。reader は WAL を生成しない)。Reader 側の適用確認は pgaudit のロードとwal_sender_timeout=0で行う(mental-appointment 本番で実測 2026-08-26)
4. BG 作成前: blue で pg_stat_statements 拡張を作成
preload 済みだが拡張未作成。Green は同期中 read-only で後から作れないため BG 作成前に blue で作成。
⚠️ ★ベースラインは「拡張作成」と切り離し、BG 作成の直前に取る(dr-work-record 本番 2026-08-26 の実測で判明)。
pg_stat_statementsの統計は §3 の Writer reboot でリセットされるため、§3 直後に取得すると実質空のファイルになる。 実測では reboot 後 7分46秒の時点で取得し、21件すべてがcalls=1の検証クエリ(CREATE EXTENSION自身・verify script・api-probe)で、業務トラフィックが 1 件も入らなかった。推奨の分割:
- §3 直後:
CREATE EXTENSIONのみ実施(BG 作成前でないと Green 側で作れないため、これは早めに)- BG 作成の直前: 実トラフィックを含む期間を経てからベースラインを取得
dr-work-record は
work_recordsの書込が JST 06:50〜06:51 の日次バッチのみ(POST /work-records/bulk・毎日ではない)で、DatabaseConnectionsも終日フラット 1.5。深夜帯に取ると比較対象の SQL が存在しないため、バッチ通過後(JST 07:00 以降)に取るのが望ましい。
pgtunnel "$BLUE_EP" 15432 # blue(現primary) writer endpoint
psql -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"
psql -c "\dx pg_stat_statements" # installed_version が入っていること
# ★性能退行判定用ベースライン取得(clone 3-5・本番は実トラフィックがあるので重要。§8-6 と突合)
psql -c "\copy (SELECT queryid, query, calls, total_exec_time, mean_exec_time, rows FROM pg_stat_statements WHERE dbid=(SELECT oid FROM pg_database WHERE datname=current_database()) ORDER BY total_exec_time DESC LIMIT 50) TO '/tmp/pg_stat_baseline_dwr.csv' CSV HEADER"
pgclose 15432
# ※pgaudit は CREATE EXTENSION 不要(pgaudit.log=all のセッション監査は preload+param+reboot で有効・§3済)- [ ] blue で
pg_stat_statements拡張 installed(★BG 作成前でないと不可。PG13 では 1.8 が入る → §8 でALTER EXTENSION … UPDATEで 1.10 系へ) - [ ] Top50 SQL ベースライン
/tmp/pg_stat_baseline_dwr.csv取得(§8-6 の退行判定用) - [ ] ★ベースラインが空でないことを確認(
calls>1の業務クエリが含まれるか。SELECT now()-pg_postmaster_start_time()で統計の収集期間もあわせて記録する)。空なら BG 作成前に取り直す
5. ③ 標準 BG 作成(非-Global・target 16.14)→ Green 検証
target=16.14 の根拠: staging が 16.14 で移行済のため本番も揃える(
ValidUpgradeTargetに 16.11/16.13/16.14 が available・2026-08-25 本番実機で確認)。CPG は familyaurora-postgresql16で 16.13/16.14 共通のため変更不要。 ⚠️ 実行直前にdescribe-db-engine-versionsで再確認(16.15 等が来ていれば staging との整合も含めて採否を判断):bashaws rds describe-db-engine-versions --region $R --engine aurora-postgresql --engine-version 13.23 \ --query 'DBEngineVersions[0].ValidUpgradeTarget[?IsMajorVersionUpgrade==`true`].EngineVersion' --output json
CL_ARN=$(aws rds describe-db-clusters --region $R --db-cluster-identifier $CL --query 'DBClusters[0].DBClusterArn' --output text)
aws rds create-blue-green-deployment --region $R \
--blue-green-deployment-name dr-work-record-bg \
--source "$CL_ARN" \
--target-engine-version 16.14 \
--target-db-cluster-parameter-group-name dr-work-record-pg16 \
--target-db-parameter-group-name dr-work-record-pg16
# bgid 取得(以降で使用)
BG=$(aws rds describe-blue-green-deployments --region $R \
--query "BlueGreenDeployments[?Source=='$CL_ARN'].BlueGreenDeploymentIdentifier | [0]" --output text)
echo "BG=$BG"
# AVAILABLE 待ち
aws rds describe-blue-green-deployments --region $R --blue-green-deployment-identifier $BG \
--query 'BlueGreenDeployments[0].Status' --output text # → AVAILABLE- [ ] BG
AVAILABLE/green clusterEngineVersion=16.14
6. ★Switchover 前 GO/NO-GO(F-4-Gate・preflight.md §6-E 準拠)
# ① BG AVAILABLE / StatusDetails=null / SwitchoverDetails 各 AVAILABLE
aws rds describe-blue-green-deployments --region $R --blue-green-deployment-identifier $BG \
--query '{status:BlueGreenDeployments[0].Status,details:BlueGreenDeployments[0].StatusDetails,sw:BlueGreenDeployments[0].SwitchoverDetails[].{tgt:TargetMember,status:Status}}' --output json
# green cluster 識別子を BG(SwitchoverDetails) から取得
GREEN_CL_ARN=$(aws rds describe-blue-green-deployments --region $R --blue-green-deployment-identifier $BG \
--query "BlueGreenDeployments[0].SwitchoverDetails[?contains(TargetMember,':cluster:')].TargetMember | [0]" --output text)
GREEN_CL=${GREEN_CL_ARN##*:cluster:}; echo "GREEN_CL=$GREEN_CL"
# ② green member CPG in-sync(engine=16.14 / cpg=in-sync 期待)
aws rds describe-db-clusters --region $R --db-cluster-identifier "$GREEN_CL" \
--query 'DBClusters[0].{engine:EngineVersion,members:DBClusterMembers[].{id:DBInstanceIdentifier,cpg:DBClusterParameterGroupStatus}}' --output json
# ③ replica lag ≒ 0(blue=source と green の OldestReplicationSlotLag・直近の最大)
for C in $CL $GREEN_CL; do
aws cloudwatch get-metric-statistics --region $R --namespace AWS/RDS --metric-name OldestReplicationSlotLag \
--dimensions Name=DBClusterIdentifier,Value=$C \
--start-time "$(date -u -v-10M +%Y-%m-%dT%H:%M:%SZ)" --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 60 --statistics Maximum --query "sort_by(Datapoints,&Timestamp)[-1].Maximum" --output text | sed "s/^/$C lag=/"
done
# ④ blue 長時間tx=0(source=現 cluster endpoint へ・§0-1 の pgtunnel/BLUE_EP を使用)
pgtunnel "$BLUE_EP" 15432
psql -c "SELECT pid,state,now()-xact_start AS age,left(query,60) AS query FROM pg_stat_activity
WHERE datname=current_database() AND state<>'idle' AND xact_start IS NOT NULL AND pid<>pg_backend_pid()
ORDER BY xact_start;" # ← 0 rows 期待
BLUE_CNT=$(psql -tAc "SELECT count(*) FROM work_records;"); echo "blue work_records=$BLUE_CNT"
# ★全テーブル件数スナップショット(blue・exact count・小容量DB前提。大容量DBは n_live_tup 近似に切替)
ALLCOUNT_SQL="SELECT relname, (xpath('/row/c/text()', query_to_xml(format('SELECT count(*) c FROM %I.%I', schemaname, relname), false, true, '')))[1]::text::bigint FROM pg_stat_user_tables ORDER BY relname"
psql -tAF' ' -c "$ALLCOUNT_SQL" > /tmp/rowcounts_blue.txt
echo "blue 全テーブル件数 → /tmp/rowcounts_blue.txt ($(wc -l < /tmp/rowcounts_blue.txt) tables)"
pgclose 15432
# ★ Green 実接続検証(green cluster endpoint へ): server_version=16.14 / read_only=on / 件数が blue と一致
GREEN_EP=$(aws rds describe-db-clusters --region $R --db-cluster-identifier "$GREEN_CL" --query 'DBClusters[0].Endpoint' --output text)
pgtunnel "$GREEN_EP" 15433
psql -tAc "SELECT current_setting('server_version'), current_setting('default_transaction_read_only');" # 16.14 | on
psql -tAc "SELECT count(*) FROM work_records;" # ← $BLUE_CNT と一致すること
# ★全テーブル件数スナップショット(green)+ blue と diff(lag=0 のため一致が期待)
psql -tAF' ' -c "$ALLCOUNT_SQL" > /tmp/rowcounts_green.txt
echo "=== 全テーブル件数 diff(blue ↔ green・出力が空=完全一致)==="
diff /tmp/rowcounts_blue.txt /tmp/rowcounts_green.txt \
&& echo "✅ 全テーブル件数一致" \
|| echo "★差分あり: 上記テーブルを確認(高書込テーブルの in-flight 数件差は許容/構造的な大差は NG)"
# ⑤ green で ANALYZE(read_only を外したセッションで・切替後の性能事故防止)
# ★全テーブルを対象(clone リハ procedure-clone-rehearsal.md 6-4 と統一)。特定テーブルに絞らない。
psql -c "SET default_transaction_read_only=off; ANALYZE VERBOSE;"
pgclose 15433- [ ] AVAILABLE / StatusDetails=null / SwitchoverDetails 全 AVAILABLE / CPG in-sync / lag≒0 / 長時間tx=0
- [ ] ★Green 実接続検証(踏み台 psql・green cluster endpoint):
server_version=16.14 /work_records件数が blue と一致 /default_transaction_read_only=on - [ ] ★全テーブル件数一致:
/tmp/rowcounts_blue.txt↔/tmp/rowcounts_green.txtの diff が空(構造的な差が無いこと。高書込テーブルの in-flight 数件差は許容。※大容量DBは exact count が重いのでpg_stat_user_tables.n_live_tup近似に切替) - [ ] Retool/consumer が cluster/reader ep 接続(Switchover でインスタンス名変化に非依存)
7. ④ Switchover(★書込断 数秒・当日枠)
aws rds switchover-blue-green-deployment --region $R \
--blue-green-deployment-identifier $BG --switchover-timeout 300
# 完了ポーリング
while :; do S=$(aws rds describe-blue-green-deployments --region $R --blue-green-deployment-identifier $BG --query 'BlueGreenDeployments[0].Status' --output text); echo $S; \
[ "$S" = SWITCHOVER_COMPLETED ] && break; [ "$S" = SWITCHOVER_FAILED ] && { echo FAILED; break; }; sleep 15; done
# 確認: 実体が 16.14・旧は -old1 にリネーム
aws rds describe-db-clusters --region $R \
--query "DBClusters[?contains(DBClusterIdentifier,'dr-work-record')].{id:DBClusterIdentifier,engine:EngineVersion,status:Status}" --output json- [ ]
SWITCHOVER_COMPLETED/dr-work-record=16.14 available/dr-work-record-old1=13.23 残存(保険)
8. ⑤ 事後
# 新primary の cluster endpoint を再取得(Switchover 後・identifier は不変)
NEW_EP=$(aws rds describe-db-clusters --region $R --db-cluster-identifier $CL --query 'DBClusters[0].Endpoint' --output text)
pgtunnel "$NEW_EP" 15432
# pg_stat_statements を PG16 向けに UPDATE
psql -c "ALTER EXTENSION pg_stat_statements UPDATE;"
psql -tAc "SELECT extname||' '||extversion FROM pg_extension WHERE extname='pg_stat_statements';" # → 1.10 系
# pgaudit ドリフト是正の確認(新primary)
psql -c "SELECT name,setting FROM pg_settings WHERE name IN ('pgaudit.log','rds.logical_replication','shared_preload_libraries');" # pgaudit.log=all / logical=on / spl に pgaudit
# 8-4 更新が必要な拡張が残っていないか(installed と default が一致・0行が正・clone 8-4)
psql -c "SELECT name, default_version, installed_version FROM pg_available_extensions WHERE installed_version IS NOT NULL AND installed_version <> default_version;"
# 8-5 BG 用レプリケーション slot が消えていること(0行が正・clone 8-5)
psql -c "SELECT slot_name, slot_type, active FROM pg_replication_slots;"
# 8-6 性能退行チェック: Top SQL を取得し §4 ベースラインと突合(clone 8-6/8-7)
psql -c "\copy (SELECT queryid, query, calls, total_exec_time, mean_exec_time, rows FROM pg_stat_statements WHERE dbid=(SELECT oid FROM pg_database WHERE datname=current_database()) ORDER BY total_exec_time DESC LIMIT 50) TO '/tmp/pg_stat_after_dwr.csv' CSV HEADER"
# diff /tmp/pg_stat_baseline_dwr.csv /tmp/pg_stat_after_dwr.csv 等で mean_ms の悪化を確認。怪しいものは EXPLAIN (ANALYZE,BUFFERS) でプラン比較
pgclose 15432
# 書込/読取 probe(verify script・read + write/sequence 継続性)
bash docs/sre/scripts/pg16-verify-dr-work-record.sh --env production --label post
# API 直プローブ(fd-platform 踏み台→ALB)★SG PR #2579 マージ後のみ実行可(未マージなら curl が届かず skip)
bash docs/sre/scripts/pg16-verify-dr-work-record.sh --env production --label post --api-probe
# pgaudit 監査ログが CloudWatch に出ているか(新primary・直近5分の AUDIT 行数)
aws logs filter-log-events --region $R --log-group-name /aws/rds/cluster/$CL/postgresql \
--start-time $(( ($(date +%s) - 300) * 1000 )) --filter-pattern 'AUDIT' \
--query 'length(events)' --output text # >0 なら監査ログ出力あり- [ ]
ALTER EXTENSION … UPDATE完了/verify scriptsequence_ok=t・server_version=16.x - [ ] BG用 slot=0(
pg_replication_slots0行)/更新が必要な拡張=0(clone 8-4/8-5) - [ ] 性能退行なし:§4 ベースライン
/tmp/pg_stat_baseline_dwr.csvと Top SQL を突合(怪しいものはEXPLAIN (ANALYZE,BUFFERS)でプラン比較) - [ ] ★pgaudit ドリフト是正の確認:新 primary(PG16) で
SHOW shared_preload_libraries;に pgaudit を含む・SHOW pgaudit.log;=all・CloudWatch/aws/rds/cluster/dr-work-record/postgresqlに監査ログが出ること - [ ] アプリ E2E(在宅事業部+ストアアプリで医師勤務記録 read/write)成功/Datadog
service:dr-work-record-msエラー無し
9. ⑥ Terraform 整合(継続構成B・plan no-diff)
live 使用中の
-pg16を cluster/instance PG として継続利用(内製 family13 PG は作らない)。詳細はpreflight.md§6-C。
# 1) コード変更(別PR・継続構成B)を develop にマージ:
# main.tf の DB モジュール呼び出しを
# rds_family="aurora-postgresql16"
# cluster_parameter_group_custom_enable=false
# cluster_parameter_group_name = module.pg16_blue_green_param_groups.cluster_parameter_group_name
# instance_parameter_group_name = module.pg16_blue_green_param_groups.db_parameter_group_name
# create_instance_parameter_group=false
# + tfvars/変数 rds_engine_version=16.14、pg16_param_groups.tf から pg13_source モジュール撤去
# 2) 本番 tfstate に対して import → plan(apply は CI/手動で)
cd fastdoctor-template/dr-work-record/production
export AWS_PROFILE=production-terraform
./download-tfvar.sh
terraform init -input=false
# plan で -pg16 が add(state 未登録・AWS 実在)になる場合は先に import:
terraform import -var-file=terraform.tfvars \
'module.pg16_blue_green_param_groups.aws_rds_cluster_parameter_group.target' dr-work-record-pg16
terraform import -var-file=terraform.tfvars \
'module.pg16_blue_green_param_groups.aws_db_parameter_group.target' dr-work-record-pg16
# no-diff 確認(family13 内製PG の destroy は旧blue削除後にクリーン成功)
terraform plan -var-file=terraform.tfvars- [ ] tfvars/変数の
rds_engine_version=16.14、rds_family="aurora-postgresql16"、cluster_parameter_group_custom_enable=false、cluster/instance PG をmodule.pg16_blue_green_param_groups出力へ、create_instance_parameter_group=false - [ ]
-pg16(cluster CPG/instance PG) をterraform import(plan で add になる場合) - [ ]
max_replication_slotsは明示しない(PG16 default と同値で永続 diff 回避) - [ ]
terraform planが no-diff(family13 内製 PG の destroy は旧blue削除後にクリーンに成功)
10. ⑦ 後片付け
# BG デプロイリソース削除(--delete-target は付けない=クラスタは残す)
aws rds delete-blue-green-deployment --region $R --blue-green-deployment-identifier $BG
# 安定確認後(保険期間経過後)に旧 blue を削除
aws rds modify-db-cluster --region $R --db-cluster-identifier dr-work-record-old1 --no-deletion-protection --apply-immediately
# ★本番は Writer+Reader の 2 台 → old1 の全メンバーを削除してから cluster 削除(1台だけ消すと delete-db-cluster が失敗)
OLD_MEMBERS=$(aws rds describe-db-clusters --region $R --db-cluster-identifier dr-work-record-old1 \
--query 'DBClusterMembers[].DBInstanceIdentifier' --output text 2>/dev/null || \
aws rds describe-db-clusters --region $R --db-cluster-identifier dr-work-record-old1 \
--query 'DBClusters[0].DBClusterMembers[].DBInstanceIdentifier' --output text)
echo "old1 members = $OLD_MEMBERS" # 例: dr-work-record-1-old1 dr-work-record-0-old1
for i in $OLD_MEMBERS; do aws rds delete-db-instance --region $R --db-instance-identifier "$i" --skip-final-snapshot; done
for i in $OLD_MEMBERS; do aws rds wait db-instance-deleted --region $R --db-instance-identifier "$i"; done
aws rds delete-db-cluster --region $R --db-cluster-identifier dr-work-record-old1 \
--final-db-snapshot-identifier dr-work-record-old1-final-$(date +%Y%m%d)
# source用 CPG(dr-work-record-pg13-bg) は旧blue削除後に孤児化 → 削除
aws rds delete-db-cluster-parameter-group --region $R --db-cluster-parameter-group-name dr-work-record-pg13-bg- [ ] BG リソース削除/旧blue削除(保険期間後)/source CPG 削除/監視ダッシュボード撤去(別PR)
- [ ] (任意)de-Global 後の t4g ダウンサイズ検討(コスト最適化)
11. ロールバック
⚠️ Switchover 後の切り戻しは、切替後に新primary(16.14)へ入った書込が失われる(old1/スナップショットは切替時点で更新停止)。実行前に 判断者・連絡経路・監視復帰(mute解除) を確定し、書込停止 or 差分許容の合意を取ること。BG は一方向で「逆 Switchover」は不可=手動で consumer を戻す。
A) Switchover 前(BG 作成〜ゲートまで・まだ切替ていない)=無停止で安全
# BG を破棄すれば blue(13.23) が無傷で継続(--delete-target は付けない)
aws rds delete-blue-green-deployment --region $R --blue-green-deployment-identifier $BG
# (任意)§3 の CPG 付替/pgaudit も元に戻すなら default CPG へ戻して reboot(★瞬断)
# aws rds modify-db-cluster --region $R --db-cluster-identifier $CL \
# --db-cluster-parameter-group-name default.aurora-postgresql13 --apply-immediately
# aws rds reboot-db-instance --region $R --db-instance-identifier $WRITER && aws rds wait db-instance-available --region $R --db-instance-identifier $WRITER
# aws rds reboot-db-instance --region $R --db-instance-identifier $READER && aws rds wait db-instance-available --region $R --db-instance-identifier $READER
# (任意)§2 で外した Global を戻すなら:
# aws rds create-global-cluster --region $R --global-cluster-identifier dr-work-record --source-db-cluster-identifier "$CL_ARN"B) Switchover 後・旧blue(dr-work-record-old1) 未削除(★post-switchover 書込は喪失)
# 1) old1(13.23) の存在・エンドポイント確認
aws rds describe-db-clusters --region $R --db-cluster-identifier dr-work-record-old1 \
--query 'DBClusters[0].{engine:EngineVersion,status:Status,writer:Endpoint,reader:ReaderEndpoint}' --output json
OLD1_EP=$(aws rds describe-db-clusters --region $R --db-cluster-identifier dr-work-record-old1 --query 'DBClusters[0].Endpoint' --output text)
OLD1_RO=$(aws rds describe-db-clusters --region $R --db-cluster-identifier dr-work-record-old1 --query 'DBClusters[0].ReaderEndpoint' --output text)
echo "OLD1 writer=$OLD1_EP reader=$OLD1_RO"
# 2) consumer を old1 エンドポイントへ向け直す(★rename しても endpoint hash が変わり元 DNS は戻らない=secret 更新が必要)
# DATABASE_URL(secret) の host を $OLD1_EP に更新(他項目は現状維持):
# aws secretsmanager put-secret-value --region $R --secret-id $SECRET \
# --secret-string "$(踏み台で現 DATABASE_URL の host を $OLD1_EP に置換した JSON)"
# アプリ(ECS) を強制再デプロイして再接続 / Retool の resource Host も old1 に変更:
aws ecs update-service --region $R --cluster dr-work-record-cluster --service dr-work-record-service --force-new-deployment
# 3) 13.23(old1) に戻ったこと・アプリ疎通を確認(verify script / Datadog)C) Switchover 後・old1 も削除済(D-0 スナップショットから復元)
# D-0 スナップショットを特定($SNAP がシェルに無ければ検索)
SNAP=$(aws rds describe-db-cluster-snapshots --region $R --snapshot-type manual \
--query "reverse(sort_by(DBClusterSnapshots[?contains(DBClusterSnapshotIdentifier,'dr-work-record-pre-pg16')],&SnapshotCreateTime))[0].DBClusterSnapshotIdentifier" --output text)
echo "restore from $SNAP"
aws rds restore-db-cluster-from-snapshot --region $R \
--db-cluster-identifier dr-work-record-restore \
--snapshot-identifier "$SNAP" --engine aurora-postgresql --engine-version 13.23
aws rds create-db-instance --region $R --db-instance-identifier dr-work-record-restore-0 \
--db-cluster-identifier dr-work-record-restore --engine aurora-postgresql --db-instance-class db.r6g.large
aws rds wait db-instance-available --region $R --db-instance-identifier dr-work-record-restore-0
# → consumer を restore クラスタ endpoint へ向け直す(B-2 と同手順で secret 更新+再デプロイ)12. 接続残留時の復旧手順(§3 reboot / §7 Switchover 共通)
アプリ・Retool とも cluster endpoint 接続(DNS は再起動/切替で不変)→ 基本は自動再接続で自己回復。復旧作業が要るのは、コネクションプールが切れた接続を掴んだままエラーを出し続ける残留ケースのみ。(staging 版は
staging.md§G-2)
- 検知:reboot/Switchover 直後は一時的に接続エラーが出るが数分で消えれば正常。残るかを見る。
- Datadog
service:dr-work-record-ms(env:prd)の Errors(Can't reach database server/Connection terminated/server closed the connection/terminating connection due to administrator command) - 内部ALB
/healthCheck=200 /DatabaseConnectionsの回復(ダッシュボードdr-work-record-aurora-pg16-bluegreen)
- Datadog
- アプリ(dr-work-record-service / NestJS+Prisma)復旧:エラーが数分残る場合、ローリング再起動で新規接続を強制(アプリ層は無停止)。bash
aws ecs update-service --region $R \ --cluster dr-work-record-cluster --service dr-work-record-service --force-new-deployment- 確認:
/healthCheck=200 / verify script--api-probe200(#2610 マージ後)/ Datadog Errors 解消。
- 確認:
- Retool(self-hosted・
fd-platform-retool)復旧:基本は次のクエリ実行で自動再接続。残留なら Retool UI で対象リソースの「Test connection」/クエリ再実行 → それでも駄目なら self-hosted Retool コンテナを再起動。 - 確認:verify script/踏み台 psql で疎通、Datadog Errors 解消、
DatabaseConnections平常化。