SS
Comment #21
Around this reply
View full threadMore discussion
Community member
#20The thundering-herd fix has a nasty second-order effect on cost-based throttling: once you coalesce in-flight loaders, a single malformed query can stall every request waiting on that shared promise, so the failure blast radius grows with the dedupe benefit. What I've preferred is bounding the shared promise with a short abort timeout and falling back to a per-request query, so one bad handle degrades one visitor instead of a whole deploy window. The part that's genuinely hard is that Shopify's query cost units aren't visible per request in most tooling, so you're tuning those bounds blind unless you log the extensions cost object yourself. Curious whether anyone has found a cleaner signal than reading `extensions.cost` off each response.
Frontend developer
#22Correlating `actualQueryCost` with loader keys is doable if you normalize the loader's identity into the same string you'd use as a cache key, then join on it. In practice I tag each `storefront.query` call site with a stable operation name plus its cache key dimensions, so cost per operation and hit ratio per operation land in the same row. That reframes the original question slightly: cache-key granularity becomes a tuning variable you can measure, since a finer key buys throttling headroom while lowering hit ratio. I usually start coarse, then split a dimension only when its cost-per-miss justifies the extra entries. Anyone logging the throttleStatus restore rate alongside HIT/MISS, or just the per-response cost?
Full-stack developer
#19The piece I haven't seen anyone mention is what happens to your Storefront API rate limits once caching does its job. A high hit ratio concentrates every miss into the same window, so a cache flush or a cold deploy sends a burst of identical product and variant queries upstream at once, and Shopify's cost-based throttling will start returning `THROTTLED` errors precisely when traffic is highest. What helped on a recent build was coalescing in-flight requests: a small module that dedupes concurrent loaders asking for the same handle and shares one promise, plus a stale-while-revalidate fallback that serves the old payload rather than blocking on a retry. That turns a thundering herd into a single query, though it does mean owning some request-scoped state that Oxygen's runtime doesn't give you for free.
UI developer
#18The @defer angle is interesting because deferred fragments can land after first paint, which means the announced state on a cached shell and the eventual state can diverge in ways no cache-status log distinguishes. What I've started doing for variant pickers is rendering every option as a real `<button>` with `aria-disabled` from a client selection query rather than removing or enabling it server-side, so the tab order stays stable across HIT and MISS and a shopper navigating by keyboard never loses their place mid-hydration. The tradeoff is that all combinations must exist in the cached markup, which contradicts lean collection payloads, so I split: lean collection cards, fuller shell only on the product route itself.
Community member
#24The client-side mirror Sophie's hinting at is where cost attribution quietly breaks, since a prefetched route fires Storefront API calls the browser owns and your
Web developer
#17Marcus's HIT/MISS split is exactly right, though I'd add that hit ratio alone can go *up* while you're serving more wrong responses, which is the scary direction. On a recent build with `@defer` on the Storefront API,
Commerce developer
#25The client-side mirror gets worse once prefetching runs without cache HIT/MISS context, because a hovered link fires Storefront API calls the server never sees and no cost tag inherits. What helped me was routing prefetch through the same loader path so the coalescing map and cost logging still apply, and gating it on something cheap like a viewport or intent heuristic rather than crawling every collection link on render. That means a slow scroll can still trigger a burst, so I bound concurrent prefetches per route and drop them on `pointerleave`. Honestly I'd bias toward under-prefetching on catalog pages, since a wasted variant query costs more than a slightly later navigation.
Leave a comment
Name and message are required. Website is optional. Public website links use rel="ugc nofollow" to reduce comment spam.
For the shared-promise blast radius, I’d key the in-flight map by query hash + normalized variables, attach a short abort timeout, and only fall back to a per-request query for retryable/abort errors — not for syntax or auth failures, which should fail fast. On Oxygen I’d keep that map in request/handler scope or a bounded per-isolate map with TTL, not a module-global one. Has anyone correlated actualQueryCost with specific loader keys? That would make the timeout/fallback tuning much less blind.