Database operation comparison

DB操作・処理の新旧比較

2026-09-29時点の組織全体スナップショット方式と、現在の対象別クエリ・Entity Transaction方式を比較します。

この資料は、業務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時点の旧方式#

2026-09-29時点のDB操作・処理。readは組織全体snapshot、writeは組織共通lock内のsnapshot mutation、AI Workerも実行文脈と結果反映で全snapshotに依存する。

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の影響を受ける構造でした。

現在の方式#

現在のDB操作・処理。readはroute・target別の限定query、writeはentity transactionと行lockまたはCAS、AI Workerは対象文脈だけを読みprovider I/Oをtransaction外で実行する。

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 queuePostgreSQL永続job + claim/lease同じ基本構造を維持し、release fenceとclaimを強化
AI入力組織全snapshotから対象を解決対象workflow graphと直近履歴だけ
OpenAI待機中のDB transactionprovider呼出し自体は別処理だが、前後が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は別途確認が必要です。