この資料は、業務PostgreSQLへのアクセス方式を、2026-09-29時点と現在で比較します。旧方式の基準はGit tag v2026.09.29(commit eba90cd721d3905ae2d09ae05d2e8db94606259f)、現在の基準は2026-10-01のmain(commit 30b101f70ea31c11706ea148f7272fbc29053a69)です。
対象は、かけあいWebとAI Workerが扱う業務PostgreSQLです。Cloudflare Auth WorkerのD1操作は、別のデータストア・実行環境であるため、この比較には含めません。
結論#
9月29日時点は、組織全体を1つのAppData snapshotとして読み、更新時には組織共通lockの中で全量読込・clone・差分書込を行う組織全体スナップショット + 粗粒度ロック方式でした。
現在は、画面・API・AI処理ごとに必要な行だけを読み、更新対象の行だけを短いtransactionでlockまたはCASする対象別クエリ + Entity Transaction方式です。異なるentityの更新は互いに待たず、同じentityの競合だけを409または再試行可能な失敗として扱います。
2026-09-29時点の旧方式#
Read#
画面やAPIはwithPostgresStoreRequestを通り、最初に組織revisionを確認します。同じrevisionのimmutable snapshotがcacheにあれば再利用しますが、cache missまたはrevision更新後は、組織に属する41の業務表を読み、全体をAppDataへdecodeします。route handlerは、その大きなオブジェクトから対象ユーザーや成果物を配列検索していました。
したがって「全要求が必ず41表を物理読込する」という意味ではありません。revision別cacheと同時読込のcoalescingはありました。ただし、更新で組織revisionが進むとsnapshotを作り直すため、更新頻度と組織内データ量の影響を受けます。
Write#
同じ組織の更新は、対象entityが別でもpg_advisory_xact_lockを共有します。lock取得後に全AppDataを読み、structuredCloneしたdraftへdomain operationを適用し、変更されたaggregateだけをUnit of Workとして書き込みます。最後に組織revisionを増やし、全AppDataをもう一度読み直して整合性を確認します。
旧方式は「全表を毎回置換する方式」ではありません。書込自体は変更範囲へ絞られていました。問題は、組織共通lockのcritical sectionに、全量読込・clone・差分判定・全量再検証が含まれていたことです。
同一組織のwriteが並ぶと、lock待ちのtransactionがDB connectionを保持します。調査記録では、組織lock待ち、2秒のlock timeout、pool枯渇、5秒のpool取得timeoutを経て、read・認証・readinessにも500/503が波及する連鎖が確認されています。
AI Worker#
永続command/job、claim、leaseは9月29日時点ですでに存在しました。AI処理を同期HTTPの中で最後まで実行する方式ではありません。
一方で、Workerが生成・feedbackの入力文脈を解決するときは組織全snapshotを読み、結果のmaterializeにもsnapshot mutationを使っていました。永続queueがあっても、前後のDB処理は組織全体のデータ量と共通lockの影響を受ける構造でした。
現在の方式#
Read#
主要画面と通常APIは、最初にrequest identityからactorを解決し、repository内でsession、組織、role、owner、teamなどのlive DB認可を再確認します。その後、routeや対象に応じて必要な行だけを取得します。
これは「1 request = 1 SQL」という意味ではありません。たとえばdashboardやAI入力は、認可、対象成果物、依存関係、最近の履歴を、同じ短いread transaction内の複数SQLで組み立てます。違いは、組織全体を読むのではなく、対象IDと認可条件をqueryへ含め、履歴にLIMITやcursor paginationを使うことです。
既存の画面selectorを安全に再利用する箇所では、取得した行だけを小さなAppData互換projectionへdecodeします。型がAppDataでも、組織全snapshotではありません。
Write#
通常更新は機能別のentity repositoryへ入ります。transaction内でlive認可とruntime fenceを確認し、対象行だけをFOR UPDATE等でlockするか、organization_id、entity ID、revision、leaseなどを条件にしたUPDATE ... RETURNINGでCASします。
更新対象に応じて、履歴、監査、outbox、job状態も同じtransactionで確定します。組織全体のsnapshotは作らず、通常のentity更新ではorganizations.revisionも増やしません。そのため、異なるentity A/Bは独立して進み、同じentityの競合だけが直列化されます。
通常production HTTPから旧snapshot mutationへ到達した場合は、handler本体を実行せず503で拒否します。全量snapshot repositoryは削除されておらず、bootstrap、import、保守処理と非本番fallbackに用途を限定して残っています。
AI Worker#
WebはPostgreSQLへ永続command/jobをenqueueし、WorkerはFOR UPDATE SKIP LOCKEDとleaseで1件をclaimします。Workerは対象artifactまたはworkflow sessionを起点に、所属team、参照元、直近messageなど、provider入力に必要なgraphだけを読みます。
OpenAI呼び出しはDB transactionの外で実行するため、応答待ちの間にDB connectionを保持しません。応答後はleaseと現在の対象状態を検証し、対象別transactionで結果とjob状態を確定します。
AI enqueueのsingle-flightなど、対象を限定したadvisory lockは現在も使います。また、複数Workerのjob claimにはSKIP LOCKEDを使います。「advisory lockを一切使わない」のではなく、通常の利用者更新を組織全体の1本のlockへ集約しない設計です。
DB接続の保護#
DB poolのcheckout前にbounded admissionを置き、通常要求の同時数と待機数を制限します。認証・readiness等のcritical処理には予約枠を残し、queue上限または待機期限を超えた要求は、pool内部へ無制限に積まず、Retry-After付きの503へ変換します。
比較表#
| 観点 | 2026-09-29時点 | 現在 |
|---|---|---|
| 一般的な方式名 | 組織全体スナップショット + 粗粒度ロック | 対象別クエリ + Entity Transaction |
| Readの範囲 | revision別cache miss時に組織内41業務表を読込 | route、actor、team、artifact等の対象行だけ |
| 履歴 | snapshotへ全履歴を展開 | cursor/LIMITで必要な最近履歴を取得 |
| 画面用データ | 全AppDataからメモリ内検索 | 小さなDTOまたはAppData互換projection |
| Writeの排他単位 | organization共通advisory transaction lock | 対象行lock、entity revision/lease CAS |
| Write内容 | 全量read・clone後、変更aggregateを書込 | 対象行と必要な関連行だけを書込 |
| 独立entityの同時更新 | 同じ組織なら共通lockで待つ | 原則として互いを待たない |
| 同一entityの競合 | 組織revisionまたはsnapshot整合性で検出 | revision/lease条件で1件だけcommit、他は409等 |
| AI queue | PostgreSQL永続job + claim/lease | 同じ基本構造を維持し、release fenceとclaimを強化 |
| AI入力 | 組織全snapshotから対象を解決 | 対象workflow graphと直近履歴だけ |
| OpenAI待機中のDB transaction | provider呼出し自体は別処理だが、前後がsnapshot依存 | provider I/Oは明示的に対象read transaction外 |
| 結果反映 | snapshot mutation | 対象別materialize transaction |
| 過負荷制御 | lock待ちがpoolへ波及 | checkout前のbounded queue、critical予約枠、Retry-After |
| 全量snapshotの現在位置 | 通常read/writeの中心 | import・保守・bootstrap・非本番fallbackに限定 |
根拠と再現方法#
旧方式は、現在のファイル名だけから推測せず、tagに固定したソースを確認しました。主な確認箇所は次のとおりです。
git show v2026.09.29:okr-cai-saas/src/lib/postgres-request-store.ts
git show v2026.09.29:okr-cai-saas/src/lib/db/postgres/repository.ts
git show v2026.09.29:okr-cai-saas/src/lib/db/postgres/reader.ts
git show v2026.09.29:okr-cai-saas/src/lib/ai/generation-job.ts
git show v2026.09.29:okr-cai-saas/src/lib/ai/feedback-job.ts
現在方式の主な根拠は、okr-cai-saas/src/lib/db/postgres/dashboard-reader.ts、postgres-entity-api.ts、profile.ts、workflow-collaboration.ts、ai-workflow.ts、ai-generation-jobs.ts、pool-admission.tsです。移行理由、負荷時の障害連鎖、実装・Staging検証結果は、docs/engineering/history/100-concurrent-db-remediation-plan-2026-09-30.mdに記録されています。
確認範囲#
現在方式はリポジトリの実装と学生側Stagingの試験・公開配備記録まで確認しています。100/200同時、AI burst、2時間soak等の記録はありますが、この資料作成時に本番DBや稼働hostへ接続して再確認したものではありません。管理者ログインが必要な一部操作と、本番環境で現在動いているreleaseは別途確認が必要です。

