Cache and consistency

Use query caching only where stale is safe

Identify eligible reads and understand that writes do not automatically invalidate Hyperdrive query cache entries.

Hyperdrive can cache the results of read queries for a configured time. It parses each query, decides whether it reads or writes, and may store the response of a read. A matching read can then return near the Worker without reaching the origin database at all.

The defaults: a cached result lives for a max_age of 60 seconds, plus a 15-second stale_while_revalidate window where Hyperdrive serves the old result while refreshing in the background. You can raise max_age up to one hour, or tune it down.

Here’s the property that decides everything. The cache does not invalidate when your application writes. An UPDATE goes straight to the origin, but a cached SELECT of the same row keeps returning the old value until it expires. So caching is wrong for any read that must show the new value right away.

Two configurations, one database

For a mixed workload, create a second configuration with caching turned off, against the same database:

npx wrangler hyperdrive create app-db-fresh \
  --connection-string="$DATABASE_URL" --caching-disabled

Bind both and route each read to the right one:

{
  "hyperdrive": [
    { "binding": "HYPERDRIVE", "id": "57b7076f58be42419276f058a8968187" },
    { "binding": "HYPERDRIVE_FRESH", "id": "a1c9330bb2ae4d62a1a0d1b7b1b8f3c2" }
  ]
}
// popular, staleness-tolerant reads
const catalog = postgres(env.HYPERDRIVE.connectionString)

// auth, permissions, reads right after a write
const fresh = postgres(env.HYPERDRIVE_FRESH.connectionString)

The cache-disabled path still gets connection pooling and fast connection setup. You lose nothing except the cache, which is exactly what you want for fresh reads.

One quirk worth knowing: Hyperdrive detects uncacheable functions like NOW() or RANDOM() by text matching, even inside SQL comments. A stray -- NOW() comment silently makes a query uncacheable.

Classify before you configure

Take three reads and decide cached or fresh for each: a public catalog, an account balance after a transfer, and a dashboard summary.

The catalog caches happily. A product description sixty seconds old harms nobody.

The account balance must be fresh. A user who just moved money and still sees the old number will file a support ticket, or worse, retry the transfer.

The dashboard is a judgment call. Pure reporting tolerates a minute of lag. But if an action button reads from it, treat it as fresh.

My advice: when in doubt, start uncached and add caching where load appears. Stale data that looks correct is one of the hardest bugs to notice.

Lesson completed