Skip to content

PUL-22: cache the store snapshot on Upstash Redis, fix two uncached hot paths - #14

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

PUL-22: cache the store snapshot on Upstash Redis, fix two uncached hot paths#14
nitheeshdr merged 1 commit into
mainfrom
feat/pul-22-snapshot-cache

Conversation

@nitheeshdr

Copy link
Copy Markdown
Collaborator

Jira issue

PUL-22

Summary

Real Supabase egress-quota restriction today traced to readSnapshot()
reassembling the whole order/customer/product mirror from Postgres on
every cache miss. Three real fixes:

  1. /api/whatsapp/chats -- polled every 30-60s per open Inbox tab with
    zero caching and an explicit no-store. Almost certainly the
    dominant driver of today's incident. Fixed with the same
    Cache-Control pattern already used on /api/analytics.
  2. /api/whatsapp/products -- same gap, hit on debounced keystrokes.
  3. The real fix: readSnapshot() now checks Upstash Redis (free, no
    card required -- R2 was the first choice but demands a payment
    method even on its free tier) before touching Postgres. The cron
    sync route proactively warms it right after each sync.

Also fixed two stale docs found along the way (SNAPSHOT_CACHE_MINUTES
default mismatch, a kv_store comment describing a table that no
longer exists).

Checks

  • npm run typecheck
  • npm run lint (0 errors, 5 pre-existing warnings unrelated)
  • Upstash REST API shape verified against real credentials (SET/GET/DEL)
  • Not yet verified against a real synced store -- the fresh
    Supabase project (today's other recovery) has no connected
    stores yet, so the actual cache hit/miss/warm path in
    readSnapshot() hasn't been exercised end-to-end for real.

…ot paths

Real Supabase egress-quota restriction (service outage) today traced
back to readSnapshot() in src/lib/woo/mirror.ts, which reassembles a
store's entire order/customer/product history from Postgres on every
cache miss -- tens of MB on a real store per that file's own comment.
The in-process memo only helps a warm serverless instance, which per
that same comment is the exception on Vercel, not the rule.

Three real, uncached/undercached hot paths found and fixed:

1. /api/whatsapp/chats -- polled every 60s (was 30s) by any open Inbox
   tab, paid the full readSnapshot() cost every single time with zero
   HTTP caching, and the client explicitly forced cache: "no-store".
   This was almost certainly the dominant driver of today's incident:
   every open inbox tab, every 30-60s, for as long as it stayed open.
   Fixed: same private + Vary: Cookie Cache-Control already used on
   /api/analytics and /api/customers/[key], no-store removed
   client-side so the browser can actually use it, poll interval
   lengthened to match the cache window.

2. /api/whatsapp/products -- hit on every 250ms-debounced keystroke in
   the campaign product picker, no caching at all. Same treatment,
   max-age=120 since a catalogue changes far less often than a chat
   list.

3. The deeper fix: readSnapshot() now checks a Redis-backed cache
   (src/lib/storage/snapshot-cache.ts, Upstash's free tier, confirmed
   no card required) before ever touching Postgres, and writes back on
   a miss. forgetSnapshot() invalidates it. The cron sync route
   proactively warms it right after each store's sync -- the one place
   a full Postgres reassembly is actually expected, once per store per
   10-minute cycle, not once per user-facing request. R2 was the first
   choice (also zero egress) but requires a payment method to enable
   even on the free tier; Upstash doesn't.

Also fixed two stale docs found along the way: README.md's
SNAPSHOT_CACHE_MINUTES default said 60, code and .env.example say (and
mean) 10. .env.example's kv_store comment described a table dropped in
the multi-tenant migration and never recreated -- corrected to
describe where WooCommerce credentials and the mirror actually live
now, and that nothing in the app currently reads
SUPABASE_SERVICE_ROLE_KEY at all.

Not yet verified against a real synced store -- the fresh Supabase
project (today's other recovery) has no connected stores yet. Checks
(typecheck, lint, the Redis REST API shape) all pass; the actual
readSnapshot() cache hit/miss/warm path needs a real store connected
and synced to exercise for real.

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 5:30pm UTC

@nitheeshdr
nitheeshdr merged commit 436cbc7 into main Sep 7, 2026
7 of 10 checks passed
@nitheeshdr
nitheeshdr deleted the feat/pul-22-snapshot-cache branch September 7, 2026 17:30
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