Skip to content

PG16 アップグレード preflight:mental-appointment(★Global Database・本番)

本書は mental-appointment 本番の PG16 移行前 preflight(read-only)調査結果とその手法をまとめたリファレンス。 実施手順本体(clone リハ/staging/本番当日)は別ファイル(作成予定 #17390 / #17391 / #17392)。本書は なぜ/現状値/consumer 棚卸し/アプリ互換/E2E 疎通の方法 を記録する。

📚 mental-appointment 手順書セット(予定):

  • 📖 本書(services/mental-appointment/preflight.md)= preflight リファレンス:調査結果・現状値・consumer・アプリ互換・E2E 方法
  • 🧪 clone リハ手順 mental-appointment/clone-rehearsal.md(#17390)/ 🧪 staging 手順 mental-appointment/staging.md(#17391)/ 🏭 本番当日手順 mental-appointment/production.md(#17392)

方針:dr-work-record と同じく Global Database を削除して standalone 化 → 標準(非-Global)Blue/Green(詳細は dr-work-record.md §6)。ただし mental-appointment は us-east-1 / ap-northeast-1 両リージョンにアプリ(ECS/ALB)が存在する点が異なる(§3・§5 参照)。

対象サービス: mental-appointment / 親イシュー: #17280 / preflight イシュー: #17281 / 最終更新: 2026-08-13 関連: staging CPG #17388 / 本番 CPG #17389 / clone 手順 #17390 / staging 手順 #17391 / 本番手順 #17392 / 踏み台→ALB SG(staging)terraform_for_aws#2621


★0. 最重要: mental_appointment DB はハイブリッド(凍結+アクティブ)— 2026-06-01 に予約本体が v2 へ移行済(2026-08-13 実測)

移行 E2E 設計を左右する核心。必読。

2026-06-01 の「時間帯予約(v2) 全面展開」で、メンタル予約の本体は別系統(v2)へ移行した。移行対象の mental_appointment DB(本番・staging とも)は以下のハイブリッド状態:

テーブル本番の最新 created_at状態
🟢 アクティブ(継続書込)patients2026-08-13(当日)継続
🟢 アクティブdoctor_appointment_blocks(医師枠)2026-08-13(当日)継続
🟢 アクティブappointment_patient_histories2026-08-09継続
🔴 凍結(legacy)appointments / appointment_histories / appointment_required_actions / appointment_health_insurance_histories2026-06-01(全面展開日で停止)読取のみ
🔴 凍結appointment_online_videos / clius_kartes2026-05-31読取のみ
🔴 凍結appointment_cancels(6/02) / pick_up_pharmacy_histories(6/09)〜6月上旬読取のみ

新規の時間帯予約(v2)は mental_appointment DB に書かれない(別系統)。v2 予約の実ストア=online_ops_service DB(online-ops-service 自前DB・Aurora PG 16.11・既にPG16) と特定。テーブル: "Appointment" / TimeSlotReservation / TimeSlotAppointmentState / DoctorTimeSlotAppointmentShift / AppointmentPatient / AppointmentDoctorAssignment / AppointmentVideo / AppointmentNotificationSent 等(Prisma PascalCase テーブル)。BFF(ai-consultation-assist/bff)は graphql-online-ops.service.ts("v2 appointment queries 用")で online-ops-service を appointmentV2 の取得先にしている。

実証(staging): 患者WEB /v2 で作成した予約 1e4d9f39-4aa0-4b8e-9a85-3cc8a6c8650b(accountId 307557 / memberId 307414 / status PENDING / createdAt 2026-08-13 11:37 UTC)は mental_appointment DB に不在online_ops_service."Appointment" に存在。同DBに MigrateFixedTimeToTimeSlotLog(固定予約→時間帯予約 移行ログ)テーブルあり=2系統分岐の裏付け。 移行スコープ: 現行予約が載る online_ops_service既に PG16(16.11)=本移行(#17280 mental_appointment 13.23→16)の対象外。今回の対象はあくまで legacy mental_appointment(凍結面の読取+アクティブ面 patients/医師枠の書込)。

全体アーキテクチャ(2つの Aurora = 2系統)

「app(ECS)」と「Aurora(DB)」を分離して表示。v2 の実ストア online_ops_service(既PG16=対象外)移行対象 mental_appointment(13.23) の2つの Aurora があり、findById(v2 DB に存在するか)で書込先が決まる。

要点:分岐キーは予約種別ではなく findById(v2 DB=online_ops_service に存在するか)。存在→v2 Aurora で完結/不在→legacy 委譲で mental_appointment に write。online-ops は「v2 に write/read + mental_appointment を read(固定時間予約)」、mental-appointment-service は「mental_appointment に write/read」。

★どの操作がどっちのDBを触るか(v2/legacy 分岐の実装・早見表)

「6/1」でDBを直接分けているのではない。分岐は3段で、日付分岐は最上流の「空き枠検索」だけ。DBを実際に選ぶのは create=種別/update等=レコード存在online-ops-service/api/src/appointment/)。

実装分岐キーDB の決まり方
① 空き枠検索queries/appointment-availability.facade.tsbusinessDate vs CUTOVER_DATE_JST='2026-06-01'constants/cutover.ts)★唯一の日付分岐枠の種別を決める(<6/1→FIXED_TIME / >=6/1→TIME_SLOT)。DB は選ばない
② 予約作成facades/create-appointment.facade.ts L52input.appointmentType(種別)TIME_SLOT→OPS DB(online_ops_service/v2) / FIXED_TIME→Legacy API→mental_appointment
③ 更新/取消/リスケfacades/{update,cancel,reschedule}-appointment.facade.tsappointmentRepo.findById()(OPS DBに存在するか) L94/110/147存在→v2(updateTimeSlotAppointmentUsecase) / 不在→Legacy(PATCH /v1/admin/appointments/{id}/patient)→mental_appointment日付も種別も見ない
(read)診察履歴queries/patient-examination-history-appointment.read-repository.ts無分岐(両DBを読んでマージ)v2(Appointment)+mental_appointment(appointments L234) を結合。「固定時間予約」行=mental_appointment 由来
  • 含意:6/1 以降は新規予約が TIME_SLOT で v2 に載るため、以後の create/update も v2 が既定。mental_appointment に UI 経由で書くのは 「FIXED_TIME(v2未移行の固定予約)」を更新した時だけ(③の findById 不在パス)。docstring 明記「移行後 全予約が OPS DB 移行完了後、Legacy API 呼び出しは削除」=mental_appointment 書込は過渡期の後方互換。
  • ★UI 経由 write を staging で実証(2026-08-14)OPS DB 不在=legacy 確定の固定時間予約 21a32f73-b359-4337-8a90-c342d66c61bc(事前確認 in_ops_Appointment=0)を OPS online-karte.fstdr.app/ops/mental/{id} で開き患者情報(姓)を「てすと」→「てすとテスト」に編集したところ、mental_appointment.appointment_patient_histories に新規行が追記(count 1→2・recorded_at/updated_at=2026-08-14 02:35・event-sourcing で追記型)。→ UI 経由の mental_appointment write は「legacy(FIXED_TIME・OPS DB 不在)予約の患者情報編集」で可能と確定。分岐は予約単位findById)なので、その患者が v2 予約を他に持っていても関係ない。
  • ★write を ECS まで特定して再実証(2026-08-14・COMMIT タイムスタンプ一致):固定時間予約 09d77b1f-f200-496e-bd88-21117ac6f9db(患者「よねまる いち」)を OPS で開き名(いち→に)を編集。BEFORE=appointment_patient_histories 1行(よねまる/いち・05-21 06:32)→ AFTER=2行(よねまる/08-14 07:16:03 追記)。同時に pg_stat_activity10.10.3.65(mental-appointment-service) の直近クエリ=COMMIT@07:16:03 を捕捉=新規行 recorded_at と秒一致。→ 「UI編集→mental-appointment-service ECS が mental_appointment に COMMIT」まで一致で確定(接続・書込・書いた ECS の3点同時実証)。

