Summary
sessionStorage runs at roughly 85% of quota on a normal home-page load, and every write is
wrapped in a silent try {} catch {}. When it tips over, caching stops working with no error,
no log and no user-visible symptom other than the app refetching several megabytes on every
page load.
Measured on the live site's production build:
| Key |
Size |
globalPerfRankings_v3 |
2,912 KB |
homeAppSpecs_v2 |
1,408 KB |
homeGeoCounts_v1 |
3 KB |
gpuPrices_v1 |
2 KB |
| Total |
4,325 KB |
Browser sessionStorage quota is typically ~5,120 KB per origin. Writing another 1.3 MB throws
QuotaExceededError immediately.
How it was found
While bumping homeAppSpecs_v1 → _v2 in #148. The bump left the old ~1.3 MB entry behind,
which was enough to push the new write past the quota. homeAppSpecs_v2 was silently never
written — the page rendered correctly and refetched every time, with nothing in the console.
It only became visible because the cache key was being explicitly checked.
That orphan is fixed in #148, but it exposed the underlying problem: there is no headroom.
Why it matters
- Two keys account for 4.3 MB and both grow with the network. Flux is at ~6,500 nodes and
~1,120 app specs today; neither figure is shrinking.
- Any future cache key bump repeats the same silent failure for the lifetime of the tab.
- Users on browsers with a smaller quota, or with other tabs on the same origin, lose caching
entirely and refetch ~4 MB per navigation.
- Because the catch is silent, nobody finds out.
Suggested directions
Roughly in order of value for effort.
1. Do not cache what can be recomputed. homeAppSpecs stores rawSpecs — the entire
globalappsspecifications response — but the consumers need a small derived subset. #148 took
this approach for fluxinfo: it caches a ~365-entry aggregate rather than the ~465 KB payload,
and recomputes categories on read. The same applies here.
2. Trim globalPerfRankings_v3. At 2.9 MB it is the single biggest consumer. Worth checking
whether the whole ranking set is needed client-side or only the top N plus the searched wallet.
3. Make the failure loud. Replace the silent catch with something that at minimum
console.warns on QuotaExceededError, and consider evicting the largest stale entry and
retrying once.
4. Consider IndexedDB for the large payloads. localforage is already a dependency and is
already used for appStore. IndexedDB quotas are orders of magnitude larger and it is async,
which suits payloads of this size better than a synchronous multi-megabyte
JSON.stringify on the main thread.
5. Add a size guard. Refuse to write entries over a threshold rather than attempting and
silently failing, so the behaviour is deliberate.
Acceptance criteria
Reproduction
// on https://fluxnode.app.runonflux.io/#/home, after the page settles
Object.keys(sessionStorage).map(k => ({ key: k, kb: Math.round(sessionStorage.getItem(k).length / 1024) }))
try { sessionStorage.setItem('__probe', 'x'.repeat(1.3 * 1024 * 1024)) } catch (e) { e.name }
// -> "QuotaExceededError"
Summary
sessionStorage runs at roughly 85% of quota on a normal home-page load, and every write is
wrapped in a silent
try {} catch {}. When it tips over, caching stops working with no error,no log and no user-visible symptom other than the app refetching several megabytes on every
page load.
Measured on the live site's production build:
globalPerfRankings_v3homeAppSpecs_v2homeGeoCounts_v1gpuPrices_v1Browser sessionStorage quota is typically ~5,120 KB per origin. Writing another 1.3 MB throws
QuotaExceededErrorimmediately.How it was found
While bumping
homeAppSpecs_v1→_v2in #148. The bump left the old ~1.3 MB entry behind,which was enough to push the new write past the quota.
homeAppSpecs_v2was silently neverwritten — the page rendered correctly and refetched every time, with nothing in the console.
It only became visible because the cache key was being explicitly checked.
That orphan is fixed in #148, but it exposed the underlying problem: there is no headroom.
Why it matters
~1,120 app specs today; neither figure is shrinking.
entirely and refetch ~4 MB per navigation.
Suggested directions
Roughly in order of value for effort.
1. Do not cache what can be recomputed.
homeAppSpecsstoresrawSpecs— the entireglobalappsspecificationsresponse — but the consumers need a small derived subset. #148 tookthis approach for fluxinfo: it caches a ~365-entry aggregate rather than the ~465 KB payload,
and recomputes categories on read. The same applies here.
2. Trim
globalPerfRankings_v3. At 2.9 MB it is the single biggest consumer. Worth checkingwhether the whole ranking set is needed client-side or only the top N plus the searched wallet.
3. Make the failure loud. Replace the silent catch with something that at minimum
console.warns onQuotaExceededError, and consider evicting the largest stale entry andretrying once.
4. Consider IndexedDB for the large payloads.
localforageis already a dependency and isalready used for
appStore. IndexedDB quotas are orders of magnitude larger and it is async,which suits payloads of this size better than a synchronous multi-megabyte
JSON.stringifyon the main thread.5. Add a size guard. Refuse to write entries over a threshold rather than attempting and
silently failing, so the behaviour is deliberate.
Acceptance criteria
after a load, not just assumed
Reproduction