PG16 アップグレード preflight・リファレンス:eligibility-verification(EVS)
📖 本書はリファレンス(なぜ・調査結果・固有値・設計判断・ゲート根拠)。 手順(どうやる)は →
clone-rehearsal.md/staging.md/production.md。 汎用手順の背景は../../procedure-clone-rehearsal.md/../../procedure-production.md。
対象サービス: eligibility-verification(オンライン資格確認・電子処方箋・保険証 OCR の MS) 担当: SRE(yusaku.ishizawa) 最終更新: 2026-08-19(★実機再確認: production / staging 両アカウント)
0. 現状サマリ(★2026-08-19 実機確認)
| 環境 | エンジン | Cluster PG | 状態 |
|---|---|---|---|
| staging | 16.13 | eligibility-verification-pg16 | ✅ 移行完了(2026-06-25〜26 の staging-direct で Switchover 済・#14063) |
| production | 13.23 | eligibility-verification(custom PG13・in-sync) | ⏳ 未実施=残タスク |
⚠️ 旧 services/eligibility-verification.md からの重要な訂正(実機確認で判明)
| 旧記載 | 実際 |
|---|---|
現行エンジン 13.20 | 13.23(2026-08-19 実機・Terraform main.tf も 13.23) |
| 「本番は CPG 未作成」 | 両 CPG とも作成済:eligibility-verification(PG13 custom)/ eligibility-verification-pg16(PG16 target)。cluster/instance PG の両方が存在(2026-08-19 実機) |
| 「フェーズB(CPG 付替+再起動)が必要」 | ✅ 2026-06-30 に実施済み(#14065 に全コマンド・出力の実行ログあり)。SHOW rds.logical_replication=on / wal_level=logical / wal_sender_timeout=0、CREATE EXTENSION pg_stat_statements も完了。再起動後にアプリが自動再接続し ocr_results が 735,918→735,919 と増加=write 健全も確認済み |
★★ 残っているのは「フェーズC(BG 作成)→ フェーズD(Switchover)」だけ
フェーズB が済んでいるため、本番は Switchover の 1 枠のみで完了する(当初計画の 2 枠から 1 枠に削減できる)。 瞬断を伴う事前メンテ枠は不要。
⚠️ ただし BG 作成の直前に §3-2 の
SHOWを1回だけ再確認すること。フェーズB から2ヶ月弱経っており、 その間の別作業で CPG が戻される可能性はゼロではない(コストは数十秒・production.md§2)。
🔴 ただし BG 作成前に解消すべきブロッカーが 1 件ある
本番 EVS DB に未適用の migration が 1 本ある(
_prisma_migrations= 2 に対し app repo は 3 本)。 本番デプロイのたびにprisma migrate deployが走るため、BG 同期中にデプロイされるとCREATE TABLEが Blue にだけ流れ、Switchover 後の新 primary に存在せずアプリが落ちる。 → 詳細と対処は §5-2。BG 作成前に必ず解消/合意すること。
0-1. アーキテクチャ(誰が Aurora を触るか・2026-08 時点)
図から読み取るべき3点
- Aurora を触るのは OCR 経路だけ — 「オン資確認実行」(
/v1/insurance-card)と電子処方箋は外部 API を呼ぶだけで Aurora に接続すらしない(#13098 でコード確定)。疎通確認の経路を間違えやすい最大のポイント(§6-1)。 - アプリは cluster endpoint に接続 — Switchover でエンドポイント名は据え置かれるため Secret / DB_HOST / ECS の再設定は不要。必要なのは接続の張り直しだけ(§6)。
- 踏み台は DB には届くが ALB には届かない — DB SG は fd-office 踏み台を許可するが、 ALB SG は prefix list のみ許可。だから HTTP 経由の疎通確認は成立せず、 判定は DB 側(
pg_stat_activity)から行う(§6-2 / §11)。
1. 環境・接続(実機確認 2026-08-19)
| 項目 | staging | production |
|---|---|---|
| AWS アカウント | 301608970378 | 967691968827 |
| AWS プロファイル | staging-admin | 参照=production(read-only) / 変更系=production-admin(⚠️ read-only では BG 作成・Switchover 不可。#12905) |
リージョン(R) | ap-northeast-1 | ap-northeast-1 ★本番も東京(us-east-1 の dr-work-record / mental-appointment とは異なる) |
| Secrets Manager Secret ID | eligibility-verification | eligibility-verification |
DB 名(psql dbname) | eligibility_verification | eligibility_verification |
| クラスタ endpoint | eligibility-verification.cluster-ctb7v7xkimcr.ap-northeast-1.rds.amazonaws.com | eligibility-verification.cluster-cvwvyuc2cloj.ap-northeast-1.rds.amazonaws.com |
| DB Security Group | sg-0ab38c77bb380fda7 | sg-01a53660a7ef4826d |
| 踏み台(★fd-office 系のみ) | i-0cefcd4a25e8703e5(fd-office-connection・SG sg-0cf2a96d7429ac09e) | i-03b8c9b9fb3c4fe9a(fd-office-connection-bastion・SG sg-0c5ef6b0d1cf98489) |
| 接続方式 | Prisma が DATABASE_URL 単一 Secret で接続(schema.prisma: url = env("DATABASE_URL"))。DB_HOST(ECS env)はアプリ未使用=変更不要。Secret 値・DB_HOST は Terraform 管理(jsonencode)のため手動変更は次の apply で上書きされる | 同左 |
⚠️ 踏み台は fd-office 系でないと届かない
EVS の DB SG の 5432 inbound は 2つの SG のみ(実機確認):
- production
sg-01a53660a7ef4826d←sg-03ef3659aca4c210a(EVS ECS)/sg-0c5ef6b0d1cf98489(fd-office-connection-bastion)- staging
sg-0ab38c77bb380fda7←sg-0a4c49925d69da1b1(EVS ECS)/sg-0cf2a96d7429ac09e(fd-office-connection)他サービスで常用する fd-platform 踏み台(
i-09384db1d690bcfd0等)は inbound に含まれず到達不可。 誤った踏み台を指定すると port-forward が張れても 5432 に届かず、原因究明で時間を溶かす。代替踏み台(production):
i-0a96515fe2956f506(fd-office-connection)も同じ SG を持ち到達可(#14064)。 ⚠️i-0cefcd4a25e8703e5は staging 専用。旧手順書はこれを本番用として記載していたが、 本番アカウント(967691968827)には存在しない(#14064 で判明・実機でInvalidInstanceID.NotFoundを確認)。
⚠️ 参照専用ロールでは DB に接続できない(#13926 / #14064)
production の read-only ロールは
secretsmanager:GetSecretValueとssm:StartSessionを持たない。 つまり--profile productionでは DB パスワードの取得もポートフォワードもできず、DB 内部の確認が一切できない。
describe-*(RDS/EC2/ECS/CloudWatch)=production(read-only)で可- DB 内部の確認(psql)=
production-adminが必要(verify script もproduction-adminを使う)この制約により、2026-06-23 時点では本番の
\dx/pg_stat_activityが確認できず preflight が止まっていた。 権限手当て後の 2026-06-24 にまとめて実施している(§4-1)。
2. クラスタ構成(アップグレード対象=production)
| 項目 | 値(2026-08-19 実機確認) |
|---|---|
クラスタ識別子(SOURCE_CL) | eligibility-verification |
| インスタンス構成 | Writer 1台(eligibility-verification-0)/ Reader 無し |
| インスタンスクラス | db.t3.medium(★バースタブル・2 vCPU) |
| Performance Insights | 無効(→ DBLoad メトリクスは emit されない) |
| DB Subnet Group | eligibility-verification-subnet |
| Global Database | 非所属(GlobalClusterIdentifier=null)→ Global 削除の手順は不要(dr-work-record / mental-appointment との最大の違い) |
| バックアップ保持 | 7 日 |
| 現行エンジン | 13.23 |
| target エンジン | 16.13(staging の実績と揃える。16.14 も ValidUpgradeTarget にあるが staging 未検証のため採らない) |
ValidUpgradeTarget(13.23 から・2026-08-19 実測): 14.20/14.22/14.23 / 15.15/15.17/15.18 / **16.11/16.13/16.14** / 17.7/17.9/17.10 / 18.3/18.4 → 13.23 → 16.13 は直接メジャーアップ可能(中間バージョンを挟む必要なし)。
- Reader 無し=再起動が要る場合も Writer 1台のみ(Reader→Writer の順序考慮は不要)。
- 非-Global = 標準 BG をそのまま作れる(Global 削除→standalone 化の前段が不要)。
★2-1. db.t3.medium(バースタブル)であることの含意
BG の Green 構築(初期コピー)+論理レプリケーション適用は CPU を継続的に使う。t3 は CPU クレジットを 使い切るとベースライン性能(20%)へスロットリングされるため、
- Green 構築が想定より大幅に長引く
- Switchover 前に レプリラグが 0 へ収束しない(= GO 条件を満たせず枠を空振り)
という失敗が起こりうる。BG 実行中は CPUCreditBalance を最重要監視項目として見る(§10 のダッシュボード)。 db.t3.medium のクレジット上限は 576、ベースラインは 20%/vCPU。
3. パラメータグループ
| 役割 | 名前 | family | 存在(2026-08-19 production) |
|---|---|---|---|
| ソース側 custom CPG(PG13・cluster) | eligibility-verification | aurora-postgresql13 | ✅ あり・クラスタに付与済(in-sync) |
| ソース側 instance PG(PG13) | eligibility-verification | aurora-postgresql13 | ✅ あり(user param なし) |
| ターゲット側 cluster PG(PG16) | eligibility-verification-pg16 | aurora-postgresql16 | ✅ あり |
| ターゲット側 instance PG(PG16) | eligibility-verification-pg16 | aurora-postgresql16 | ✅ あり(user param なし) |
user param(両 cluster PG 共通・実測)
| パラメータ | 値 | ApplyMethod | 型 |
|---|---|---|---|
rds.logical_replication | 1 | pending-reboot | static(反映に再起動が必要) |
wal_sender_timeout | 0 | pending-reboot | dynamic |
⚠️
ApplyMethod=pending-rebootは「この値をどう反映させるか」の記録であって「未反映」を意味しない。 2026-06-30 の再起動で反映済み(#14065)。実際の適用状態は DB 直のSHOWが正(§3-2)。
shared_preload_libraries は pg_stat_statements(Source=system) = Aurora PG13 の既定値と一致するため user param として現れない(#14065 の実行ログでは付替前の CPG 参照時に user param として表示されている)。Terraform(main.tf)でも同値を宣言しておりドリフトではない。 pgaudit を preload する dr-work-record と違い、EVS は pgaudit 不使用=target CPG も同値で整合が取れている。
- Terraform: source 側 custom CPG は
microservice-ecsのcluster_parameter_group_custom_enable=true(main.tf)、 target 側はpg16_param_groups.tf(template_modules/options/aurora-bluegreen-param-groups)。 - 関連 PR: staging #2164 / production #2253(いずれも MERGED・実機に反映済み)。
★3-2. BG 作成直前の再確認(済んでいる前提だが、数十秒なので必ず1回やる)
フェーズB は 2026-06-30 に完了済み(#14065)。describe-db-clusters の Writer メンバー DBClusterParameterGroupStatus も in-sync(2026-08-19 実機)。それでも確証は DB 直の SHOW でしか取れないため、 BG 作成前に踏み台経由 psql で以下を確認する(read-only・production.md §2):
SHOW rds.logical_replication; -- 期待: on
SHOW wal_level; -- 期待: logical
SHOW wal_sender_timeout; -- 期待: 0
SHOW shared_preload_libraries; -- 期待: pg_stat_statements を含む
\dx pg_stat_statements -- 拡張が作成済みか(未作成なら BG 作成前に CREATE EXTENSION)- 3つとも期待値(想定どおり) → そのまま BG 作成へ。メンテ枠は Switchover の1枠のみ。
- 万一
off/replicaが返った場合のみ(=フェーズB 以降に誰かが CPG を戻した)、 Writer 1台の reboot が必要=瞬断なので Switchover とは別の事前枠を取り直す(production.md§2-B)。
4. 使用拡張・後処理の該当有無(実機確認 2026-06)
| 項目 | 該当 | 備考 |
|---|---|---|
インストール拡張(\dx) | plpgsql のみ | pg_stat_statements は別途 CREATE EXTENSION |
| pg_stat_statements | 要 | preload 済み・CREATE EXTENSION → Switchover 後に ALTER EXTENSION ... UPDATE(1.10 へ) |
| pgaudit | 未使用 | dr-work-record と違い CPG に含めない=target CPG との整合ズレの心配なし |
| pg_cron / pg_partman(bgw) | 未使用 | 該当なし |
| pg_repack / postgis | 未使用 | 該当なし |
| Auto Scaling / Reader | なし | Writer 1台 |
| 外部 CDC(Datastream 等 publication/slot) | なし | online-karte のような外部論理レプリ無し=BG 作成が external replication で失敗するリスクなし(BG 作成直前に再確認) |
→ 後処理で実際にやるのは ①拡張更新(pg_stat_statements)②(任意)統計リセット ③アプリエラーログ監視 の3つのみ。
4-1. ★preflight 実測結果 全節(横断チェックリスト A〜G 準拠)
確認方法(SQL・コマンド・期待値)は横断チェックリスト postgresql-pg16-upgrade-preflight-checklist.md を正とし、 本節は EVS の実測結果のみを記録する(#13802 で全 SQL を staging 実機検証済み・#14064 で本番実施)。
| 実施 | 環境 | Issue |
|---|---|---|
| 2026-06-17 | staging | #13802(チェックリストの全 SQL を実機検証) |
| 2026-06-24 | production | #14064(A〜C / D / G-1 を全節実施) |
A. DB 内部チェック(Blue/Green 阻害要因)
| 項目 | staging | production | 判定 |
|---|---|---|---|
| A-1 PK 無しテーブル | 0行 | 0行 | ✅ |
| A-2 unlogged / prepared_xacts / replication slot / large object / matview | すべて 0 | すべて 0 | ✅ |
| A-3 長時間 tx・進行中 DDL | なし | なし(自セッションの SELECT のみ) | ✅ |
A-4 サポートされない reg* 型カラム | 0 | 0 | ✅ |
A-5 無効な database(datconnlimit=-2) | 0行 | 0行 | ✅ |
| A-6 template0 / template1 | — | 両方 datistemplate=t | ✅ |
B. Extension
| 項目 | staging | production | 判定 |
|---|---|---|---|
pg_extension | plpgsql 1.0 のみ | plpgsql のみ | ✅ |
pg_available_extensions(全件・drift) | plpgsql 1.0/1.0(drift なし) | 同左 | ✅ |
pg_stat_statements | preload 済み・未作成 | preload 済み・未作成(installed_version=NULL / default 1.8) | ✅ 作成はフェーズB |
| postgis / pg_repack / pg_cron / pg_partman / cube / seg | 0件 | 0件 | ✅ 事前対応不要 |
preload レベルでも事前対応は不要: 現行 CPG の
shared_preload_librariesはrdsutils,pg_stat_statementsのみで、pgaudit / pg_cron / pg_partman を含まない(#14064)。
C. PG16 非互換
| 項目 | staging | production | 判定 |
|---|---|---|---|
C-2 DB 内関数/ビューの EXTRACT・階乗 ! !! | 0件 / 0件 | 0行 / 0行 | ✅ 該当なし |
| C-3 public スキーマ ACL | PUBLIC=UC(PG13 既定維持) | PUBLIC=UC(owner YvkE3F7S=UC) | ✅ ブロッカーではない |
C-4 password_encryption | md5 | md5 | ✅ ブロッカーでない(既存 md5 資格で接続成立) |
C-5 包含演算子 @ ~ 削除(幾何型・cube/seg) | 0行 / 0件 | 0行 / 0件 | ✅ 該当なし |
C-6 廃止言語(plpython2u / plpythonu) | 0件 | 0件 | ✅ 該当なし |
C-7 バックアップ関数旧名(pg_start_backup 等) | — | 関数/ビュー 0行 | ✅ 該当なし |
C-8 挙動変更(hash_mem_multiplier 1.0→2.0 等) | — | 現状 1 | ⚠️ 移行後の監視対象(下記) |
C-3
PUBLIC=UCについて: PG15 以降 public スキーマの既定 ACL は縮小されたが、 既存 DB をアップグレードする場合は既存 ACL がそのまま維持されるため移行ブロッカーではない。 セキュリティ観点の見直し候補ではあるが、本移行では変更しない(変更すると影響範囲が読めない)。C-4
md5について: SCRAM 化は PG16 移行とは独立した別タスク。md5 のままでも接続は成立する(実機確認済み)。⚠️ C-8 は「該当なし」ではなく「移行後に見る」項目: PG16 では
hash_mem_multiplierの既定が 1.0 → 2.0 に変わる(現状1)。ハッシュ処理のメモリ配分が変わるため、 Switchover 後はDBLoad/FreeableMemory/ 一時ファイル / slow query を監視する(§10 のダッシュボード+§12 の Top SQL 突合)。
D. 接続元・接続先の棚卸し
| 項目 | 結果(production・2026-06-24) | 判定 |
|---|---|---|
| Connection Pooler(RDS Proxy / PgBouncer) | なし(staging も出力空) | ✅ |
pg_stat_activity の接続元 | アプリ(ECS)由来のみ — YvkE3F7S の2ホスト(10.21.1.67 / 10.21.3.28)・application_name 空・長寿命プール接続 | ✅ |
| DB SG inbound 5432 | eligibility-verification-sg(アプリ) + fd-office-connection(踏み台) の2 SG のみ | ✅ 外部DBクライアント用の許可なし |
| 外部 DB クライアント(Trocco / ReDash / Retool / BI / Airflow) | なし | ✅ |
pg_replication_slots / pg_publication / pg_publication_tables / pg_stat_replication | すべて 0 行 | ✅ BG 阻害なし(外部 CDC 非運用) |
★最重要の確認: 外部 logical slot / publication があると BG の Read Replica 作成が
Replica creation is canceled due to external replication.で失敗する(online-karte の実例 #13732)。 EVS は 0 行なのでこのリスクはない。BG 作成直前にもう一度確認する(production.md§3)。
E / F / G. パラメータ差分・ORM 互換・Blue/Green 前提
| 項目 | 結果 | 判定 |
|---|---|---|
| F ORM/driver 互換 | Prisma 6.x(5.0+ で PG16 正式サポート)。pg / pg-native / Kysely / TypeORM の直接利用はなし=Prisma に集約。$queryRaw 等の生SQL も無し(#13098 でコード確認) | ✅ |
| F アプリ SQL | Aurora へ出る SQL は prompt_definitions の mode(UNIQUE) SELECT / ocr_results INSERT / 管理用 prompt_definitions UPDATE のみ。標準的な SELECT/INSERT/UPDATE/RETURNING で、PG16 非互換項目(EXTRACT 戻り値型・階乗・public CREATE 依存)に該当なし。jsonb も通常サポート範囲 | ✅ |
| G-1 BG 前提(付替前の現状) | rds.logical_replication=off / wal_level=replica / wal_sender_timeout=1min / shared_preload_libraries=rdsutils,pg_stat_statements | ⚠️ 想定どおり→フェーズBで on/logical/0 化 |
| G-2 付替+再起動後 | ✅ 2026-06-30 に on / logical / 0 を確認(#14065) | ✅ 完了 |
ValidUpgradeTarget | 13.20 時点: 16.8 / 16.9 / 16.10 / 16.11 / 16.13(#14064)/ 13.23 時点(2026-08-19 再確認): 16.11 / 16.13 / 16.14 | ✅ 16.13 を採用 |
| バックアップ保持 | 7 日 | ✅ |
結論(#14064)
本番 eligibility-verification は Blue/Green 可能な状態。BG を阻害する外部レプリケーション・ 外部 DB クライアントは存在しない。read-only で取得可能な DB 側 preflight は全節クリア。
残課題(preflight の枠外・別作業)
| 項目 | 状態 |
|---|---|
| C-1 / F: アプリ repo の grep(生SQL・EXTRACT・階乗・Prisma 互換) | ✅ 完了(#13098 comment・上表 F に反映) |
| E / H: AWS describe 系(パラメータ差分・DNS TTL) | ⚠️ 一部未実施(psql 不要で取得可)。DNS TTL は当日 §0 で確認 |
| C-2 関数定義スキャンの完全版 | pg_get_functiondef が集約関数で array_agg is an aggregate function を出すため、prokind NOT IN ('a','w') + CTE フェンス版が未実施。拡張=plpgsql のみ・ビュー 0 行のため 0 行見込みで判定影響なし |
5. ★スキーマ実態と「このサービス最大のリスク=シーケンス継続性」
EVS の DB は極小(app repo eligibility-verification-service/api/prisma/ で確定)。 ⚠️ 環境でテーブル数が違う(production 3 / staging 5・2026-08-20 実測)。理由は §5-2 の未適用 migration。
| テーブル | 役割 | PK | 書込 | prod | stg |
|---|---|---|---|---|---|
ocr_results | OCR 結果の追記専用ログ(アプリは読み返さない) | SERIAL(int・autoincrement) | ★アクティブ(OCR 実行ごとに 1 INSERT) | ✅ | ✅ |
prompt_definitions | OCR 指示文マスタ | SERIAL(int・autoincrement) | 管理時のみ | ✅ | ✅ |
_prisma_migrations | Prisma migration 履歴 | text | migration 時のみ | ✅ (2) | ✅ (3) |
myna_mock_consented_patients | マイナ資格確認モック(#16756) | SERIAL | QA 時のみ | ❌ 未適用 | ✅ |
myna_mock_consent_query_settings | 同上 | SERIAL | QA 時のみ | ❌ 未適用 | ✅ |
★5 テーブルすべて
SERIALPK(_prisma_migrationsを除く)= シーケンス継続性の検証対象。 staging の実測で 4 シーケンスが検出されるのはこのため。
なぜシーケンスが最大リスクなのか
Blue/Green の論理レプリケーションは「行」は複製するが「シーケンスの現在値」は複製しない。ocr_results / prompt_definitions は SERIAL PK(nextval 依存)なので、Switchover 後の Green で シーケンスが max(id) より遅れていると、次の OCR 実行がその場で PK 衝突して落ちる。
- read だけの確認では絶対に検出できない(件数は一致して見える)。
- UUID PK の mental-appointment ではこのリスクが無い(preflight で
sequence=0と判定される)が、 EVS は真逆=ここが移行検証の主眼。
→ 対策は §11 の verify script(4) シーケンス継続性)を Switchover 直後・アプリ再開前に必ず実行すること。 DRIFT が出たら setval で是正してからアプリを流す。
5-2. 🔴 未適用 migration = BG 中に走ると Switchover 後に壊れる(★最優先で解消)
本番 EVS DB には未適用の migration が 1 本ある。BG 同期中にこれが走ると Switchover 後にアプリが落ちる。
確定している事実
| 事実 | 根拠 |
|---|---|
origin/main に migration が 3 本ある(3本目 = 20260803000000_add_myna_mock_consent_tables) | app repo・2026-08-04 マージ(#16756 / #16760) |
その内容は CREATE TABLE ×2 + CREATE INDEX ×2(myna_mock_*) | migration.sql |
本番の _prisma_migrations = 2(staging = 3) | 2026-08-20 実測(verify script) |
| 本番の稼働イメージは v1.7.1(リリース 2026-07-13) | task definition eligibility-verification-task:38 実測 |
本番デプロイのたびに prisma migrate deploy が走る | .github/workflows/eligibility-verification-service@deploy-prod.yml の「Database migration」ステップ |
→ migration マージ(08-04) 以降に本番デプロイが無く未適用のまま(上記の整合からの推定)。 つまり 次回の本番デプロイでこの DDL が本番 DB に流れる。
なぜ危険か
Aurora Blue/Green は内部で論理レプリケーションを使うが、DDL は Green に複製されない(AWS も BG 中の DDL を避けるよう明記)。 BG 同期中にこの migration が走ると:
- テーブルは Blue にだけ作られ、Switchover 後の新 primary(旧 Green)には存在しない
- アプリはその頃テーブルがある前提で動くため
relation does not existで落ちる - 最悪、同期自体が壊れて BG をやり直しになる
対処(BG 作成前に必ずどちらかを確定させる)
| 方針 | 補足 | |
|---|---|---|
| A(推奨) | BG 作成の前に本番デプロイを1回通し、migration を適用しておく | Green は Blue のコピーとして作られるため、先に適用すれば Green もテーブルを継承する。以後は DDL 凍結 |
| B | BG 期間中に EVS の本番デプロイが発生しないことを関係者と合意 | リリース凍結の徹底が前提。緊急修正が入ると破綻するため A より弱い |
⚠️ Prisma には環境別に migration を出し分ける仕組みが無く、
prisma migrate deployは 保留中の migration をすべて適用する。myna_mock_*は staging の QA 用モックだが、 本番にもテーブルだけは作られる(未使用でも作られる)点に注意。
当日の確認方法
-- BG 作成前と Switchover 後の両方で一致すること
SELECT count(*) FROM _prisma_migrations;
SELECT migration_name, finished_at FROM _prisma_migrations ORDER BY finished_at;verify script の 2b/2c(全テーブル件数の pre/post diff)でも、_prisma_migrations の件数差が出たら DDL が流れた証拠として検知できる。
⚠️
2b/2cの diff は同一環境内(pre ↔ post)の比較専用。テーブル数が違うため staging と production の出力を突き合わせても意味がない。
5-1. 移行前ベースライン(件数)
既知の実測値:
- 2026-06-24(#14064・preflight 時点):
ocr_results= 729,194 行 /max(id)= 729,194 /_prisma_migrations= 2 /prompt_definitions= 2 - 2026-06-30(#14065・フェーズB 時点):
ocr_results= 735,918 行 - 2026-08-20(verify script
--label pre・read-only 実行):ocr_results= 808,995 行 /max(id)= 809,010 /prompt_definitions= 2 /_prisma_migrations= 2 / 全 3 テーブル
★約 50 日で +73,077 行(1 日あたり約 1,460 件) = 本番は現役で書き込みが走っている(実行の 20 秒前にも INSERT があった)。 Switchover の書込断が実トラフィックに当たることの裏付けであり、メンテ枠の周知が必要。 ★件数(808,995) と
max(id)(809,010) の 15 のギャップは、削除ではなくロールバックされた INSERT(シーケンスのみ消費)による正常な差。--write-probeを使うと同じ理由でこのギャップが 1 増える。
当日は改めて取得し §14 へ追記する。整合確認クエリ:
SELECT COUNT(*) FROM _prisma_migrations;
SELECT COUNT(*) FROM ocr_results;
SELECT COUNT(*) FROM prompt_definitions;6. consumer 棚卸し(誰が EVS Aurora を触るか)
DB SG 5432 inbound(実機・§1 の枠内)= EVS の ECS + fd-office 踏み台のみ。SaaS(Retool 等)・ CIDR 直許可・ETL(Trocco 等)・外部 logical レプリは 無し=consumer は事実上 1 系統。
| consumer | 経路 | read/write |
|---|---|---|
eligibility-verification-service(ECS Fargate・desired 2) | Prisma(DATABASE_URL = cluster endpoint)→ Aurora | prompt_definitions read / ocr_results write |
| SRE(調査) | fd-office 踏み台 → SSM port-forward → psql | read-only |
- ECS: cluster
eligibility-verification-cluster/ serviceeligibility-verification-service(2026-08-19 実測 running=2・タスク IP 例10.21.3.33/10.21.1.153)。 - cluster endpoint 接続=Switchover でエンドポイント名が据え置かれる(identifier は不変)ため、 Secret / DB_HOST / ECS の再設定は不要。必要なのは接続の張り直し(再接続)だけ。
- ⚠️ ECS タスク IP は再デプロイで変わるため、IP を手順書に固定で書かない(verify script は実行時に
aws ecsから動的解決する)。
6-1. ★どの画面操作が EVS Aurora を触るか(重要・誤解しやすい)
| 画面操作 | 経路 | EVS Aurora |
|---|---|---|
「オン資確認実行」ボタン(/admin/kartes/:id/eligibility_verification) | EVS /v1/insurance-card → 外部 OQS 照会のみ。結果は fastdoctor-manager 自身の DB に保存 | 触らない(接続も書込も発生しない) |
| 保険証/医療証の画像アップロード(OCR) | Image 保存 → ExecuteOcrJob(非同期)→ OcrPreviewService → EVS POST /v1/ocr/preview | ocr_results に +1(接続も発生)★これで確認する |
→ EVS Aurora の疎通確認は「保険証画像の OCR」で行う。「オン資確認実行」では EVS Aurora は動かない。 Flipper ocr_from_eligibility_verification が有効であること(OFF だと legacy Ocr::Service 経由で EVS を通らない)。
6-2. API プローブの限界(EVS 固有)
GET /health-checkは DB を触らない liveness のみ(controller が固定文字列'OK'を返すだけ)=DB 疎通の証明にならない。- → アプリ→Aurora の疎通は「verify script の 接続元→ECS 属性化(
pg_stat_activity)」+「画面からの OCR E2E」で担保する(§7)。
HTTP 経路から DB まで叩く案は採らない(2026-08-20 判断)。
ALB → ECS → Auroraを一気通貫で確認する案(踏み台→ALB の SG を開けて read クエリを投げる)を検討したが、 production で GraphQL read が利用できないため見送った(mental-online-karte#17740 / terraform_for_aws#2659 はクローズ)。 将来 production で利用可能になった場合に再開できるよう、候補だった経路を記録として残す:
7. アプリ経由のライブ検証(staging で実証済み・2026-06)
⚠️ staging 限定(本番 acct
967691968827/.fstdr.jpでは実施しない)。実 PII 不可。 Textract+Bedrock の少額課金+ocr_results1行が発生するので後始末する。ocr_resultsは本番 OCR(個人情報を含み得る)も入るため、調査時はSELECT *を避け id/件数/時刻のみ参照。
7-1. GraphQL 経由(fdc api graphql)
経路(#13098 でコード確定): EVS の OCR は入口が2系統あるが、どちらも同じ OcrPreviewUsecase.call() に合流する。
REST : POST /v1/ocr/preview (AuthGuard = x-api-key・サーバー間・mode は呼び出し側指定)─┐
GraphQL: previewInsuranceCardOcr (CatAuthGuard + @requiresScopes・ユーザー単位・mode 固定) ├─→ OcrPreviewUsecase.call()
previewMedicalCertificateOcr ─┘ ├ prompt_definitions READ(find by mode)
└ ocr_results WRITE(create=1行 INSERT)- GraphQL は chronic-api フェデレーション Gateway 経由(認証=CAT +
@requiresScopes([is:Operator]/[is:ServiceAccount]))。fdc api graphqlはこの Gateway を叩く(トークンは自動リフレッシュ)。 - 書込トリガー: GraphQL mutation
previewInsuranceCardOcr(またはpreviewMedicalCertificateOcr)。 1リクエスト=prompt_definitionsREAD +ocr_resultsに 1行 INSERT(jsonb)。 - 認証/実行:
pnpm fdc auth login(is:Operator)→fdc api graphql。
⚠️ REST の Swagger 記述に誤りがある(#13098 で指摘):
POST /v1/ocr/previewの説明に 「このAPIは、MS上にレコードを生成する副作用は発生させません」とあるが、実装はocr_resultsに INSERT する。 副作用なしと誤解して本番で叩かないこと。⚠️ read-only の GraphQL クエリ(
promptDefinitions)は production では利用できない(2026-08-20 判断)。OcrResolver.promptDefinitions→prisma.promptDefinition.findMany()でprompt_definitionsを read-only に SELECT できる経路はコード上存在するが、production では使えないため 疎通確認には採用していない(§6-2 / #17740)。
- 手順(詳細・実証ログは #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>)。
- ダミー保険証画像(「TEST/DUMMY」明記)を生成 → S3
7-2. 画面(fastdoctor-manager)経由 — 実証手順(2026-06-22 実施)
- ダミー保険証画像を用意(実 PII 不可・「TEST/DUMMY・テスト タロウ」等を明記)。
健康保険証のレイアウトに寄せると OCR 分類が通りやすい。 - EVS Aurora を read-only 監視(fd-office 踏み台
i-0cefcd4a25e8703e5経由 SSM):sqlSELECT count(*) AS cnt, max(id) AS max_id, max(created_at) AS latest FROM ocr_results; -- ベースライン SELECT pid, client_addr, state, state_change FROM pg_stat_activity WHERE datname='eligibility_verification' 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。 - その画面で保険証の写真をアップロード(=OCR トリガー)。
- 非同期ジョブ(
ExecuteOcrJob)のため反映は数十秒〜数分遅れることがある。
- オンライン診療 依頼フォーム
- EVS Aurora を再確認:
ocr_resultsの max_id/件数が +1、新規 ECS 接続が active→idle になれば成立。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。 「オン資確認実行」では同 DB は 無変化(接続も idle のまま)であることも確認=§6-1 の経路差を実証。
注意・落とし穴
- 非同期遅延:
ExecuteOcrJobはジョブのため即時反映でないことがある(数分待って再確認)。 - 冪等スキップ:
ExecuteOcrJobはreturn if image.ocr_result.present?。同じ画像の再アップロードは EVS を呼ばない(増えないのが正常)。確認は毎回別の新規画像で。 - 分類失敗: OCR が「健康保険証/医療証」と分類できないと
alertに「…どちらでもありませんでした」が入る(行は作られるがprocessed_result={})。ダミーは保険証レイアウトに寄せる。
8. 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> |
| Switchover 後の旧 Blue | eligibility-verification-old1(自動リネーム) |
9. ★Switchover 前 GO/NO-GO ゲート(EVS 版)
production.md §5 で機械的に実行する。1つでも欠けたら NO-GO=枠を閉じて延期。
| # | 条件 | 確認方法 |
|---|---|---|
| ① | BG Status=AVAILABLE / StatusDetails=null / SwitchoverDetails 各 AVAILABLE | describe-blue-green-deployments |
| ② | green メンバーの CPG が in-sync・engine=16.13 | describe-db-clusters(green) |
| ③ | レプリラグ ≒ 0(OldestReplicationSlotLag / slot の confirmed_flush_lsn 差が 0 bytes) | CloudWatch + blue で pg_replication_slots |
| ④ | blue の長時間 tx = 0 | blue で pg_stat_activity |
| ⑤ | 全テーブル件数が blue == green | verify script 2b(blue/green それぞれ)を diff |
| ⑥ | ★green で ANALYZE 実行済(read_only を外したセッションで・全テーブル対象) | 切替直後の性能事故防止 |
| ⑦ | ★CPUCreditBalance に余裕がある(枯渇していない) | §10 のダッシュボード(EVS 固有・t3.medium) |
| ⑧ | 保険スナップショットが available・engine=13.23 | describe-db-cluster-snapshots |
⑤⑥ は全テーブルが 3 つしかないため所要は僅少(mental-appointment の clone 実測で
ANALYZE VERBOSE27.2 秒 → EVS はさらに短い見込み)。
10. 監視(CloudWatch ダッシュボード)
Terraform: fastdoctor-template/eligibility-verification/production/pg16_dashboard.tfダッシュボード名: eligibility-verification-aurora-pg16-bluegreen(ap-northeast-1) 共通モジュール: template_modules/options/aurora-bluegreen-dashboard
green(動的名 eligibility-verification-green-xxxxx)は name_prefix への SEARCH で自動表示される。
★EVS 固有のウィジェット選択(実測に基づく)
| 設定 | 値 | 理由(実測) |
|---|---|---|
show_burstable_cpu_credit | true | db.t3.medium(バースタブル)。BG 中の最重要監視項目(§2-1) |
show_db_load | false | Performance Insights 無効 → DBLoad は emit されない(list-metrics=0) |
show_volume_bytes_left | false | AuroraVolumeBytesLeftTotal を emit しない(list-metrics=0) |
show_db_loadとshow_burstable_cpu_creditは同じスロット(x=8,y=7)を使うため排他(同時 true は module のpreconditionで plan 失敗)。 ※撤去可: BG 完了・旧 Blue(-old1) 削除・様子見後、module ブロックを削除して apply(destroy される)。 ※staging は移行済みのため本番のみに置く。
11. 移行前後の検証スクリプト(prove)
preflight(pg16-preflight-aurora.sh・読み取り専用)とは別に、Switchover の前後で DB 直の read / 接続 / シーケンス継続性を機械的に確認するスクリプト。
- 場所:
docs/sre/scripts/pg16-verify-eligibility-verification.sh(GitHub) - 使い方(切替の前後で実行して比較):bash
bash docs/sre/scripts/pg16-verify-eligibility-verification.sh --env production --label pre # …Switchover… bash docs/sre/scripts/pg16-verify-eligibility-verification.sh --env production --label post--envで profile / 踏み台を自動切替(staging=staging-admin/i-0cefcd4a25e8703e5、 production=production-admin/i-03b8c9b9fb3c4fe9a)。両 env とも ap-northeast-1。
| # | 検証 | 何を守るか |
|---|---|---|
| 1 | server_version | 13 系 / 16 系の確認 |
| 2 | read プローブ(3テーブルの件数・max(id)・最新時刻) | データ欠落 |
| 2b/2c | 全テーブル exact 件数 → --label post で pre と自動 diff | 構造的な欠落 |
| 3 | 接続元→ECS 属性化(pg_stat_activity の client_addr を aws ecs で逆引き) | アプリ→Aurora 疎通(§6-2 の代替) |
| 4 | ★シーケンス継続性(read-only) pg_sequences.last_value vs 対応表の max(id) | §5 の最大リスク。書込ゼロで DRIFT を検出 |
| 5 | ★write/nextval 実検証(--write-probe 指定時のみ・BEGIN; INSERT; ROLLBACK) | 実 nextval の確定検証 |
| 6 | writability(pg_is_in_recovery() / default_transaction_read_only) | 昇格後に書けるか |
| 7 | API プローブ(--api-probe・GET /health-check) | ECS/ALB 生存のみ(DB 疎通ではない・§6-2) |
合格基準(post 実行時): server_version が 16 系 / 2c の diff が空(または ocr_results の in-flight 数件差のみ) / 4 の verdict が全て OK/unused / 3 で ECS 接続が復帰(★本番は 2 行・各 1 本以上) / 6 で in_recovery=false。
11-1. ★3) の期待値(本番は 2 行)
ECS の desiredCount は staging=1 / production=2(タスクサイズは両環境 1024 CPU / 2048 MB・実測 2026-08-20)。 したがって 3) の出力に現れる eligibility-verification-service の行数は staging 1 行 / production 2 行が期待値で、 本番で 1 行しか出なければ片方のタスクが再接続できていないことを意味する。
⚠️ 接続本数の絶対値は合否条件ではない(判定は「タスク IP からの接続が 1 本以上」)。 Prisma はプールを遅延して開き、アイドル接続は回収されるため本数は実行時のトラフィックで変動する。 実測でも staging 3 本 / production 2 本×2 と差が出たが、タスクサイズは同一でどちらも健全。
見るべきは ①0 本でないか ②本番は 2 行そろっているか ③
backend_startが Switchover 時刻より後か。 ※connsはstate別の集計なので、同一タスクでidleとactiveが混在すると行が分かれる(合計は足し算)。 ※local/rdsadminの行は Aurora 内部の管理プロセスでアプリとは無関係(判定対象外)。
★Switchover 直後は「0 本」が正常 → post は --wait-ecs 必須
Switchover は Blue を read-only 化 → エンドポイント DNS を Green に付け替え → Blue を -old1 にリネームする。 つまり切替後は **物理的に別のインスタンス(旧 Green)**に繋がり、新 primary の pg_stat_activity は そもそも旧接続を持たない(旧 Blue の接続は切断される=瞬断)。加えて Prisma は接続を遅延して張る (次のクエリが来るまで再接続しない)ため、切替直後は **「少ない」ではなく「0」**になる。
| タイミング | 3) の見え方 |
|---|---|
| pre(Blue) | 2 行 / 各 2 本 = 計 4 本(2026-08-20 実測) |
| post 直後(新 primary) | 0 行 ★これは正常 |
| トラフィック到来後 | 2 行 / 各 1〜2 本に回復 |
→ --wait-ecs を付けずに post を実行すると、正常なのに FAIL と誤検知する。 本番の OCR 書込は約 1,460 件/日 ≒ 1 分あたり約 1 件(§5-1 実測)なので、--wait-ecs 180 なら 期待値で約 3 リクエストが到来し Prisma が張り直す。
なお client_addr の値(ECS タスク IP)は切替では変わらない。変わるのは行数・本数のみ。 ただし復旧のため ECS を強制再デプロイすると IP が変わるため、スクリプトは IP を直書きせず 実行時に aws ecs から再解決している。
⚠️
--write-probeはROLLBACKするがシーケンスは戻らない(ocr_resultsの id を1消費する)。ocr_resultsはアプリが読み返さない追記専用ログのため実害はないが、既定 OFF。 4(read-only)で DRIFT なしを確認できていれば 5 は省略してよい。
12. Terraform 整合(Switchover 後・旧 Blue 削除より前)
- パス:
fastdoctor-template/eligibility-verification/production/ main.tfの DB モジュール呼び出しを更新:rds_engine_versionを"13.23"→"16.13"rds_familyを"aurora-postgresql13"→"aurora-postgresql16"- source 側 custom CPG(
cluster_parameter_group_custom_enable=true+cluster_parameter_group_params)を PG16 用 CPG 参照へ切り替え(-pg16を継続利用する方針= #14418)
terraform planに downgrade / replace(ForceNew)が出ないことを確認してから apply。- apply は
-targetでドリフト巻き込みを回避(staging 実績 PR: #2263)。 - 旧
eligibility-verification-old1(PG13)は数日安定確認後に削除。source 側 PG13 CPG は孤児化してから削除。
13. 関連 Issue / PR
| 種別 | 参照 |
|---|---|
| 親チケット | mental-online-karte #12725「[DB] eligibility-verification PostgreSQL 16系アップグレード」 |
| 本番手順書 | mental-online-karte #13721(CLOSED・本書群で置換) |
| staging 調査・検証 | mental-online-karte #13098 |
| staging-direct 実施 | mental-online-karte #14063 |
| Clone リハーサル | mental-online-karte #13719(CLOSED) |
| 本番 preflight | mental-online-karte #13926(CLOSED) |
| 本番 CPG 追加(TF) | mental-online-karte #13927(CLOSED)/ terraform_for_aws #2253 |
| 本番 フェーズA-1/B(CPG 付替+再起動) | mental-online-karte #14065(CLOSED・2026-06-30 実施・全コマンド実行ログあり) |
| staging CPG 追加(TF) | terraform_for_aws #2164 |
| 実装メモ(custom PG / logical / PG16 target) | mental-online-karte #12905 |
昇格後の PG 整合(-pg16 継続利用) | mental-online-karte #14418 |
| 本番 preflight 全節実施(A〜G) | mental-online-karte #14064(OPEN・2026-06-23/24 の実測ログあり) |
| プレフライト チェックリスト(横断)の整備 | mental-online-karte #13802(OPEN)/ doc: postgresql-pg16-upgrade-preflight-checklist.md |
| 踏み台→ALB フルパス疎通(★見送り) | mental-online-karte #17740(CLOSED・not planned)/ terraform_for_aws #2659(CLOSED) |
| prove スクリプト | mental-online-karte #17737 / terraform_for_aws #2656 |
| CloudWatch ダッシュボード | mental-online-karte #17738 / terraform_for_aws #2657 |
| 手順書再編(本書) | mental-online-karte #17739 / terraform_for_aws #2658 |
| 対象 DB の棚卸し・方針 | mental-online-karte #13044 |
14. 実施記録
| 日付 | 環境 | フェーズ | 結果・所要・気付き |
|---|---|---|---|
| 2026-06-16〜17 | staging | Clone リハーサル | Green 作成 約33分(Read Replica 約16分+PG16 化 約14分)。Green offline 約12分(Blue 無影響)。Switchover 書込断 約2秒。 |
| 2026-06-17 | staging | preflight(チェックリスト全 SQL) | 全項目 ✅(#13802)。plpgsql のみ / PK なし 0 / 外部 slot 0 / RDS Proxy なし / md5 / PUBLIC=UC。logical=off は CPG 未付替のため想定どおり。 |
| 2026-06-23 | production | preflight 着手・権限ブロッカー | read-only ロールに GetSecretValue / ssm:StartSession が無く DB 内部確認が不能と判明(#14064)。踏み台を i-03b8c9b9fb3c4fe9a と特定(旧記載 i-0cefcd4a25e8703e5 は本番に存在せず誤り)。CPG レベルでは preload 要対応なしを確認。 |
| 2026-06-24 | production | preflight 全節実施(A〜C/D/G-1) | ✅ ブロッカー無し(#14064)。外部 slot/publication/レプリ すべて 0 行=BG 阻害なし。接続元はアプリ ECS の2ホストのみ=外部DBクライアントなし。ベースライン ocr_results=729,194。C-8 hash_mem_multiplier は移行後の監視対象として認識。 |
| 2026-06-22 | staging | 画面 E2E 実証 | 保険証 OCR で ocr_results が +1(4293→4295)。「オン資確認実行」では EVS Aurora 無変化=経路差を実証。 |
| 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。 |
| 2026-06-30 | production | フェーズB(CPG 付替+Writer reboot=瞬断) | ✅ 成功(#14065)。付替前 logical=off/wal_level=replica/wal_sender_timeout=1min → 付替+reboot 後 on/logical/0。CREATE EXTENSION pg_stat_statements 完了。外部CDC(slot/publication/replication)0行を確認。再起動後にアプリが設定変更なしで自動再接続し ocr_results 735,918→735,919=write 健全。 |
| 2026-08-19 | 両 | 実機再確認 | staging=16.13 移行完了。production=13.23・CPG 付与済(in-sync)・db.t3.medium/PI 無効・非-Global・Writer1台。旧 doc の「13.20 / 本番 CPG 未作成」を訂正。 |
| 2026-08-20 | 両 | verify script 実走(--label pre・read-only) | ✅ 両環境で全チェック PASS(exit=0)。staging=16.13 / production=13.23。3) 接続元→ECS 属性化は 本番 2 タスク・計 4 本を検出(staging 1 タスク 3 本)。4) シーケンスは両環境 DRIFT なし。移行前ベースラインを取得(/tmp/pg16-verify-evs-rowcounts-production-pre.txt)。 |
| 未実施 | production | フェーズC/D(BG 作成→Switchover) | ★残タスクはここだけ。フェーズB 済みのため Switchover の1枠のみで完了する。snapshot 名・所要・結果は実施時に追記。 |