書込経路の理解図(患者情報編集のシーケンス・実測)

決め手=findById(OPS DB に在るか)=予約1件単位。日付でも患者でもない。不在(legacy)→mental_appointment に書く/存在(v2)→v2 で完結。

  • E2E への効き方:read は「固定時間予約(6/1以前)」の表示(無分岐マージなので mental_appointment 接続が切れると消える)。write は (a) 継続書込監視(b) UI(編集可能な legacy 予約を1つブックマークし、Switchover 前後で「編集→appointment_patient_histories に新規行」を確認) の2択。※ legacy 予約は大半が事務局キャンセルのため「編集導線がある legacy 予約」を選ぶ。

移行 E2E への含意(重要)

  • 「新規予約を作成 → appointments テーブルで確認」は誤り(v2 別系統へ行くため mental_appointment を通らない)。
  • 正しい E2E:
    1. 凍結面(appointments 等)=読取健全性:consumer(online-ops / Retool / Trocco)が過去予約データを読めることを Switchover 前後で確認。
      • online-ops-service(v2) は mental_appointment の主要な read consumer(Kysely selectFrom・APPOINTMENT_DATABASE_URL):appointments(14)/appointment_histories(9)/doctors(8)/doctor_appointment_blocks(6)/appointment_cancels(5)/appointment_patient_histories(4)/pick_up_pharmacy_histories(3)/clius_kartes(2)/patients(1)。主コード src/appointment/queries/patient-examination-history-appointment.read-repository.ts患者の過去診察履歴=6/1以前の固定予約を ops/カルテ画面に表示)・appointment.query-service.tsappointment-block-adapter.ts
      • E2E(UI・staging 実証 2026-08-13):ops/カルテの予約詳細(online-karte.fstdr.app/ops/mental/{appointmentId})を開き、診察履歴に「固定時間予約」種別の 6/1以前の行(例: 2026-05-31)が表示されることを Switchover 前後で確認。
        • ★マーカー:診察履歴の 予約種別 列で 「固定時間予約」=mental_appointment(appointments) から読んだ legacy 記録 / 「時間帯予約」=v2(online_ops_service)。→ 「固定時間予約(6/1以前)」行が出続ける=mental_appointment read 健全。壊れると 6/1以前履歴が欠落。
        • ★実装根拠(online-ops-service/api/src/appointment/queries/patient-examination-history-appointment.read-repository.ts:診察履歴クエリ patientExaminationHistories は2ソースをマージ。時間帯予約=this.kysely.selectFrom('Appointment')(online_ops_service/v2)/固定時間予約=this.kyselyAppointment.replica(true).selectFrom('appointments')(L227/234・=mental_appointment)kyselyAppointmentAPPOINTMENT_DATABASE_URL(=mental_appointment) 接続で、if (!this.kyselyAppointment.isConnected()) return [](L220)ガードあり=mental_appointment に繋がらなければ固定時間予約行は空になり画面から消える。∴ 固定時間予約行の表示=mental_appointment read 成功の証跡。
        • 準備:6/1以前に「固定時間予約」を持つ患者を1人ブックマーク
          • ★推奨ブックマーク(staging・2026-08-14 DB裏取り済)https://online-karte.fstdr.app/ops/mental/09d77b1f-f200-496e-bd88-21117ac6f9db
            • 患者「よねまる いち」(accountId 307191 / memberId 306877)。メイン予約 09d77b1f 自体が固定時間予約(2026-05-31 07:30・事務局キャンセル)=URL を開くだけで online-ops が mental_appointment.appointments を read
            • Legacy DB 実在確認:appointments.id=09d77b1f…(patient_id b208a823・MENTAL_HEALTH)/この患者は Legacy に固定予約3件。
            • 画面での確認箇所:予約情報の「予約種別=固定時間予約」表示(診察履歴の「予約種別」列で「固定時間予約」行でも可)。表示が消えたら mental_appointment read が壊れたサイン。
          • (旧例)ops/mental/e7e90236-06b5-4a71-b274-e10b4c71ee32(2026-05-31 固定時間予約あり・確認済)。
          • ⚠️ ops URL の id は mental_appointment の appointments.id/patients.id とは限らない(v2 側 id のことがある)。固定時間予約の予約IDは Legacy appointments.id と一致(09d77b1f で実証)。
    2. アクティブ面(patients / doctor_appointment_blocks / appointment_patient_histories)=書込健全性:書き手=mental-appointment-service(repo chronic-phase-appointment-service・現役デプロイ) と特定。Switchover 後に以下の書込が通ること:
      • ★アクティブ面 write テスト用の FIX 案件(staging・2026-08-14 実証)09d77b1f-f200-496e-bd88-21117ac6f9db(患者「よねまる いち」・memberId 306877)。OPS でこの legacy 予約の患者情報(氏名)を編集mental_appointment.appointment_patient_histories に新規行追記(実証: 名 いち→に で 1→2 行10.10.3.65(mental-appointment-service) の COMMIT@07:16:03 と秒一致)。Switchover 前後で同編集を行い追記されることを確認する。
      • ★テスト可否(アクティブ3表・個別に明記)
        テーブルUI 固定テスト案件方法
        appointment_patient_histories09d77b1f(よねまる いち)legacy 予約の患者情報編集→行追記(実証済)
        patients△ 同案件(09d77b1f)の患者情報編集の patient master 更新経路実証マーカーは appointment_patient_histories を推奨(確実に追記)
        doctor_appointment_blocksUI 固定案件は無い(UI からはテスト不可)現行 UI の医師枠操作は v2(createDoctorShiftBlockV2online_ops_service)に書き、mental_appointment.doctor_appointment_blocks へは UI から直接書けない(書込は legacy/バッチ経路)。→ 継続書込監視(max(updated_at)/n_tup_ins の進行)で健全性確認する
      • patientssrc/appointment/repository/patient.repository.tspatient.create/update)。トリガ=管理 usecase updatePatientsendKarte(ORCA患者ID紐付け)・予約フロー。
      • appointment_patient_historiesAppointmentPatientHistory(患者情報スナップショット)。
      • doctor_appointment_blocksdoctorAppointmentBlock.repository.tscreateMany・医師ブロック設定)。
      • online-ops-service(v2) は mental_appointment の live 3表を直接書かない(v2 の Kysely 書込は自前 online_ops_service の PascalCase 表。mental_appointment へは appointment-block-adapter読取のみ)。医師シフト(稼働)は別サービス dr-shiftDR_SHIFT_SERVICE_ENDPOINT/entried-shifts)を両者が HTTP 参照。
      • ★書込 E2E の方式(staging 実測+実装で確定)=「継続書込監視」+「legacy 予約編集の UI」の2択
        • v2 予約(6/1以降)への UI 操作は全て v2(online_ops_service) に書く(実測3操作):
          • 患者情報変更(氏名/カナ)(v2予約 加藤さん)→ v2 AppointmentPatient のみ更新(mental_appointment.patients.updated_at 不変)=実測
          • ORCA 登録/再確認→ 空振り(実保険証+OCR→オン資→ORCA・外部副作用大)。
          • 医師ブロック→ v2 mutation createDoctorShiftBlockV2/doctor-shifts)=v2 書込。
        • ✅ ただし「legacy(FIXED_TIME・OPS DB 不在)予約」の患者情報編集は mental_appointment に書く(2026-08-14 実証):予約 21a32f73…in_ops_Appointment=0)を OPS で姓「てすと」→「てすとテスト」に編集 → appointment_patient_histories に新規行追記(count 1→2・02:35・event-sourcing)。分岐は予約単位findById)=③の legacy パス。
        • ★実装根拠(online-ops-service/api/src/appointment/facades/update-appointment.facade.ts:予約更新は Facade が 「OPS DB(=online_ops_service/v2) に予約が存在するか」でルーティング
          • appointmentRepo.findById()ヒット(=TIME_SLOT/v2 予約)→ updateTimeSlotAppointmentUsecase → online_ops_service に書く(L110-114)。その後 会員基盤/ORCA へ同期(L122-142)。
          • 不在(=v2未移行の FIXED_TIME 固定予約)→ updateViaLegacyApiPATCH /v1/admin/appointments/{id}/patient → mental-appointment-service → mental_appointment(L147-150, L301)。
          • docstring 明記:「移行後:全予約が OPS DB に移行完了後、レガシー API 呼び出しロジックは削除される」=mental_appointment 書込は過渡期の後方互換。設計 doc も「write は v2 一本/V2_FLAG 本番常時 ON」。
        • mental_appointment に UI から書く唯一の道=「固定時間予約(v2未移行 legacy)」を更新することだが、legacy 予約は 6/1 以降作られず大半が事務局キャンセルで編集導線が限られる。
        • ∴ 書込 E2E = Switchover 前後で patients / doctor_appointment_blocks / appointment_patient_historiesmax(updated_at)n_tup_ins が引き続き進むかを read-only 監視(本番は自然発生・staging は必要なら backend 同期を意図的にトリガ)。書き手(mental-appointment-service)と経路はコードで確定済み。
    3. 件数突合は全テーブルで実施(凍結面は不変、アクティブ面は微増を許容)。
  • ⚠️ 移行は依然必要patients/doctor_appointment_blocks が当日も書込=DB は生きている)。ただし E2E は上記に沿う。

