Skip to content

PUL-22: replace whole-snapshot Redis cache with cheap, targeted reads - #15

Merged
nitheeshdr merged 1 commit into
mainfrom
fix/pul-22-snapshot-cache-redesign
Sep 7, 2026
Merged

PUL-22: replace whole-snapshot Redis cache with cheap, targeted reads#15
nitheeshdr merged 1 commit into
mainfrom
fix/pul-22-snapshot-cache-redesign

Conversation

@nitheeshdr

Copy link
Copy Markdown
Collaborator

Summary

The Redis whole-snapshot cache shipped earlier for PUL-22 doesn't survive real scale — a real 22,210-order store (naturesjoystore.com) serializes to 188.31MB, which blows past Upstash's entire free-tier storage quota and times out on every write, confirmed by testing against the live store. The write was failing silently (by design), so nothing was actually being cached.

  • getAnalyticsForVersion() checks the existing Postgres-bytea shared analytics cache using just {storeUrl, lastSyncAt} — no query, both already on hand — before paying for a full snapshot reassembly. /api/analytics and /api/reports/export now use this fast path.
  • readProducts(), readMostRecentCurrency(), readPhoneByCustomerKey() answer the product picker and inbox chat-enrichment directly, without pulling in the whole order history.
  • New migration adds a partial expression index on woo_orders' billing phone — cuts the phone lookup from ~8s to ~1.7s on the real store (already applied to production).
  • Sync routes now warm the shared analytics cache instead of the raw snapshot.

Follow-up (not blocking this PR)

Testing also surfaced that a full readSnapshot() can now exceed Postgres's own 2-minute statement timeout outright (TOAST decompression on ~121MB of compressed order JSON) — independent of caching. This PR makes that path far less frequently hit (once per sync cycle instead of once per request), but doesn't fix the underlying read itself. Tracked separately.

Test plan

  • npm run typecheck — clean
  • npm run lint — clean (only pre-existing warnings)
  • Verified readProducts/readMostRecentCurrency/readPhoneByCustomerKey against real production data (naturesjoystore.com, 22,211 orders): 392ms, 213ms, 1.7s respectively
  • Verified getAnalyticsForVersion writes to and reads from the shared Postgres-bytea cache correctly
  • Migration applied to production and recorded in supabase_migrations.schema_migrations

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

The Redis snapshot cache shipped earlier for PUL-22 doesn't survive real
scale: a real 22,210-order store serializes to 188.31MB, which blows past
Upstash's entire free-tier storage quota and times out on every write --
confirmed by testing against a live production store. Every write was
silently failing (by design, so a cache failure never breaks a request),
which meant the cache was never actually caching anything for this store.

Replaces it with three narrower fixes instead of a bigger Redis blob:

- getAnalyticsForVersion() (analytics/cache.ts) checks the existing
  Postgres-bytea shared analytics cache using just the store's URL and
  last-sync timestamp -- both already on hand, no query required -- before
  paying for a full snapshot reassembly. /api/analytics and
  /api/reports/export now use this fast path; on a cache hit neither ever
  touches the raw order/customer/product mirror.
- readProducts(), readMostRecentCurrency() and readPhoneByCustomerKey()
  (mirror.ts) answer the product picker and inbox chat-enrichment routes
  directly from narrow, targeted queries instead of pulling in the entire
  order history to answer a question about 53 products or a phone number.
- A partial expression index on woo_orders' billing phone (new migration)
  cuts readPhoneByCustomerKey from ~8s to ~1.7s on the real store.

The sync routes now warm the shared analytics cache after a sync instead of
the raw snapshot, since that's what the hot paths actually read.

Testing against naturesjoystore.com (22,211 real orders) also surfaced a
separate, deeper issue: a full readSnapshot() can now exceed Postgres's own
2-minute statement timeout outright (TOAST decompression on ~121MB of
compressed order JSON), independent of any caching layer. This change makes
that path far less frequently hit -- once per sync cycle instead of once per
request -- but doesn't fix the underlying read itself; tracked as a
follow-up, not blocking this fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pulse-commerce Ready Ready Preview Sep 7, 2026 6:16pm UTC

@nitheeshdr
nitheeshdr merged commit 81b25a4 into main Sep 7, 2026
7 of 10 checks passed
@nitheeshdr
nitheeshdr deleted the fix/pul-22-snapshot-cache-redesign branch September 7, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant