perf: cache SSR data fetching to eliminate upstream pool fanout - #31
Merged
Merged
Conversation
anyxem
approved these changes
Sep 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Homepage and /pools SSR took 12-18s TTFB. Each render issued ~177 upstream calls (measured via a request-counting proxy in front of the testnet api-web-server):
fetchAllPoolsFromApipaged/pool?offset=N10 items at a time → 34 sequential calls per crawl/pool/{id}/delegationscall per pool (155 pools) per crawl/poolscrawled twice per render (summary + list each fetched independently)/block/{id}fetches for the recent-blocks widgetDATABASE_URL) none of this was cached — every render paid the full costFix
src/lib/server-cache.ts(new): in-process TTL cache with in-flight de-duplication and stale-while-revalidate, persisted onglobalThisso page bundles and route-handler bundles in the same server process share one store (same pattern as the existing pg pool singleton inlib/postgres.ts).src/lib/explorer-ssr.ts: single sharedgetPoolsSnapshot()(120s SWR) now feeds the homepage summary,/pools,/api/pool/listand/api/pool/summary; computed pool summary cached 30s, mempool 10s, transaction total 60s.src/lib/explorer-source.ts:items=100pages fetched in adaptive parallel rounds with retries (upstream breaks foritems > 100; verified)/chain/{height}id lookups +Promise.allblock fetches (sequential previous-block walk kept as fallback for older API versions)chain/tipcached 5s, recent transactions 10s, immutable block/height lookups 1h (in-memory + Nextforce-cachedata cache)/api/pool/list,/api/pool/summary,/api/block/tip: route through the shared cached loaders; response shapes unchanged./api/block/tipstays fast under k8s probe hammering (in-flight dedup, no locks).Compatibility
Measurements (testnet API via local counting proxy)
/api/block/tipunder 20-way hammeringCold is bounded by one shared crawl (~4.5s through a latency-adding local proxy; lower against production).
next build,tsc --noEmitand 14/14 jest tests pass (4 new tests cover the cache: dedup, TTL, SWR, error propagation).