Skip to content

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-role
terraformproduction-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=1wal_sender_timeout=0shared_preload_libraries=pgaudit,pg_stat_statementspgaudit.log=all を含むpreflight.md §3・pgaudit ドリフト是正)
  • [ ] ★pgaudit を有効化する:本番は現状 default PG で pgaudit 未適用(TF からドリフト)。本移行で pgaudit.log=all を有効化(他サービスは既に有効で整合的・ドリフト是正)。監査ログ量が増える点は関係者へ周知しておく。
  • [ ] メンテ枠・関係者(在宅事業部=医師アプリ実機E2E)・ロールバック担当 待機
  • [ ] 監視 mute / DNS TTL≤5s
bash
# 変数(本番・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 json

0-1. 踏み台 psql ヘルパ(本手順の psql 系で共通利用・最初に読み込む)

SSM port-forward + Secret 取得を関数化。pgtunnel <db-host> <local-port> 後に psql が使える。終了は pgclose <local-port>

bash
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 保険スナップショット(ロールバック起点)

bash
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 化・無停止想定)

bash
# 削除前: 単一リージョン(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 で関係者へ周知済であること。

bash
# 本番は 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
  • [ ] Writerrds.logical_replication=on / wal_level=logical(★BG の前提はこれ)
  • [ ] Writer/Reader とも shared_preload_librariespgaudit を含む・pgaudit.log=allwal_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 件も入らなかった。

推奨の分割:

  1. §3 直後: CREATE EXTENSION のみ実施(BG 作成前でないと Green 側で作れないため、これは早めに)
  2. BG 作成の直前: 実トラフィックを含む期間を経てからベースラインを取得

dr-work-record は work_records の書込が JST 06:50〜06:51 の日次バッチのみPOST /work-records/bulk・毎日ではない)で、DatabaseConnections も終日フラット 1.5。深夜帯に取ると比較対象の SQL が存在しないため、バッチ通過後(JST 07:00 以降)に取るのが望ましい。

bash
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 は family aurora-postgresql16 で 16.13/16.14 共通のため変更不要。 ⚠️ 実行直前に describe-db-engine-versions で再確認(16.15 等が来ていれば staging との整合も含めて採否を判断):

bash
aws rds describe-db-engine-versions --region $R --engine aurora-postgresql --engine-version 13.23 \
  --query 'DBEngineVersions[0].ValidUpgradeTarget[?IsMajorVersionUpgrade==`true`].EngineVersion' --output json
bash
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 cluster EngineVersion=16.14

6. ★Switchover 前 GO/NO-GO(F-4-Gate・preflight.md §6-E 準拠)

bash
# ① 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(★書込断 数秒・当日枠)

bash
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_COMPLETEDdr-work-record=16.14 available/dr-work-record-old1=13.23 残存(保険)

8. ⑤ 事後

bash
# 新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 script sequence_ok=tserver_version=16.x
  • [ ] BG用 slot=0pg_replication_slots 0行)/更新が必要な拡張=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;=allCloudWatch /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

bash
# 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 planno-diff(family13 内製 PG の destroy は旧blue削除後にクリーンに成功)

10. ⑦ 後片付け

bash
# 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 作成〜ゲートまで・まだ切替ていない)=無停止で安全

bash
# 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 書込は喪失)

bash
# 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 スナップショットから復元)

bash
# 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

  1. 検知: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
  2. アプリ(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-probe 200(#2610 マージ後)/ Datadog Errors 解消。
  3. Retool(self-hosted・fd-platform-retool)復旧:基本は次のクエリ実行で自動再接続。残留なら Retool UI で対象リソースの「Test connection」/クエリ再実行 → それでも駄目なら self-hosted Retool コンテナを再起動
  4. 確認:verify script/踏み台 psql で疎通、Datadog Errors 解消、DatabaseConnections 平常化。