PG16 アップグレード サービス別パラメータ:eligibility-verification
手順本体は Cloneリハーサル版 / 本番実施版 を参照(本書は固有値と実施記録のみ)。 値の出典は上記2手順書および実機確認(2026-06)。
対象サービス: eligibility-verification担当: SRE(yusaku.ishizawa) 最終更新: 2026-06-19
1. 環境・接続
| 項目 | staging | production |
|---|---|---|
AWS プロファイル(P) | staging-admin | 書き込み権限のある production プロファイル(⚠️ read-only 不可。#12905 参照) |
リージョン(R) | ap-northeast-1 | ap-northeast-1 |
| Secrets Manager Secret ID | eligibility-verification | eligibility-verification |
DB 名(psql dbname) | eligibility_verification | eligibility_verification |
| 踏み台(bastion・SSM ポートフォワード) | fd-office 踏み台(describe-instances で確認) | i-03b8c9b9fb3c4fe9a(fd-office-connection-bastion)(実機確認 2026-06-24)。SG sg-0c5ef6b0d1cf98489(fd-office-connection)が EVS DB SG sg-01a53660a7ef4826d inbound に含まれ到達可。⚠️ fd-platform 踏み台は EVS SG 不許可で不可 |
| 接続方式 | Prisma が DATABASE_URL 単一 Secret で接続(schema.prisma: url = env("DATABASE_URL"))。DB_HOST(ECS env)はアプリ未使用=変更不要。Secret 値・DB_HOST は Terraform 管理(jsonencode)のため手動変更は次の apply で上書きされる。 | 同左 |
2. クラスタ構成(アップグレード対象)
| 項目 | 値 |
|---|---|
クラスタ識別子(SOURCE_CL) | eligibility-verification |
| インスタンス構成 | Writer 1台(eligibility-verification-0)/ Reader 無し(実機確認済み・本番でも describe-db-clusters の Members で再確認) |
| インスタンスクラス | describe-db-instances の DBInstanceClass で取得(リハーサルでは source と同クラスに寄せる) |
| DB Subnet Group | eligibility-verification-subnet |
| VPC Security Group | describe-db-clusters の VpcSecurityGroups[].VpcSecurityGroupId で取得 |
| 現行エンジン | 13.20 |
| target エンジン | 16.13 |
Reader 無しのため、フェーズB の再起動は Writer 1台のみ。
3. パラメータグループ(#2164 で作成)
| 役割 | 名前 | family |
|---|---|---|
| ソース側 custom CPG(PG13) | eligibility-verification | aurora-postgresql13 |
| ターゲット側 Cluster/Instance PG(PG16) | eligibility-verification-pg16 | aurora-postgresql16 |
rds.logical_replication=1/wal_sender_timeout=0(両方・pending-reboot)shared_preload_libraries:rdsutils,pg_stat_statements(source のrdsutilsを落とさずpg_stat_statementsを追加)。PG16 ターゲット CPG にも同値を揃える。- ソース側 custom CPG は
microservice-ecsのcluster_parameter_group_custom_enable=trueで作成。ターゲット側はtemplate_modules/options/aurora-bluegreen-param-groups。 - ⚠️ 本番は CPG 未作成。staging の #2164 と同じ差分を production に展開する apply が必要(本番手順 フェーズA-1)。
4. 使用拡張・後処理の該当有無(実機確認・2026-06)
| 項目 | 該当 | 備考 |
|---|---|---|
インストール拡張(\dx) | plpgsql のみ | pg_stat_statements は別途 CREATE EXTENSION |
| pg_stat_statements | 要 | preload 済み・CREATE EXTENSION → Switchover 後に ALTER EXTENSION ... UPDATE(1.10 へ) |
| pg_cron / pg_partman(bgw) | 未使用 | 該当なし |
| pg_repack | 未使用 | 該当なし |
| Auto Scaling / Reader | なし | Writer 1台 |
→ 後処理で実際にやるのは ①拡張更新(pg_stat_statements)②(任意)pg_stat_statements リセット ③アプリエラーログ監視 の3つのみ。
5. Blue/Green 名・スナップショット名
| 項目 | 値 |
|---|---|
Blue/Green 名(BG_NAME、本番) | eligibility-verification-bg |
| Blue/Green 名(リハーサル) | eligibility-verification-bgtest |
Clone 名(リハーサル CL) | eligibility-verification-bgtest-blue |
| Switchover 前スナップショット | eligibility-verification-pre-pg16-<YYYYMMDDHHMM> |
6. 整合確認テーブル(Green/Switchover 後の件数確認)
SELECT COUNT(*) FROM _prisma_migrations;
SELECT COUNT(*) FROM ocr_results;
SELECT COUNT(*) FROM prompt_definitions;ライブ複製の非侵襲追従確認(staging-direct ②):
- high_write_table =
ocr_results、確認式 =SELECT max(id) FROM ocr_results;(id は単調増加・INSERT 専用ログのためmax(id)が適切。OCR を1件流すと +1)。Blue / Green で同値に追従=複製 OK。 - アプリ経由テストデータの後始末: OCR で入った
ocr_results行はアプリが読み返さず無害。削除する場合は staging 限定でDELETE FROM ocr_results WHERE id=<id>;(id を控えておく)。
6b. アプリ経由のライブ検証(staging 実DB 直接適用版で実施)
procedure-staging-direct.md の「アプリ経由のライブ検証」を eligibility-verification の staging 実DB で行うための固有情報。
アプリの DB 書込(コードで確定・mental-online-karte #13098 コメント 2026-06-10)
- EVS api で Aurora(Prisma) を触るのは OCR モジュールだけ。資格確認(
/v1/insurance-card)・電子処方箋は Aurora を触らない(外部APIのみ)。 - テーブルは2つ:
prompt_definitions(OCR指示文・READ/管理時のみ WRITE)/ocr_results(OCR結果・WRITE 専用ログ。アプリは読み返さない)。 - 書込トリガー: GraphQL mutation
previewInsuranceCardOcr(またはpreviewMedicalCertificateOcr)。1リクエスト=prompt_definitionsREAD +ocr_resultsに 1行 INSERT(jsonb)。 - 認証/実行:
fdc api graphql(pnpm fdc auth loginでis:Operator。トークンは自動リフレッシュ)。
アプリ経由の書込を1件起こす最小手順(staging 限定・実PII不可)
詳細・実証ログは #13098 コメント(2026-06-10)。要点のみ:
- ダミー保険証画像(「TEST/DUMMY」明記)を生成 → S3
s3://eligibility-verification-staging/evs-ocr-test/に put →aws s3 presign(1h)。 - 署名付きURL を
&/=でシェルが壊さないようファイル経由で GraphQL に埋め込み:bash# /tmp/evs_presigned_url.txt に presign URL を保存後 python3 - <<'PY' url=open('/tmp/evs_presigned_url.txt').read().strip() open('/tmp/evs_ocr_query.gql','w').write( 'mutation { previewInsuranceCardOcr(input:{ imageUrl:"%s" }) ' '{ result { ocrResult { insuranceNumber } alert } userErrors { message code } } }'%url) PY cat /tmp/evs_ocr_query.gql | pnpm --silent fdc api graphql # userErrors:[] = 成功(ocr_results に1行 INSERT) - 後始末: S3 のダミー画像と一時ファイル削除(テスト行は無害=アプリは読み返さない。消すなら staging で
DELETE FROM ocr_results WHERE id=<id>)。
検証手順(Blue/Green の各フェーズで上記 OCR を実行)
- フェーズC(同期中・Blue 無停止): アプリで OCR 1件実行 → Blue の
ocr_results最新 id を控える → Green(read-only/別トンネル)で同 id が出現=INSERT が論理レプリケーションで複製されることを確認。sql(ラグで Green 反映が一瞬遅れる点に留意。OCR は INSERT(DML) なので複製される=壊すのは DDL。)SELECT id, created_at FROM ocr_results ORDER BY id DESC LIMIT 1; -- Blue / Green 双方 - フェーズD(Switchover 前後): 切替直前に OCR 1件(ベースライン)→ 直後に OCR 1件 →
ocr_resultsが +1 されれば据え置きエンドポイントで新 PG16 にアプリ経由 read/write OK。書込断(数秒)中のアプリ挙動も観察。 - フェーズE: アプリのエラーログ監視+ OCR フロー疎通確認。
⚠️ staging 限定(本番 acct
967691968827/.fstdr.jpでは実施しない)。実PII不可。Textract+Bedrock の少額課金+ocr_results1行が発生するので後始末する。ocr_resultsは本番 OCR(個人情報含み得る)も入るため、調査時はSELECT *を避け id/件数/時刻のみ参照。
画面(fastdoctor-manager)からの接続確認 — 実証手順(2026-06-22 実施)
fdc api graphql を使わず、fastdoctor-manager の管理画面操作だけで EVS Aurora への接続・書込を確認する手順(実機検証済み)。
前提:どの画面操作が EVS Aurora を触るか(重要)
| 画面操作 | 経路 | EVS Aurora |
|---|---|---|
「オン資確認実行」ボタン(/admin/kartes/:id/eligibility_verification) | EVS の /v1/insurance-card を HTTP → 外部 OQS 照会のみ。結果は fastdoctor-manager 自身の DB(eligibility_verifications/kartes/patients)に保存 | 触らない(接続も書込も発生しない) |
| 保険証/医療証の画像アップロード(OCR) | Image 保存 → ExecuteOcrJob(非同期)→ OcrPreviewService → EVS v1/ocr/preview | ocr_results に +1(接続も発生)★これで確認する |
→ EVS Aurora の接続確認は「保険証画像の OCR」で行う(「オン資確認実行」では EVS Aurora は動かない)。Flipper ocr_from_eligibility_verification が有効なこと(OFF だと legacy Ocr::Service 経由で EVS を通らない)。
手順
- ダミー保険証画像を用意(実PII不可・「TEST/DUMMY・テスト タロウ」等を明記)。例: Pillow + ヒラギノで生成(
健康保険証のレイアウトに寄せると OCR 分類が通りやすい)。 - EVS Aurora を read-only 監視(接続: fd-office 踏み台
i-0cefcd4a25e8703e5経由 SSM。fd-platform 踏み台は EVS SG 不許可):sql-- ベースライン SELECT count(*) AS cnt, max(id) AS max_id, max(created_at) AS latest FROM ocr_results; -- 接続状況(ECS から active になるか) SELECT pid, client_addr, state, state_change FROM pg_stat_activity WHERE datname='eligibility_verification' AND usename='aK6Fr2fA' AND pid<>pg_backend_pid(); - 画面で保険証画像をアップロード。実際の画面遷移(staging・2026-06 実施):
- オンライン診療 依頼フォーム
https://contact-test.fastdoctor.jp/online-consultation/?scene=complete(Basic 認証fast/fast1)で患者情報を登録して案件を作成。 - オンライン医師差配
https://backend-stg.fstdr.jp/online_coordinator/で、作成した患者の詳細情報モーダルを開く。 - モーダルの 「保険証[未登録]」をクリック →
https://backend-stg.fstdr.jp/admin/medical_examinations/<id>/receiptの receipt 画面へ。 - その画面で保険証の写真をアップロード(=OCR トリガー)。
- 非同期ジョブ(
ExecuteOcrJob)のため、ocr_resultsへの反映は数十秒〜数分遅れることがある。
- オンライン診療 依頼フォーム
- EVS Aurora を再確認:
ocr_resultsの max_id/件数が +1、新規 ECS 接続が active→idle になれば「画面操作→EVS Aurora 書込」が成立。sqlSELECT id, created_at, (result::text ILIKE '%TEST%' OR raw_result::text ILIKE '%TARO%') AS dummy, -- ダミー判定(PII 非表示) (coalesce(alert,'')='') AS classified_ok -- alert 空=健康保険証として分類成功 FROM ocr_results WHERE id > <baseline max_id> ORDER BY id;
実測例(2026-06-22 staging): 画像アップロード → ocr_results が 4293→4294→4295 と +1 ずつ増加、dummy=t / classified_ok=t(健康保険証として分類成功・alert なし)。「オン資確認実行」では同 DB は 無変化(接続も idle のまま)であることも確認=経路の違いを実証。
注意・落とし穴
- 非同期遅延:
ExecuteOcrJobはジョブのため即時反映でないことがある(数分待って再確認)。 - 冪等スキップ:
ExecuteOcrJobはreturn if image.ocr_result.present?。同じ画像の再アップロードは EVS を呼ばない(増えないのが正常)。確認は毎回別の新規画像で。 - 分類失敗: OCR が「健康保険証/医療証」と分類できないと
alertに「…どちらでもありませんでした」が入る(行は作られるがprocessed_result={})。ダミーは保険証レイアウトに寄せる。 - staging 限定・実PII不可・テスト行は後始末(
DELETE FROM ocr_results WHERE id IN (...))。
7. Terraform 整合対象(Switchover 後)
- パス:
fastdoctor-template/eligibility-verification/production/(staging は.../staging/、PG16 CPG はpg16_param_groups.tf) rds_engine_versionを"13.20"→"16.13"へ- parameter group family を
aurora-postgresql16に、eligibility-verification-pg16参照へ terraform planに downgrade / replace(ForceNew)が出ないことを確認してから apply
8. 関連 Issue / PR
- 親チケット: mental-online-karte #12725 「[DB] eligibility-verification PostgreSQL 16系アップグレード」
- 本番手順書 Issue: mental-online-karte #13721
- staging 調査・検証の進捗: mental-online-karte #13098
- Clone リハーサル Issue: mental-online-karte #13719
- 実装メモ(カスタム PG / logical_replication / PG16 ターゲット): mental-online-karte #12905
- 対象 DB の棚卸し・方針決定: mental-online-karte #13044
- PG16 用 custom parameter group 追加(staging・MERGED): terraform_for_aws #2164
9. 実施記録
| 日付 | 環境 | フェーズ | 結果・所要・気付き |
|---|---|---|---|
| 2026-06-16〜17 | staging | Cloneリハーサル | Green 作成 約33分(Read Replica 約16分+PG16 化 約14分)。Green offline 約12分(Blue 無影響)。Switchover 書込断 約2秒。 |
| 2026-06-25〜26 | staging | staging-direct(実DB直接) | B→C→D 完走(#14063)。Switchover 書込断 約2秒(events 実測)。Green=16.13 / Blue==Green 整合 / ライブ複製OK / 拡張 1.10 / slot 0 / A-0事後 0。アプリ(画面・GraphQL 両経路)→新PG16 書込OK。 |
| 未実施 | production | 本番 | メンテ枠2つ(①CPG付替+再起動 ②Switchover)を別枠で確保予定。snapshot 名・結果は実施時に追記。 |
10. 本番実行コマンド(#14063 staging 実績ベース・production 値・AWS CloudShell 前提)
staging-direct(mental-online-karte #14063)で実行した一連のコマンドを production 値に置換し、AWS CloudShell から実行する前提でまとめたもの。手順本体の解説は procedure-production.md を正とし、本節は「そのまま流せる具体コマンド」。 ⚠️ 本番は フェーズB(メンテ枠①)と フェーズD(メンテ枠②)を別枠で実施(必須)。周知は
#on本部_release-ops+#fdtech-general。
10-0. CloudShell 前提・共通変数
- CloudShell を production アカウント(967691968827)のコンソールで開く。コマンドに
--profileは付けない(CloudShell はログイン中コンソール ID の認証情報を使う)。--regionは明示的に付ける(既定リージョンに依存しない)。 - ⚠️ 実行ロールに書き込み権限が必要:
rds:ModifyDBCluster/RebootDBInstance/CreateDBClusterSnapshot/SwitchoverBlueGreenDeployment、secretsmanager:GetSecretValue、ssm:StartSession。read-only ロールでは不可。 - psql を別途インストール:
sudo dnf install -y postgresql16(CloudShell は Amazon Linux)。 - ポートフォワードと psql は CloudShell の別タブ(Actions → New tab。同一環境で
localhost共有)。\copyは1行で書く(折り返し不可)、CSV は$HOME配下に出力して「Actions → Download file」で取得。 - 後始末(毎回・必須): psql は
\qで抜けたらunset CREDS PGUSER PGPASSWORD(資格情報を環境変数に残さない)。port-forward タブは Ctrl-C で終了。ダウンロードした CSV 等の機微ファイルは作業後に削除し、共有範囲を限定する。 - ⚠️ read-only 確認用と「DDL/ANALYZE 実行用」のセッションを分ける:
- 参照確認だけのセッション →
SET default_transaction_read_only = on;で固定(事故防止)。 CREATE EXTENSION(B-5)/ALTER EXTENSION ... UPDATE(D-5)/ANALYZE(C-3)を行うセッションではdefault_transaction_read_only = onを設定しない。read-only のままだとcannot execute ... in a read-only transactionで失敗する。安全確認の癖で read-only を入れたまま DDL すると失敗するので注意。
- 参照確認だけのセッション →
# 認証確認(Account=967691968827・書き込みロールであること)
aws sts get-caller-identity --query '{Account:Account,Arn:Arn}' --output json
export R=ap-northeast-1
export SOURCE_CL=eligibility-verification
export BG_NAME=eligibility-verification-bg
export BASTION=i-03b8c9b9fb3c4fe9a # fd-office-connection-bastion(production)
export DB_HOST=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
--query "DBClusters[0].Endpoint" --output text --region $R) # ≒ eligibility-verification.cluster-cvwvyuc2cloj.ap-northeast-1.rds.amazonaws.com前提: A-1(Terraform で本番に CPG 作成)= terraform_for_aws #2253 を apply 済みであること(
eligibility-verification/eligibility-verification-pg16が存在)。
10-T. タイムスケジュール例(準備 4時間 + 正式メンテ枠 1時間)
「A-3 ベースライン取得 〜 D-2 直前」までの準備フェーズを約4時間(Blue は無停止/書込断は Switchover の数秒のみ=サービス停止ではない)、D-2 Switchover 以降を正式なダウンタイム(サービス停止)1時間枠として設計する例。 ⚠️ フェーズB(CPG 付替+再起動=瞬断)は別の事前メンテ枠で実施済みであること(この 4h/1h とは別枠)。A-3 ベースラインは B-5 で拡張作成後、事前に通常トラフィックを蓄積済みである前提。
準備フェーズ(約4時間・Blue 無停止・枠外でも可)
| 経過 | 作業 | 備考 |
|---|---|---|
| T-4:00 | A-3 ベースライン取得(Top SQL+CSV・Blue/PG13) | read-only。トラフィック蓄積済み前提 |
| T-3:55 | C-1 BG 可否ゲート確認(available / Global 非所属 / 既存BGなし / 16.13可) | |
| T-3:50 | C-2 BG 作成 → AVAILABLE 待ち(実測 約31〜33分) | Green のみ offline・Blue 無停止 |
| T-3:10 | C-3 Green 検証(server_version=16.13 / 件数整合 / ANALYZE)+ ライブ複製確認 | |
| T-1:00 | C-4 DDL/migration 凍結の最終確認 | 同期中は Blue へ DDL を流さない |
| T-0:30 | D-0 最終ゲート(AVAILABLE / Details=null / SwitchoverDetails 各 AVAILABLE)+ レプリラグ0 + 長時間tx0 | |
| T-0:20 | D-1 手動スナップショット取得 → available(数分・ロールバック保険) | |
| T-0:05 | D-1.5 最終ゲート再確認・GO/NO-GO 判断 | NG なら枠を開かず延期 |
正式メンテ枠(T0〜T+1:00・サービス停止扱い・周知済み)
| 経過 | 作業 | 備考 |
|---|---|---|
| T0 | D-2 Switchover 実行(書込断 実測 約2秒) | |
| T+0:02 | D-3 完了・リネーム確認(SWITCHOVER_COMPLETED / Renamed) | |
| T+0:05 | D-4 PG16 疎通・アプリ再接続・主要機能(OCR write)確認 | port-forward 張り直し(原名→新PG16) |
| T+0:15 | D-5 拡張更新(ALTER EXTENSION UPDATE)/ slot 確認 / A-0事後(-old1 残留 0) | |
| T+0:20〜T+1:00 | 監視バッファ(40分): エラー率・レイテンシ・DB接続・主要機能を監視。ロールバック判断者待機 | 異常時は #12725 §13 基準で判断(復元元 $SNAP) |
- 実際の書込断は Switchover の数秒だが、再接続・DNS 伝播・事後確認・ロールバック余地を含め1時間を「サービス停止」枠として周知する(安全マージン)。
- 準備4時間の大半は BG 作成(約33分)+検証+snapshot+ゲートで、Blue は稼働継続=この間はサービス停止ではない。
10-B. フェーズB(CPG 付替 → 再起動=瞬断・メンテ枠①)
# B-0 付け替え前の確認(4点)
# ① 付け替える CPG の中身が期待値か(rds.logical_replication=1 / wal_sender_timeout=0 /
# shared_preload_libraries に pg_stat_statements を含む。⚠️ shared_preload_libraries は上書き型=
# 本番の現値 rdsutils,pg_stat_statements の既存 preload を落としていないこと。instance では rdsutils が自動付与される)
aws rds describe-db-cluster-parameters --db-cluster-parameter-group-name eligibility-verification \
--query "Parameters[?ParameterName=='rds.logical_replication'||ParameterName=='wal_sender_timeout'||ParameterName=='shared_preload_libraries'].[ParameterName,ParameterValue,ApplyMethod]" --output table --region $R
# ② 現在クラスタに紐づく CPG・状態を控える(Status=available / Engine=13.20 / 全 Members PGStatus=in-sync)
# ⚠️ 付け替え前から pending-reboot が残っていたら別の未反映変更が混在の疑い → 一旦中止して調査
aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
--query "DBClusters[0].{Cluster:DBClusterIdentifier,Engine:EngineVersion,Status:Status,CurrentCPG:DBClusterParameterGroup,Members:DBClusterMembers[].{Instance:DBInstanceIdentifier,Writer:IsClusterWriter,PGStatus:DBClusterParameterGroupStatus}}" --output json --region $R
# ③ Reader 有無=再起動対象の確認(eligibility は Writer 1台。Reader ありなら B-3 は Reader→Writer の順)
# ④ 外部CDC(Datastream 等)が無いことの確認(付け替え=logical on の前に「クリーンな初期状態」を確認)
# ④-a AWS: DB SG inbound 5432 のソース(アプリ+踏み台のみが正・外部ツール用の許可が無いこと)
DB_SG=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
--query "DBClusters[0].VpcSecurityGroups[0].VpcSecurityGroupId" --output text --region $R)
aws ec2 describe-security-groups --group-ids "$DB_SG" --region $R \
--query "SecurityGroups[0].IpPermissions[?FromPort==\`5432\`].[UserIdGroupPairs[].GroupId,IpRanges[].CidrIp]" --output json
# ④-b DB内部: port-forward(B-5 の手順で localPort 5432)+ Secret 取得後、read-only で確認
# ※logical off の今は slot は構造的に 0。publication は定義として残り得るので要確認
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require" \
-c "SET default_transaction_read_only=on;" \
-c "SELECT slot_name, plugin, slot_type, active FROM pg_replication_slots;" \
-c "SELECT pubname, puballtables FROM pg_publication;" \
-c "SELECT application_name, state FROM pg_stat_replication;"
# 期待: いずれも 0行。⚠️ 外部 logical slot/publication があると後続の BG 作成が "external replication" で失敗(最終ゲートは C-1 で再確認)
# ※この psql -c は独立した接続で、`SET default_transaction_read_only=on` も接続終了で消滅する。
# よって B-5 の CREATE EXTENSION(別セッション・read-only にしない)には波及しない(接続ごとに独立)。
# 同一対話セッションで続けて DDL する場合のみ、事前に `SET default_transaction_read_only=off;` に戻すこと。
# B-1 付け替え(無停止・反映は再起動時)
aws rds modify-db-cluster --db-cluster-identifier $SOURCE_CL \
--db-cluster-parameter-group-name eligibility-verification --apply-immediately --region $R
# B-2 pending-reboot 確認
aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
--query 'DBClusters[0].DBClusterMembers[].{Instance:DBInstanceIdentifier,Writer:IsClusterWriter,PGStatus:DBClusterParameterGroupStatus}' --output table --region $R
# B-3 再起動(Reader→Writer。eligibility は Writer 1台)=瞬断
READER_IDS=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
--query "DBClusters[0].DBClusterMembers[?IsClusterWriter==\`false\`].DBInstanceIdentifier[]" --output text --region $R)
for RID in $READER_IDS; do aws rds reboot-db-instance --db-instance-identifier "$RID" --region $R; aws rds wait db-instance-available --db-instance-identifier "$RID" --region $R; done
WRITER_ID=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
--query "DBClusters[0].DBClusterMembers[?IsClusterWriter==\`true\`].DBInstanceIdentifier | [0]" --output text --region $R)
aws rds reboot-db-instance --db-instance-identifier "$WRITER_ID" --region $R
aws rds wait db-instance-available --db-instance-identifier "$WRITER_ID" --region $R
# B-4 in-sync 確認(上の describe を再実行)B-5 接続 → logical 確認 → 拡張作成(タブ1 で port-forward、タブ2 で psql):
# タブ1(開けたまま)
aws ssm start-session --target $BASTION --document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters "{\"host\":[\"$DB_HOST\"],\"portNumber\":[\"5432\"],\"localPortNumber\":[\"5432\"]}" --region $R
# タブ2
CREDS=$(aws secretsmanager get-secret-value --secret-id eligibility-verification --query SecretString --output text --region $R)
export PGUSER=$(echo "$CREDS" | python3 -c "import sys,json;print(json.load(sys.stdin)['DB_USERNAME'])")
export PGPASSWORD=$(echo "$CREDS" | python3 -c "import sys,json;print(json.load(sys.stdin)['DB_PASSWORD'])")
unset CREDS
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require"
# ↓ psql を抜けたら(\q)必ず資格情報を環境から消す(毎回・必須)
unset CREDS PGUSER PGPASSWORD
# port-forward(タブ1)は作業終了後 Ctrl-C で閉じるSHOW rds.logical_replication; -- on
SHOW wal_level; -- logical
SHOW wal_sender_timeout; -- 0
-- ※このセッションは read-only に固定しない(DDL のため)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements; -- BG 作成前に(writable なうち)
\dx pg_stat_statements→ on/logical/0 が揃わなければ フェーズC へ進まない。
A-3 ベースライン(Top SQL 抽出+CSV化): CREATE EXTENSION 後、通常トラフィックを一定期間蓄積してから取得(作成直後は統計が空=意味なし)。PG13 のカラムは total_exec_time/mean_exec_time。
画面表示(psql 内):
SELECT queryid, LEFT(query,100) AS query_preview, calls,
round(total_exec_time::numeric,2) AS total_ms, round(mean_exec_time::numeric,2) AS mean_ms, rows
FROM pg_stat_statements
WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
ORDER BY total_exec_time DESC LIMIT 50;CSV 化(CloudShell ではシェルから psql -c を正とする = $HOME が確実に展開され、\copy の折り返しも回避できる。port-forward 接続中・別タブで1行):
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require" \
-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 '$HOME/pg_stat_baseline.csv' CSV HEADER"⚠️ psql 対話内で
\copyする場合は$HOMEが展開されないため、絶対パスを明示する(例/home/cloudshell-user/pg_stat_baseline.csv)。確実なのは上記のシェルpsql -c。 ⚠️ CSV の取り扱い注意:pg_stat_statements.queryは通常は正規化 SQL だが、クエリによっては業務上見せたくない情報を含み得る。保管先・共有範囲を限定し、不要になったら削除する(CloudShell のファイルも作業後に削除)。 取得した CSV は フェーズE(事後)の Top SQL と突き合わせて性能退行を判断(queryid はアップグレードで変わるため、クエリ本文・呼出回数・mean/total 時間で比較)。
10-C. フェーズC(Blue/Green 作成・Blue 無停止)
# C-1 可否ゲート(available / Global 非所属 / 既存BGなし / 16.13可)→ procedure-production A-2 参照
# ★外部CDC 最終確認(port-forward=localPort 5432 + Secret 取得後・read-only。BG 作成直前)
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require" \
-c "SET default_transaction_read_only=on;" \
-c "SELECT slot_name, plugin, slot_type, active FROM pg_replication_slots;" \
-c "SELECT pubname, puballtables FROM pg_publication;" \
-c "SELECT application_name, state FROM pg_stat_replication;"
# 期待: BG 用以外は 0 行(外部 logical slot/publication があると create-blue-green-deployment が "external replication" で失敗)
# C-2 BG 作成
SRC_ARN=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL --query 'DBClusters[0].DBClusterArn' --output text --region $R)
aws rds create-blue-green-deployment --blue-green-deployment-name $BG_NAME --source "$SRC_ARN" \
--target-engine-version 16.13 --target-db-cluster-parameter-group-name eligibility-verification-pg16 \
--target-db-parameter-group-name eligibility-verification-pg16 --region $R
export BG=<返ってきた bgd-xxxx>
# AVAILABLE 待ち(実測 約31〜33分。Green のみ offline・Blue 無停止)
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
--query "BlueGreenDeployments[0].{Status:Status,Details:StatusDetails,Tasks:Tasks[].{n:Name,s:Status}}" --output json --region $RC-3 Green 検証(Green エンドポイントへ別タブ・別ポート 15432 で port-forward → psql):
GREEN_ARN=$(aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG --query "BlueGreenDeployments[0].Target" --output text --region $R)
GREEN_DB_HOST=$(aws rds describe-db-clusters --db-cluster-identifier "$GREEN_ARN" --query "DBClusters[0].Endpoint" --output text --region $R)
aws ssm start-session --target $BASTION --document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters "{\"host\":[\"$GREEN_DB_HOST\"],\"portNumber\":[\"5432\"],\"localPortNumber\":[\"15432\"]}" --region $RSHOW server_version; -- 16.13
SHOW shared_preload_libraries; -- rds_blue_green / writeforward を含む=Green の証拠
SELECT COUNT(*) FROM ocr_results; -- Blue と一致
ANALYZE VERBOSE; -- 切替直後の性能事故防止(※read-only 固定しないセッションで。ANALYZE は統計更新=read-only 不可)ライブ複製確認: アプリで OCR 1件 → Blue の max(id) を控える → Green で同 id 出現で複製OK。C-4: 同期中は Blue へ DDL/migration を流さない。
10-D. フェーズD(Switchover・メンテ枠②)
# D-0 最終ゲート(AVAILABLE / Details=null / SwitchoverDetails 各 AVAILABLE)
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
--query "BlueGreenDeployments[0].{Status:Status,Details:StatusDetails,Sw:SwitchoverDetails[].Status}" --output json --region $R
# レプリラグ=0(Blue writer の slot): SQL でも確認可
# SELECT slot_name,active,pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),confirmed_flush_lsn)) FROM pg_replication_slots; -- 0 bytes
# 長時間tx=0: Blue(5432) で pg_stat_activity 確認
# D-1 手動スナップショット(Blue=PG13・ロールバック保険)→ available 待ち
SNAP=eligibility-verification-pre-pg16-$(date +%Y%m%d%H%M)
aws rds create-db-cluster-snapshot --db-cluster-identifier $SOURCE_CL --db-cluster-snapshot-identifier "$SNAP" --region $R
aws rds wait db-cluster-snapshot-available --db-cluster-snapshot-identifier "$SNAP" --region $R
# D-1.5 snapshot available 後・Switchover 直前に最終ゲートを再確認(snapshot 待ちで数分経つため)
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
--query "BlueGreenDeployments[0].{Status:Status,Details:StatusDetails,Sw:SwitchoverDetails[].Status}" --output json --region $R
# 期待: Status=AVAILABLE / Details=null / SwitchoverDetails 各 AVAILABLE(崩れていたら Switchover しない)
# D-2 Switchover(書込断 数秒)
aws rds switchover-blue-green-deployment --blue-green-deployment-identifier $BG --switchover-timeout 300 --region $R
# D-3 完了・リネーム確認
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG --query "BlueGreenDeployments[0].Status" --output text --region $R # SWITCHOVER_COMPLETED
aws rds describe-events --source-type db-cluster --source-identifier $SOURCE_CL --duration 30 --query "Events[].[Date,Message]" --output table --region $RD-4 PG16 疎通(Switchover 後はエンドポイント名が新PG16を指す。port-forward を張り直して 5432 で接続)→ SHOW server_version;=16.13 / SELECT count(*) FROM ocr_results; 整合 / アプリ再接続・OCR 書込確認。 D-5 拡張更新・slot 確認(新PG16・writable):
-- ※このセッションは read-only に固定しない(DDL のため)
ALTER EXTENSION pg_stat_statements UPDATE; -- 1.10
SELECT name, installed_version, default_version FROM pg_available_extensions WHERE installed_version IS NOT NULL AND installed_version <> default_version; -- 0行
SELECT slot_name, slot_type, active FROM pg_replication_slots; -- eligibility は 0行が正A-0 事後(旧 Blue -old1 の残留接続。別タブ・別ポート 15433 で接続):
OLD_HOST=$(aws rds describe-db-clusters --db-cluster-identifier ${SOURCE_CL}-old1 --query "DBClusters[0].Endpoint" --output text --region $R)
# port-forward(15433) → psql で:
# SELECT usename,application_name,client_addr,count(*) FROM pg_stat_activity WHERE pid<>pg_backend_pid() AND client_addr IS NOT NULL GROUP BY 1,2,3; -- 0行が理想10-E. フェーズE(事後)
- 性能監視(実トラフィック最低3日・A-3 ベースライン比較)。
- Terraform 整合(
production/main.tfのrds_engine_version=16.13/rds_family=aurora-postgresql16)。staging の実績 PR: terraform_for_aws #2263。apply は-target=module.microservice-ecs.module.db等でドリフト巻き込みを回避。 - 旧
eligibility-verification-old1(PG13)は数日安定確認後に削除。 - 後始末 BG:
delete-blue-green-deployment(Switchover 後は--delete-targetを付けない)。
⚠️ 本番固有の注意: メンテ枠①②を別枠(必須)/周知
#on本部_release-ops+#fdtech-general(3〜5営業日前+前日+当日)/監視 mute/DNS TTL≤5s/ロールバック元$SNAP。logical_replicationを移行後に off へ戻す等の値見直しは #13044。