CRUMB a card from devarno-cloud

Caching Strategy: What Is Actually Cached

smo1 intermediate 7 min read

ELI5

SMO1’s code has places to keep copies of link data close to the visitor, but the most important one, the edge copy, is switched off. Every short-link visit asks the API, and the API asks the database. A small cache inside the API exists, but it only helps a side route that real visitors rarely use. The dashboard keeps its own short-lived copies in the browser.

Technical Deep Dive

Source: zoomies-edge wrangler.toml:50-63, src/handlers/redirect.ts:83-121, src/index.ts:236-256 (origin/main 5bdde59); purr-api internal/service/redirect_cache.go:50-117, internal/service/link_service.go:183-187, 376, 416, 425-470, internal/database/redis.go:89-111, 142-149, internal/service/kv_sync_service.go:89-138 (bb6cfc2); meow-web src/components/providers.tsx:30, src/lib/auth.ts:49, src/lib/airlock.ts:38-65 (e5d808c), read 2026-10-25.

Layers as coded and as deployed

flowchart TB
subgraph Browser["meow-web browser tab"]
RQ["React Query, staleTime 60 s default, 5 min for the session query"]
end
subgraph MeowServer["meow-web server"]
JW["JWKS in module memory, 5 min"]
end
subgraph Edge["zoomies-edge"]
KV["LINKS_CACHE KV: NOT BOUND in lion"]
end
subgraph Purr["purr-api"]
RD["Redis link:v3:id, 5 min, only for /:identifier"]
RL["Redis rate:key:window counters"]
end
PG[(Postgres)]
RQ -->|"/api/purr"| Purr
KV -.->|"skipped"| Purr
Edge -->|"/api/links/slug and /api/links/resolve"| PG
RD --> PG
LayerWhereKey and TTLProduction status
React Querymeow-web browserstaleTime 60 s by default (providers.tsx:30), 5 min for the auth query, 0 on billingactive
JWKS cachemeow-web server memoryAirlock JWKS, 5 min (airlock.ts:38-65)active
Edge KVCloudflare KV LINKS_CACHElink:{host}:{slug} (redirect) or link:{slug} (proxy probe), 300 soff: binding commented out
Redis redirect cachepurr-apilink:v3:{identifier}, 5 min (redis.go:144-149)active, but only on purr-api’s own GET /:identifier route
PostgresRailwaysource of truthalways hit by the edge path

Why the Redis cache does not help smo1.io redirects

ResolveForRedirect, the only reader of the Redis cache (link_service.go:445-470), backs purr-api’s own GET /:identifier and /@:username/:slug routes. The Worker calls /api/links/slug/:slug (LookupBySlug) and /api/links/resolve (ResolveByHostAndSlug), which go straight to the repository (link_service.go:425-437). So the visitor path has no cache between the edge and Postgres.

The Redis cache is careful where it does run: it stores only links AuthorizeRedirect allows, never protected, paused or expired ones; it never caches a custom-domain link under its bare slug, only under its globally unique uid (redirect_cache.go:50-69); and update and delete call invalidateRedirect for the uid plus old and new slugs (link_service.go:376, 416).

Why the edge KV is off

purr-api’s KV sync writes keys named by the bare uid and custom slug, with a camelCase value {url, isActive, expiresAt, utm*} (kv_sync_service.go:38-49, 124-133). The Worker reads link:{host}:{slug} and expects the snake_case LinkResponse shape with destination, is_active, protection_type and redirect_mode. Neither keys nor values meet. wrangler.toml:50-55 records that enabling the binding as-is “would be a regression”. See smo1-015.

Rate-limit counters

Redis also holds fixed-window counters rate:{identifier}:{window} (redis.go:89-111). The edge has its own per-IP counter in the Cloudflare Cache API, 120 per minute (smo1-011).

Key Terms

  • LINKS_CACHE — the KV binding both edge lookups support; not bound in production
  • link:v3: — purr-api’s Redis key prefix for the redirect cache
  • ResolveForRedirect — the only Redis-cached lookup, behind purr-api’s own redirect routes
  • invalidateRedirect — deletes Redis entries for a link’s uid and slugs on update or delete

Q&A

Q: Name all four cache layers in order of closeness to the user. A: In the code: React Query in the browser, Cloudflare KV at the edge, the Redis redirect cache in purr-api, then Postgres as the source of truth (plus meow-web’s server-side JWKS cache, which is not a link cache). In production the KV layer is off and the Redis layer is not on the edge path.

Q: What is the TTL for KV link entries written by the edge worker? A: 300 seconds (expirationTtl: 300, redirect.ts:117) if it were bound. With LINKS_CACHE absent nothing is written.

Q: Which cache layer is invalidated when a link is updated? A: Redis: UpdateLink and DeleteLink call invalidateRedirect for the uid and slugs. The KV sync also re-puts or deletes its bare-slug keys when KV credentials are configured, but the Worker never reads those keys.

Q: Does any cache sit between a smo1.io redirect and Postgres today? A: No. Both edge lookups go to purr-api routes that read Postgres directly.

Examples

An owner changes a link’s destination. Redis entries for its uid and slug are deleted, purr-api’s own /:uid route re-reads Postgres on the next hit, and smo1.io/<slug> serves the new destination on the very next click because nothing at the edge held the old one. The flip side: a purr-api or Postgres outage takes every redirect down with it.

neighbors on the map