★本番トラフィック実測:read/write の実在+consumer別(CloudWatch+pg_stat・2026-08-14)

コードで立てた「ECS×read/write マトリクス」を本番の実トラフィックで裏取りした(read-only・踏み台 i-09384db1d690bcfd0→Writer/Reader 各インスタンスへ SSM port-forward・pg_stat_* 参照のみ)。

① CloudWatch(Datadog aws.rds.*・直近3h・Writer=mental-appointment-0/Reader=mental-appointment-1

指標WriterReader読み
database_connections258両endpointに consumer 接続中
write_iops / write_throughput1,221 / ~201KB/s0 / 0Writer で書込・Reader ゼロ(正)
commit_throughput446/s623/stx commit 発生
read_iops / read_throughput0 / 00 / 0⚠️ ディスク読み=0。「読まれてない」ではなく buffer cache 全載り(下の blks_hit 参照)。CloudWatch read系ではアプリ SELECT を判定不可

② pg_stat(5秒デルタ=ライブか)

Reader mental-appointment-1Writer mental-appointment-0
Δtup_returned(読取)1,613,838 行(≈32万行/s)1,987
Δins/upd/del(書込)0 / 0 / 0(reader は書かない)0(書込はバースト的)
累積 DML(ins/upd)2.6M / 3.8M122K / 28K(書込実績あり
blks_hit / blks_read1.5兆 / 20.5万(cache hit 99.99%+97.8億 / 4.4万

READ は大量にライブ(Reader Δtup_returned=1.6M/5s)。CloudWatch read_iops=0 の正体=全読取が buffer cache hit でディスクに落ちないため。∴ 読取健全性は read_iops ではなく「接続数/DBLoad/PI/pg_stat tup_returned」で見る

③ consumer 別の切り分け(pg_stat_activity.client_addr の CIDR が決め手)

client_addrネットワークconsumer接続先R/W
10.10.x.x(例 10.10.22.166 他多数)ap-ne-1 peered(10.10.0.0/20・fd-platform-vpc)online-ops-serviceReaderread(1.6M tup/5s の主)
10.0.x.x(例 10.0.2.112 / 10.0.3.201)us-east-1 local(mental-appointment-sg)mental-appointment-serviceWriterwrite(Prisma・DML)
local / rdsadmin(JDBC)RDS内部・PI/監視両方—(Retool/Trocco は間欠でこの瞬間は非接続)

∴ 本番実測での確定マトリクス

ECS(driver)READ mental_appointmentWRITE mental_appointment本番証跡
mental-appointment-service(Prisma 5.13)Writer に 10.0.x・DML 実績
online-ops-service(Kysely+pg)✅(Reader・32万行/s❌(Reader Δins/upd/del=0)Reader に 10.10.x/Writer にも 10.10.x あり(下記 ⚠️)

⚠️ 訂正(2026-08-26 本番実測):ここに元々あった「Writer に 10.10.x 不在」は 点観測での取り逃しだった。 実測では Writer 側にも online-ops の接続が存在する。内訳は Reader = Kysely の select "appointments"…(固定時間予約の read)/ Writer = deep-health の SELECT 1kyselyAppointment.ping() は replica と primary の両プールSELECT 1 を投げる・#17624)。 どちらも online-ops-service-service のタスク(SG online-ops-service-sg)から来ていることを、 ENI の SG + 稼働タスクの privateIPv4Address 突合で確定済み。 write が無いという結論は変わらない(Writer 上の 10.10.x は SELECT 1 のみ・Δins/upd/del=0)。 → §3 reboot 後・Switchover 後の再接続確認は、Reader だけでなく Writer 側も見ること。

再現手順(read-only)i-09384db1d690bcfd0 から Writer/Reader インスタンス endpoint へ SSM port-forward → psqlSET default_transaction_read_only=on の上、pg_stat_database(tup_returned/inserted…の5秒デルタ)と pg_stat_activity(client_addr 別)を参照。consumer 別は client_addr の CIDR(10.10.x=online-ops / 10.0.x=mental-appointment-service)で割る。Switchover 前後で同手順=read(Reader Δtup_returned>0)・write(Writer DML/接続) の生存を確認できる。 ⚠️ online-ops の read 接続は idle 約10秒で回収されるため、点で見ると「不在」に見える。OPS UI を操作しながら連続実行して捕捉すること(上記の取り逃しの原因)。CIDR による推定だけで判断せず、ENI の SG + ECS 稼働タスクの privateIPv4 突合まで行うと確実(手順は production.md §8-C の代替手段)。

★staging:各 ECS の接続・処理を live 実測(ECS×read/write を UI 操作で捕捉・2026-08-14)

「UI 操作が該当 ECS から staging Aurora に対して行われたか」を、pg_stat_activity.client_addrECS タスク IP に逆引きして証明した(踏み台 i-0ddd2353e102d9006・read-only)。staging は ap-ne-1・単一インスタンス(mental-appointment-0.ctb7v7xkimcr…)。

IP → ECS 対応(staging・ECS describe-tasks で確定)

ECS タスク IPECS サービスtask SGDB SG inbound
10.10.3.65mental-appointment-service(writer 本体・Prisma)sg-01f3935c7fba1da5c✅ 許可
10.10.25.165online-ops-service-service(API・Kysely)sg-0d46c19ecedbcb45c✅ 許可

live 実測結果

ECSREADWRITEstaging 証跡
mental-appointment-service10.10.3.65SELECT … appointments(live)+Aurora データ更新appointment_patient_histories(21a32f73) が「てすと」(6/1) → UI 編集で 「てすとテスト」(08-14 02:35:21) を追記=1→2 行
online-ops-service10.10.25.165(書込は v2・mental_appointment には書かない)UI で固定時間予約患者ページをリロードした瞬間、10.10.25.165 から SELECT "appointments"…(Legacy DB read)を捕捉(04:11:46)
  • 固定時間予約の read が Legacy DB(mental_appointment) 由来である実装根拠patient-examination-history-appointment.read-repository.tskyselyAppointment.replica(true).selectFrom('appointments')(L227/234・コメント L183「固定予約は OPS DB ではなく Legacy DB の appointments テーブルにのみ存在する」)。isConnected() ガードは pool 初期化成功で true(node-postgres は遅延接続)。
  • 裏付け:Datadog に online-ops-service Appointment database pools initialized successfully(エラー無し)。config APPOINTMENT_DATABASE_URL/APPOINTMENT_REPLICA_DATABASE_URL → この staging クラスタ(writer/cluster-ro)。
  • ⚠️ 捕捉の注意(手順に必須):online-ops の read 接続は pg の idle 接続が約10秒で回収されるため、単発スナップショットでは写らない。UI 操作中に 1〜2秒間隔で連続サンプリングして初めて SELECT appointments を捕捉できる(本実測もそれで捕捉)。Switchover 検証時も同様に「操作しながら連続 poll」で確認する。
  • staging でも本番同様、mental-appointment-service(read+write) と online-ops-service(read) の両 ECS が Aurora に接続・処理していることを live で実証済み

1. 環境・接続

項目production
AWS プロファイルproduction-admin(⚠️ 書込権限あり・preflight は read-only 徹底
リージョン(DB)us-east-1(Aurora Global の primary。cluster endpoint も us-east-1)
クラスタ識別子mental-appointment
cluster endpointmental-appointment.cluster-cxmwyphil4em.us-east-1.rds.amazonaws.com
Secrets Manager Secret IDmental-appointment(⚠️ 値に制御文字を含む。§付録の strict=False パース必須)
DB 名mental_appointment
踏み台(bastion)i-09384db1d690bcfd0(fd-platform-bastion・us-east-1) ← DB SG が fd-platform SG を許可済み
DB SGsg-02088474cf99248f4fd-platform sg-09a3dec4eb92eebf0 を 5432 で許可)
アプリNestJS ^10.3.8 + Prisma 5.13.0 + GraphQL Yoga federation + Node 20PG16 対応◎。source: fastdoctor-jp/chronic-phase-appointment-service(package name = mental-appointment-service・ECR mental-appointment)。生SQL 33件も PG16 安全(§4)。※ online-ops-service(mental_appointment へは Kysely+pgkyselyAppointment)は同DBを読む別 consumer(§3/§9)

2. preflight 実測結果(read-only・本番 mental-appointment・2026-08-13 実測)

skill pg16-preflight 実行結果(権威データ)docs/sre/scripts/pg16-preflight-check.sh --target mental-appointment --profile production-admin --region us-east-1 --allow-production

SUMMARY: PASS=23 WARN=4 FAIL=0 → ブロッカーなし
MATRIX : mental-appointment | kind:aurora | engine:aurora-postgresql | version:13.23
         | logical:off | reader:yes | global:yes | pgaudit:no | spl:pg_stat_statements
         | max_wal_senders:20 | max_replication_slots:20 | publications:0
         | high_ext:none | warn_ext:none | app:要(repo) | risk:medium
         | blocker:none | next:reader-plan,custom-cpg,app-grep

WARN(4): ①production 接続(想定内)②members=2=Reader あり(再起動/AutoScaling 考慮)③Global Database(Global削除→標準BG)④logical=off/wal_sender_timeout=1min(CPG 付替+再起動)。他は全 PASS(PKなし表0/長時間tx0/reg型0/FDW0/postgis・pg_repack なし 等)。

以下は踏み台経由 psql の補足(§付録の手順・書き込み一切なし)。

項目実測値判定メモ
server_version13.23現行。target = 16.14(§9-A・現状最新の有効 BG ターゲット。16.13 も可)
rds.logical_replicationoff論理レプリ未使用
wal_sender_timeout1min(既定)CPG 方針次第(dr-work-record は 0 設定)
password_encryptionmd5重要:PG16 でも CPG が md5 なら md5 維持(scram 強制なし)。JDBC/Prisma とも md5 で継続接続可 → 認証方式起因のアプリ断はなし
shared_preload_librariesrdsutils,rds_casts,pg_stat_statements⚠️pgaudit なし=default CPG。→ custom CPG 作成が必要(#17388/#17389)
max_connections1706余裕大
current connections38app pool(idle)26 + JDBC 4 + その他
longest tx(ユーザー)0訂正:skill A-3 長時間tx=0。当初手動クエリで «≈8.4日» と出たのは pg_stat_activity 全体(walsender 等バックグラウンド backend の xact_start)を拾った誤検知。実ユーザーの長時間 tx はなし
reader(members)2(Writer+Reader)⚠️再起動は Reader→Writer 順。AutoScaling 考慮
pg_replication_slots0常設 CDC 消費者なし
pg_publication0logical publication なし
pg_subscription0subscriber なし
pg_prepared_xacts02PC 未使用
db_size≈1,390 MB(1.4GB)dr-work-record より大きい

接続元アプリ(application_name 集計)(空) 26 idle(Prisma app pool=本体/online-ops)/ PostgreSQL JDBC Driver 4(Retool・§3 で特定済)/ psql 1(調査)。


3. consumer 棚卸し(DB SG sg-02088474cf99248f4 の 5432 inbound)※確定版(2026-08-13)

DB SG inbound(5432)の source 全量mental-appointment-sg(sg-0bc93fb524e298f7d) / fd-platform-retool(sg-024766cf722c3329a) / fd-platform(sg-09a3dec4eb92eebf0) + CIDR 10.10.16.0/2010.10.0.0/20(=fd-platform-vpc 10.10.0.0/16・ap-northeast-1 のクロスリージョン peering)。

接続している アプリ / SaaS(確定)

種別実体接続経路(許可元)裏取り
🟦 アプリ①(本体)mental-appointment-service(repo chronic-phase-appointment-service・Prisma 5.13.0・GraphQL Yoga federation)writermental-appointment-sgECS→ECR→repo→現接続ENI(実測)
🟦 アプリ②(ops)online-ops-service/api(mental_appointment へは Kysely 0.27.3 + pg 8.11.5kyselyAppointment。※自DB online_ops_service は別スタック)reader(read専用)fd-platform-vpc CIDR(ap-ne-1→us-east-1 peering)secret online-ops-serviceAPPOINTMENT_DATABASE_URL/REPLICA 実測
🟧 SaaS③Retool(運用/管理画面・JDBC)fd-platform-retool SG(ENI 1・pg_stat_activity JDBC ×4)DB SG + 実接続
🟧 SaaS④Trocco(ETL/データ連携・バッチ抽出)reader cluster-ro-fd-platform-vpc 経由projects/trocco/connections/postgresql/mental-appointment-prd.json
⬜ 管理fd-platform 踏み台fd-platform SG本 preflight で使用

online-ops-service は read 専用(2026-08-14 本番実測で確定・§0「本番トラフィック実測」)。config(APPOINTMENT_DATABASE_URL=writer / APPOINTMENT_REPLICA_DATABASE_URL=cluster-ro) は writer/reader 両 endpoint を持つが、pg_stat_activity の client_addr で切り分けた結果、online-ops の接続は Reader のみ・Writer に 10.10.x 不在・Reader の Δins/upd/del=0=mental_appointment には書かない。コード上も kyselyAppointmentselectFrom のみで insert/update/delete 0件。→ Switchover の write 影響評価では online-ops は reader 扱い(write consumer は mental-appointment-service のみ)。

接続していない(誤検出を排除)

  • ai-triage / dr-shift / payment-service … 非接続で確定(4系統検証):①task-def 平文 host=各自クラスタのみ ②DATABASE_URL(Secret 由来)を解決=ai_triage/shift_production/payment_service(mental-appointment 参照ゼロ)③DB SG 許可リストに 3者の SG 不在 ④実DB 現接続は mental-appointment-sg ENI のみ。
    • ※ 共通接尾辞 cxmwyphil4emアカウント/リージョン共通の endpoint ハッシュでクラスタ名(プレフィックス)が別=別クラスタ。文字列一致による誤検出だった。
  • 論理CDC 系 SaaS(Datastream 等)… 無しpg_publication=0replication_slot=0subscription=0Trocco は reader への query ベース バッチ抽出(論理レプリ不使用)で BG の external replication 阻害要因にならない ✅。

ネットワーク補足(NAT 経由か?)

このDBは private subnet・SG制限・パブリックアクセス無効。到達は常にプライベート経路(同一VPC直/クロスリージョン VPC peering・TGW)で、NAT Gateway 経由ではない(NAT は private→インターネットの egress 用で、非公開 RDS への到達経路ではない)。CIDR 10.10.0.0/16 許可=fd-platform-vpc(ap-ne-1) からの peering。

Switchover 影響を受ける接続先= ①②アプリ + ③Retool + ④Trocco。移行後は 4者すべて疎通再確認(特に ②④ は reader endpoint 依存)。両リージョン(us-east-1 本体 / ap-ne-1 ECS・ALB)あり、BG/Switchover 対象は writer=us-east-1

判定replication_slots=0 / publication=0 / subscription=0 により 常設 CDC 消費者なし ✅。移行の論理レプリ観点はクリーン。


3-b. preflight オブジェクト包括確認(本番 mental_appointment・2026-08-13 実測・全PASS)

skill A-1/A-2 系に加え、外部キー整合・カスタム型・collation 等を追加確認。全てクリア。

項目結果判定
large objects / unlogged table / materialized view0 / 0 / 0
view2(定義に EXTRACT/階乗なし=skill C-2)
foreign table(FDW) / partitioned / 継承0 / 0 / 0
sequence0(全 UUID PK)BG のシーケンス drift 問題が原理的に発生しない
外部キー制約16(全 VALID・NOT VALID=0)
CHECK 制約 NOT VALID0
enum 型(MEDICAL_SERVICE_TYPE 等)9✅ major upgrade で保持・PG16 安全
domain / composite 型(user)0 / 0
user トリガ / event トリガ0 / 0
PK 無しテーブル / 非default REPLICA IDENTITY0 / 0✅(§2・BG 複製クリーン)
collationen_US.UTF-8・非default 列 collation=0✅(下記)
拡張plpgsql 1.0 のみ

sequence=0:全表 UUID PK のため BG 切替時の「Green でシーケンス未進行」問題(メジャー移行の定番リスク)が発生しないcollation:一般のメジャー移行では glibc collation 変化で text index の REINDEX が論点だが、Aurora BG は Green を新 PG16 で構築し論理レプリでデータ再投入+index 再作成するため collation 破損は非該当(Aurora が glibc 管理)。 外部キー16本 全VALID:整合クリーン。Aurora BG が Green 側で FK 再構築+整合適用。


4. アプリ/フレームワーク互換性

対象 repo(ALB mental-appointment の本体)fastdoctor-jp/chronic-phase-appointment-service(package name = mental-appointment-service・ECR mental-appointmentrelease-production.yml で push・ECS mental-appointment-cluster/mental-appointment-service)。DATABASE_URLmental_appointment に直結。

項目PG16 互換
フレームワークNestJS ^10.3.8
ORMPrisma 5.13.0@prisma/client 5.13.0 / prisma ^5.13.0)✅(Prisma 5.0+ で PG16 正式サポート)
GraphQLGraphQL Yoga federation subgraph@graphql-yoga/nestjs-federation ^3.10.6)
ランタイムNode >=20
認証方式DB password_encryption=md5(§2)✅ md5 継続で断なし

生SQL grep 結果(src・テスト除外・33件・appointment/repository/appointment.repository.ts に集中)=PG16 非互換パターン 0

使用構文(頻度)PG16 判定
AT TIME ZONE(27)✅ 標準
LATERAL join(9)✅ 標準
${id}::uuid cast(6)/ enum cast ::"MEDICAL_SERVICE_TYPE"(2)✅ 標準
Prisma.join(4)/ window OVER()(1)✅ 標準

$queryRaw + Prisma.sql を多用(33件)だが、削除機能(EXTRACT 特殊/reg*型/abstime/pg_start_backup 等)は 0。すべて PG16 で有効な構文。 ⚠️ ただし生SQL 33件と密結合のため、移行後の E2E(§5 ③ GraphQL 経由)で予約系クエリの回帰確認を推奨

⚠️ 別 consumer(第2の DB 利用者・接続確認済)mental-online-karte projects/online-ops-service/api@fastdoctor-jp/online-ops-service)も同じ mental_appointment DB を広範に読む(ops 用途・§0/§3)。secret online-ops-serviceAPPOINTMENT_DATABASE_URLmental-appointment.cluster-...(writer)+ cluster-ro-...(reader)を指すことを実測確認。Switchover 影響あり→移行後 疎通再確認必須。

★ consumer 側ドライバ/クエリビルダの PG16 互換(read 経路も評価済)

役割サービススタックPG16 互換根拠
書き手mental-appointment-servicePrisma 5.13.0(+ node20 / GraphQL Yoga)Prisma 5.0+ で正式サポート
読み手online-ops-serviceKysely 0.27.3 + pg(node-postgres) 8.11.5PostgresDialect+Pool・primary/replica)①Kysely は SQL ビルダーで互換はドライバ依存 ②pg 8.x は PG16 対応 ③online-ops 自前DB online_ops_service は既に Aurora 16.11 で同一スタック稼働=本番実証済
読み手Retool / TroccoPostgreSQL JDBCJDBC は PG16 対応

Kysely 補足:Kysely 自体はバージョン非依存。型パーサのカスタマイズは types.setTypeParser(INT8→Number) のみで、INT8(OID 20) は全 PG 共通 builtin=PG16 で OID 変化なし・無害。生 SQL(sql\``)による PG 固有機能の危険使用も無し(read クエリは selectFrom/join 中心)。→ mental_appointment 13→16 で online-ops の read に新規互換リスクなし

結論アプリ側コード変更なしで PG16 対応可(◎)。書き手 Prisma 5.13.0・生SQL は PG16 安全構文のみ・読み手 Kysely+pg 8.11.5 は既に PG16(16.11) 実績・md5 認証維持で接続断なし。


5. E2E 疎通確認方法(3経路)

経路内容前提状態
① 踏み台→DB 直(psql)server_version / 設定値 / 全テーブル件数(§7)DB SG が fd-platform 許可(済)実施可(本 preflight で実演済)
② 踏み台→ALB→app /healthapp 稼働+ルーティング。⚠️ @nestjs/terminus check([]) で DB を含まない踏み台→ALB(80) SG⚠️ SG 適用後に可(DB は触らない)
③ 踏み台→ALB→app→DB /graphqlDB を読む GraphQL query でアプリ→DB まで疎通踏み台→ALB(80) SG + read query 選定⚠️ SG 適用後に可(full path 検証はこれ)
  • ② / ③ には「踏み台→ALB(80)」SG が必要(staging = terraform_for_aws#2621。本番は同等 PR を別途作成予定)。
  • /health だけでは app→DB を確認できない(check 配列が空)。full path は ③ の DB 読み取り GraphQL query で確認する。
  • 画面経由 E2E:予約画面(患者/管理)から予約作成・取得。テスト予約データ作成手順は残タスク(staging は saku:create-test-reception スキルあり・本番は不使用)。

app 側 ALB(②③の宛先・writer リージョン us-east-1)internal-mental-appointment-1464731835.us-east-1.elb.amazonaws.com

bash
# ② /health(DBは触らない)
curl -s "http://<ALB>/health" -w ' [%{http_code}]'
# ③ /graphql(DBを読む=full path。安全な read query をスキーマから選定)
curl -s -XPOST "http://<ALB>/graphql" -H 'content-type: application/json' \
  -d '{"query":"<DBを読むクエリ>"}' -w ' [%{http_code}]'

6. CPG 設計方針(#17388 / #17389)

⚠️ 6-0. 現状 CPG の環境差(重要・#17388/#17389 の前提)

staging と本番 us-east-1 で現状の CPG 状態が異なる(2026-08-13 実測):

環境クラスタ現状 Cluster PGlogical / pgaudit
stagingap-ne-1 単一custom mental-appointment(PG13 family)on / あり
本番 東京ap-ne-1custom(main_tokyo.tf で適用)on / あり
本番 us-east-1(★移行対象)Global primarydefault.aurora-postgresql13off / なし
  • 原因:custom CPG(rds.logical_replication=1/pgaudit.log=all/wal_sender_timeout=0/max_wal_senders=20)は fastdoctor-template/pf-mental-appointment-servicevariable.tf に定義され、staging/main.tfproduction/main_tokyo.tf(東京)で適用。production/main.tf(us-east-1)には CPG 設定が無い。staging main.tf に TODO コメント # バージニア側で作成したものをモジュールに取り込むus-east-1 は Global DB 構築時に別途作成され custom CPG 未適用・IaC 未完全 import
  • 含意
    • staging(#17388):source 側は既に logical=on(PG13 custom CPG)。→「CPG付替+reboot で logical 有効化」は実質不要。作業は PG16 family の custom CPG(Green/target 用)を作るだけ。既存 PG13 custom CPG の custom parameter を PG16 側に揃える。
    • 本番(#17389):us-east-1 は default(logical=off)。→ 手順書の「CPG付替+reboot」が本番の必須実ステップ(reboot 伴う)。本番用 PG16 custom CPG 作成+付替で logical 有効化。
    • 技術的負債:本番 us-east-1 は custom CPG 未適用=IaC 未完全管理。今回の CPG付替が custom 管理下へ入れる好機(付替後に import/整合)。
    • ★インスタンス側 DB parameter group も存在(見落とし注意):cluster PG は default.aurora-postgresql13 だが、インスタンス mental-appointment-0/-1 には instance DB PG mental-appointment が適用済(user 変更パラメータ 0件=実質 default で実害なし)。→ PG16 化では cluster PG に加え instance PG(PG16 family) も必要。標準 BG の --target-db-parameter-group-name(instance PG)に PG16 版を指定する。未作成だと当日「instance PG が無い」で詰まるため #17388/#17389 の CPG 作成に instance PG(PG16) も含める。

6-1. 目標 CPG パラメータ(PG16・両環境共通の設計値)

現状 本番 us-east-1 は default CPG(pgaudit なし・logical off) のため、dr-work-record と同方針で custom CPG を作成する(staging は既存 PG13 custom を PG16 family に引き継ぐ)。

パラメータ理由
shared_preload_librariespgaudit,pg_stat_statementspgaudit セッション監査 + 統計
pgaudit.logall監査ログ有効化する
rds.logical_replication1BG レプリ用(dr-work-record 準拠)
wal_sender_timeout0BG レプリ切断防止
max_wal_senders20既存 staging/東京 custom に合わせる
max_slot_wal_keep_size1000slot WAL 保持

max_wal_senders の現状値の内訳:§2 SUMMARY の max_wal_senders:20DB 実行時値current_setting=Aurora の実効デフォルト)。一方 default.aurora-postgresql13パラメータ定義値は 10。本番 us-east-1 は default CPG のため「定義=10 / 実行時=20」の差がある。custom CPG で 明示的に 20 に設定して定義値と実行時値を一致させる(max_replication_slots も同様に 20)。

  • pgaudit は shared_preload_libraries + reboot で有効化CREATE EXTENSION 不要・session 監査)。pg_stat_statementsCREATE EXTENSION 必要。
  • password_encryptionmd5 維持(scram 強制しない)=アプリ断回避。

7. 移行前ベースライン(全16テーブル件数・2026-08-13 実測)

Switchover 後に同一 query で件数照合する(pg16-verify-*.sh の 2b/2c 相当)。

テーブル件数テーブル件数
appointment_histories533,521appointment_patient_histories273,671
appointment_health_insurance_histories336,821appointments268,015
pick_up_pharmacy_histories292,800clius_kartes236,116
appointment_online_videos236,177appointment_required_actions100,147
patients69,389appointment_cancels65,211
doctor_appointment_blocks27,847image_files8,932
appointment_medical_subsidy_certificate_histories770doctors622
_prisma_migrations27appointment_email_verifications0

dr-work-record より大きい(最大 53万行・計 ~1.4GB)。exact count(*) は動作確認済だが照合時間は長め(必要なら n_live_tup 近似へ切替)。


8. 所見・要対応・残タスク

難易度:中(medium)(skill risk:medium)。根拠=Reader あり+Global DB+CPG 付替(dr-work-record と同型)。FAIL=0・ブロッカーなし。app 面は grep 済でクリア(生SQL 標準のみ・書き手 mental-appointment-service=Prisma 5.13.0/読み手 online-ops=Kysely+pg)だが、**中の主因はインフラ(Reader/Global)**で app ではない。

✅ クリアFAIL=0(skill)/ logical=off・slot/publication/subscription/2PC=0(CDC 消費者なし)/ 長時間 tx=0(8.4日は誤検知・訂正済)/ md5 認証維持でアプリ断なし / 本体 mental-appointment-service(Prisma 5.13.0)・生SQL 33件も PG16 安全構文のみで互換 ◎ / 踏み台→DB 直アクセス可。

⚠️ 要対応(next: reader-plan / custom-cpg / app-grep)

  1. custom CPG 作成(pgaudit 込み・#17388/#17389)。default のため必須。
  2. Reader→Writer 順の再起動計画(members=2・AutoScaling 考慮)。
  3. 踏み台→ALB(80) SG(本番=#17398・staging=#17394/#2621)を作成し ②③ を検証。

✅ 解決済(#17281)

  • Retool 接続元=fd-platform-retool SG(JDBC)と特定(§3)。
  • CIDR 10.10.16.0/20 / 10.10.0.0/20 の実体=fd-platform-vpc(10.10.0.0/16・ap-northeast-1) のクロスリージョン peering と特定(§3)。
  • consumer 確定:アプリ①mental-appointment-service ②online-ops-service + SaaS③Retool ④Trocco + 踏み台。ai-triage/dr-shift/payment は非接続で確定。

✅ 解決済(#17281・§9 参照):target 16.x 確定 / federation gateway 特定+③read query / 東京 consumer 移行後再確認手順 / テスト予約データ作成手順。

□ 残タスク(#17281):(上記4件は §9 で解決。以降は移行実施フェーズで対応)

  • staging/本番 手順書本体(#17390/#17391/#17392)への反映。

9. 移行後検証・E2E 詳細手順(#17281 残タスク回答)

9-A. target engine version(16.x)確定

aws rds describe-db-engine-versions --engine aurora-postgresql --engine-version 13.23ValidUpgradeTarget(us-east-1・2026-08-14 再実測):

選択肢状態
16.14採用推奨(現状の最新 available な有効 BG ターゲット・status=available
16.13利用可(available)。dr-work-record 実績=13.23→16.13
16.11可(旧)
16.15Aurora(us-east-1) に未提供None

target = 16.14。マイナー差=bug/security 修正の累積のみ(新機能・非互換なし)で、BG 手順・CPG 設計(同 aurora-postgresql16 ファミリ)・アプリ互換に差は出ない。16.14 は 16.13 の修正を全て含み、サポート/自動アップグレード猶予が長い。BG は Green で検証してから切替えるため「最新採用」のリグレッションリスクは緩和される。

  • dr-work-record は 16.13 だが、mental-appointment 実施時点で 16.14 が available のため最新を採る(16.13 に揃える積極的理由=16.14 の既知 Aurora リグレッション等は現時点で無し)。
  • ⚠️ 本番実施の直前に describe-db-engine-versions で再確認(16.15 等が Aurora に来ていれば候補に加える)。

9-B. federation gateway 特定 + ③ 用 read query

  • 上流 gateway = chronic-apimental-online-karteHive Gateway / @theguild/federation-compositionchronic-api/gateway.config.ts / compose-supergraph.ts)。各 subgraph を supergraph に合成。認証は /auth 発行の CAT(x-access-token)を subgraph へ伝播
  • subgraph = mental-appointment-service/graphqlYogaFederationDriver)。
  • ③ 用 read query(app→DB フルパス):業務 Query は全て @authenticated + @requiresScopes(未認証の DB 読取 query は無い)。用途別に2段構え:
目的クエリ認証DB 到達
GraphQL 層 疎通(②相当){ _service { sdl } }不要✗(SDL 返すのみ)
app→DB フルパス(③){ doctors { id } }doctors 表・622行)x-access-token=ServiceAccount CAT・scope read:Doctor
bash
# ③ フルパス(要 ServiceAccount CAT): 踏み台→ALB→subgraph→DB
curl -s -XPOST "http://<ALB>/graphql" -H 'content-type: application/json' \
  -H "x-access-token: <ServiceAccount CAT>" \
  -d '{"query":"{ doctors { id } }"}' -w ' [%{http_code}]'
# 未認証で層だけ確認(DBは触らない)
curl -s -XPOST "http://<ALB>/graphql" -H 'content-type: application/json' \
  -d '{"query":"{ _service { sdl } }"}' -w ' [%{http_code}]'

CAT が用意できない場合、③は gateway chronic-api 経由の実 UI 操作(患者/管理画面)+ DB 直 psql(§付録)+ Datadog で代替。他の read query 候補:appointment(id) / appointments(ids) / appointmentAvailabilities(startDate,visitType)

9-C. 東京(ap-northeast-1) クロスリージョン consumer 移行後再確認手順

BG Switchover では cluster endpoint / reader endpoint の DNS 名は保持(Green が同名を引き継ぐ)=接続文字列変更不要。切替時に既存接続は切断→再接続される。password_encryption=md5 も CPG 維持で認証断なし。以下を Switchover 後に確認:

#consumer再確認内容
1mental-appointment-service(本体・us-east-1 / ap-ne-1 ECS)両リージョン ECS healthy / /health 200 / { doctors }(③)/ Datadog エラー無
2online-ops-service(ap-ne-1)ECS healthy / ops API で予約データ read 成功 / reader cluster-ro- 依存の機能疎通 / Datadog
3Retool(SaaS)mental-appointment を参照する Retool アプリでデータ表示成功(JDBC 再接続)
4Trocco(SaaS・reader)次回スケジュール or テスト実行が成功(cluster-ro- 抽出)。転送先データ鮮度確認
  • 全 consumer が プライベート経路(同一VPC / クロスリージョン VPC peering・fd-platform-vpc 10.10.0.0/16)。NAT 非経由なので NAT 側の設定変更は不要。
  • reader endpoint 依存(②online-ops・④Trocco)があるため、Green の Reader も昇格後に稼働していることを確認。

9-D. staging UI から E2E 予約する手順(画面・ボタン・URL)

⚠️ 本番では実行しない。staging で PG16 移行前後の予約 CRUD を比較する。出典: Notion「時間帯予約QA進め方」「QA|メンタル最短予約」。

staging URL

画面URL
患者WEB(予約・時間帯予約v2)https://mental-reserve-patient.fstdr.app/v2/appointment/select-doctor-and-date
管理画面(医師スケジュール登録=空き枠作成)https://mental-reserve-admin.fstdr.app/admin/examination-schedule-management
管理画面(予約一覧・事務局キャンセル)https://mental-reserve-admin.fstdr.app/admin/appointments
事務局/カルテ(ops・CLIUS/カルテ確認)https://online-karte.fstdr.app/ops/mental

本番は https://mental-reserve-patient.fstdr.jp/appointment/...(テスト医師表示に ?_alk= allowlist)。

前提(空き枠が無いと予約できない):管理画面(examination-schedule-management)で QA テスト医師のシフトを登録して空き枠を作る。テスト医師アカウント(staging・慢性期予約専用)=Notion「時間帯予約QA進め方」の一覧(qatest_manseiki_31〜36@fastdoctor.jp / password1! / 医師ID 1190,1191,1198〜1201)。医師枠が全て未配になると患者画面に空き枠(△/◯)が出る。

患者WEB 予約フロー(ボタン実体)

  1. /v2/appointment/select-doctor-and-date を開く
  2. 訪問区分ドロップダウン 「選択してください」「メンタルオンライン診療-初診」 選択 → 「決定」 ボタン
  3. カレンダーの 空き日時セル(△/◯)をタップdata-slot-status="t"=予約可)
  4. 「患者情報入力へ」 ボタン
  5. (未認証時)ログイン:電話番号入力 → 生年月日選択 → 送信 → SMS PIN 入力で認証
  6. 患者選択(select-patient)→ 患者を選択
  7. 患者情報入力 → 次へ / 住所 → 次へ / 薬局情報 → 次へ
  8. 確認画面:「利用規約とプライバシーポリシーに同意する」 チェック → 「同意して申し込む」 ボタン
  9. 完了:「予約が完了しました / 仮予約が完了しました」 表示

PG16 移行 E2E(staging・Switchover 前後で同一手順)

  • 予約作成(上記)→ DB 反映確認appointments/appointment_histories に行追加)→ 予約詳細/編集/キャンセル(患者マイページ・admin 事務局キャンセル)→ ops でカルテ作成・CLIUS 送信確認。
  • 移行前後で「予約作成成功・件数増加・カルテ/CLIUS 正常」を比較。差異が無ければ app→DB 経路健全。

参考(プログラム的手段):GraphQL createAppointment(input: CreateAppointmentInput!)(scope crate:Appointment・要 CAT)。appointmentAvailabilities(startDate,visitType) で空き取得 → createAppointment(businessDate,startAt,doctorId,visitType,patient/appointmentPatientId)。既存 E2E 実装: chronic-phase-appointment-servicetest/post-appointments.e2e-spec.ts(Playwright ページオブジェクト: SelectDoctorAndDatePage/ConfirmPage 等)。

  • ⚠️ saku:create-test-reception skill は オンライン診療(急性期)receptions 用backend-stg.fstdr.jp)で 別サービス→流用不可
  • ⚠️ Feature Flag(ARCH-43-enable-query-appointment-v2-*)の状態で v2 予約の挙動が変わる。QA 前に FF 状態を確認。
  • 後片付け:患者マイページ or admin 事務局キャンセル。

付録:踏み台経由 read-only 接続(コピペ可)

⚠️ Secret mental-appointment は値に制御文字を含むため、Python の strict JSON パースが失敗する(JSONDecodeError: Invalid control character)。strict=False 必須(dr-work-record の Secret とは形式が異なる)。

bash
export AWS_PROFILE=production-admin
R=us-east-1; BASTION=i-09384db1d690bcfd0; SECRET=mental-appointment; CL=mental-appointment; LPORT=15468
# 1) 踏み台→DB ポートフォワード
nohup aws ssm start-session --region $R --target $BASTION \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters "{\"host\":[\"$(aws rds describe-db-clusters --region $R --db-cluster-identifier $CL --query 'DBClusters[0].Endpoint' --output text)\"],\"portNumber\":[\"5432\"],\"localPortNumber\":[\"$LPORT\"]}" > /tmp/pgt-$LPORT.log 2>&1 &
for i in $(seq 1 20); do grep -q "Waiting for connections" /tmp/pgt-$LPORT.log && break; sleep 1; done
# 2) Secret パース(★strict=False)
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.loads(sys.stdin.read(),strict=False)["DB_USERNAME"])')
export PGPASSWORD=$(echo "$SEC" | python3 -c 'import sys,json;print(json.loads(sys.stdin.read(),strict=False)["DB_PASSWORD"])')
export PGDATABASE=$(echo "$SEC" | python3 -c 'import sys,json,urllib.parse as u;print(u.urlparse(json.loads(sys.stdin.read(),strict=False)["DATABASE_URL"]).path.lstrip("/"))')
export PGHOST=127.0.0.1 PGPORT=$LPORT
# 3) preflight(read-only)
psql -tAF'|' -c "SELECT current_setting('server_version'), current_setting('rds.logical_replication'), current_setting('password_encryption'), current_setting('shared_preload_libraries');"
# 4) 全テーブル件数(exact・書込なし)
psql -tAF' ' -c "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"
# 5) 後片付け
pkill -f "localPortNumber.*$LPORT"