Summary
On a cold AccountEnrollment Durable Object, the /usage dashboard's parallel private reads (/api/usage/consent, /api/usage/stats, /api/usage/days, /api/usage/account) can all fail with unavailable: readEnrollmentStatus is a committing path that pays the one-time deferred history audit (~6.7 s on this account) inside transactionSync, and DO storage serializes — every concurrent read queues behind the audit and blows the transport's ~5 s stage / 15 s client budget.
Reproduced evidence (2026-09-21, production)
Fresh sign-in at 16:30:58, page auto-load at 16:31:00 fired account + consent + stats together on a DO idle ~13 min: stats returned unavailable and the consent panel stayed stuck on "Loading your publishing status." With the same account's DO pre-warmed/audited, the identical signed-in session's POST /internal/usage/stats + readUsageStats completed Ok at 17:00:04–05 (~sub-second).
So: cold load → failure; warm/audited → success. First page load after any idle eviction is the exposed case.
Impact
Every signed-in user's first dashboard visit after a DO idle eviction can render "Account usage could not be loaded" plus a stuck consent spinner — on an otherwise healthy system.
Possible directions
- Make
auditHistory interruptible/chunked across transactions so it cannot hold the storage gate for ~7 s; or
- Run the deferred audit under
blockConcurrencyWhile-free background control and let reads bypass it while mutations await it (reads already provably don't need it); or
- Sequence the dashboard's calls client-side (consent first, then stats/days) — cheap but only narrows the race; or
- Keep a per-account warm read replica path for pure reads.
Summary
On a cold
AccountEnrollmentDurable Object, the/usagedashboard's parallel private reads (/api/usage/consent,/api/usage/stats,/api/usage/days,/api/usage/account) can all fail withunavailable:readEnrollmentStatusis a committing path that pays the one-time deferred history audit (~6.7 s on this account) insidetransactionSync, and DO storage serializes — every concurrent read queues behind the audit and blows the transport's ~5 s stage / 15 s client budget.Reproduced evidence (2026-09-21, production)
Fresh sign-in at 16:30:58, page auto-load at 16:31:00 fired
account+consent+statstogether on a DO idle ~13 min: stats returnedunavailableand the consent panel stayed stuck on "Loading your publishing status." With the same account's DO pre-warmed/audited, the identical signed-in session'sPOST /internal/usage/stats+readUsageStatscompletedOkat 17:00:04–05 (~sub-second).So: cold load → failure; warm/audited → success. First page load after any idle eviction is the exposed case.
Impact
Every signed-in user's first dashboard visit after a DO idle eviction can render "Account usage could not be loaded" plus a stuck consent spinner — on an otherwise healthy system.
Possible directions
auditHistoryinterruptible/chunked across transactions so it cannot hold the storage gate for ~7 s; orblockConcurrencyWhile-free background control and let reads bypass it while mutations await it (reads already provably don't need it); or