Query caching
Hyperdrive automatically caches cacheable read queries that your Worker sends to your database when query caching is turned on. This reduces database load and avoids a network round trip to your database for popular queries. Query caching is enabled by default.
Hyperdrive uses database protocols to differentiate between a mutating query (a query that writes to the database) and a non-mutating query (a read-only query). Hyperdrive caches eligible read-only query responses and does not cache writes.
Besides determining the difference between a SELECT and an INSERT, Hyperdrive also parses the database wire-protocol and uses it to differentiate between a mutating or non-mutating query.
For example, a read query that populates the front page of a news site would be cached:
-- Cacheable: uses a parameterized date value instead of CURRENT_DATESELECT * FROM articles WHERE DATE(published_time) = $1ORDER BY published_time DESC LIMIT 50-- Cacheable: uses a parameterized date value instead of CURDATE()SELECT * FROM articles WHERE DATE(published_time) = ?ORDER BY published_time DESC LIMIT 50Mutating queries (including INSERT, UPSERT, or CREATE TABLE) and queries that use functions designated as volatile ↗ or stable ↗ by PostgreSQL are not cached:
-- Not cached: mutating queriesINSERT INTO users(id, name, email) VALUES(555, 'Matt', 'hello@example.com');
-- Not cached: LASTVAL() is a volatile functionSELECT LASTVAL(), * FROM articles LIMIT 50;
-- Not cached: NOW() is a stable functionSELECT * FROM events WHERE created_at > NOW() - INTERVAL '1 hour';-- Not cached: mutating queriesINSERT INTO users(id, name, email) VALUES(555, 'Thomas', 'hello@example.com');
-- Not cached: LAST_INSERT_ID() is a volatile functionSELECT LAST_INSERT_ID(), * FROM articles LIMIT 50;
-- Not cached: NOW() returns a non-deterministic valueSELECT * FROM events WHERE created_at > NOW() - INTERVAL 1 HOUR;Common PostgreSQL functions that are not cacheable include:
| Function | PostgreSQL volatility category | Cached |
|---|---|---|
NOW() | STABLE | No |
CURRENT_TIMESTAMP | STABLE | No |
CURRENT_DATE | STABLE | No |
CURRENT_TIME | STABLE | No |
LOCALTIME | STABLE | No |
LOCALTIMESTAMP | STABLE | No |
TIMEOFDAY() | VOLATILE | No |
RANDOM() | VOLATILE | No |
LASTVAL() | VOLATILE | No |
TXID_CURRENT() | STABLE | No |
Only functions designated as IMMUTABLE by PostgreSQL (functions whose return value never changes for the same inputs) are compatible with Hyperdrive caching. If your query uses a STABLE or VOLATILE function, move the function call to your application code and pass the resulting value as a query parameter instead.
The default caching behavior for Hyperdrive is:
max_age= 60 seconds (1 minute)stale_while_revalidate= 15 seconds
The max_age setting determines the maximum lifetime a query response will be served from cache. Cached responses may be evicted from the cache prior to this time if they are rarely used.
The stale_while_revalidate setting allows Hyperdrive to continue serving stale cache results for an additional period of time while it is revalidating the cache. In most cases, revalidation should happen rapidly.
You can set a maximum max_age of 1 hour.
Hyperdrive does not purge or invalidate cached read query results when your application writes to your database. A later matching SELECT can return the cached result until the configured max_age expires. Hyperdrive can also serve the result during the stale_while_revalidate window while it refreshes the cache in the background.
Writes still go to your database. Hyperdrive only caches eligible read query responses.
This means you should choose a caching strategy based on how fresh each read must be:
- Use query caching for reads that can tolerate brief staleness. Good examples include public content, dashboards, search results, product catalogs, and other high-volume reads where a short delay after writes is acceptable.
- Lower
max_ageandstale_while_revalidatewhen a short stale window works. This keeps query caching enabled while reducing how long Hyperdrive can serve an older result. - Use a cache-disabled Hyperdrive configuration for reads that must be fresh. Create a second Hyperdrive configuration with
--caching-disabled, bind it alongside your cached configuration, and route those reads through the cache-disabled binding. Good examples include authentication, sessions, permissions, billing state, admin settings, and reads immediately after a write. Refer to Disable caching for an example. - Disable query caching everywhere only when most reads must be fresh. You still get Hyperdrive's connection pooling and fast connection setup when caching is disabled.
If an object-relational mapping (ORM) library or authentication library owns the SQL, create separate database clients for the cached and cache-disabled Hyperdrive bindings. Pass the cache-disabled client to the library or module that needs fresh reads, and use the cached client for reads that can tolerate the configured stale window.
Disable caching on a per-Hyperdrive configuration basis by using the Wrangler CLI to set the --caching-disabled option.
To create a separate cache-disabled Hyperdrive configuration against the same database:
npx wrangler hyperdrive create my-database-fresh --connection-string="<DATABASE_CONNECTION_STRING>" --caching-disabledTo turn off caching on an existing Hyperdrive configuration:
npx wrangler hyperdrive update <HYPERDRIVE_CONFIG_ID> --caching-disabledYou can configure multiple Hyperdrive connections from a single application: one connection that enables caching for popular queries, and a second connection for fresh reads that should not use query caching.
When you use multiple Hyperdrive configurations for the same database, account for the total origin connections across configurations. Refer to Tune connection pool for guidance.
For example, using database drivers:
export default { async fetch(request, env, ctx): Promise<Response> { // Create clients inside your handler — not in global scope const client = postgres(env.HYPERDRIVE.connectionString); // Use the cache-disabled binding for auth, permissions, and reads after writes. const clientNoCache = postgres(env.HYPERDRIVE_CACHE_DISABLED.connectionString); // ... },} satisfies ExportedHandler<Env>;export default { async fetch(request, env, ctx): Promise<Response> { // Create connections inside your handler — not in global scope const connection = await createConnection({ host: env.HYPERDRIVE.host, user: env.HYPERDRIVE.user, password: env.HYPERDRIVE.password, database: env.HYPERDRIVE.database, port: env.HYPERDRIVE.port, }); // Use the cache-disabled binding for auth, permissions, and reads after writes. const connectionNoCache = await createConnection({ host: env.HYPERDRIVE_CACHE_DISABLED.host, user: env.HYPERDRIVE_CACHE_DISABLED.user, password: env.HYPERDRIVE_CACHE_DISABLED.password, database: env.HYPERDRIVE_CACHE_DISABLED.database, port: env.HYPERDRIVE_CACHE_DISABLED.port, }); // ... },} satisfies ExportedHandler<Env>;The Wrangler configuration remains the same both for PostgreSQL and MySQL.
{ "hyperdrive": [ { "binding": "HYPERDRIVE", "id": "<YOUR_HYPERDRIVE_CACHE_ENABLED_CONFIGURATION_ID>", }, { "binding": "HYPERDRIVE_CACHE_DISABLED", "id": "<YOUR_HYPERDRIVE_CACHE_DISABLED_CONFIGURATION_ID>", }, ],}[[hyperdrive]]binding = "HYPERDRIVE"id = "<YOUR_HYPERDRIVE_CACHE_ENABLED_CONFIGURATION_ID>"
[[hyperdrive]]binding = "HYPERDRIVE_CACHE_DISABLED"id = "<YOUR_HYPERDRIVE_CACHE_DISABLED_CONFIGURATION_ID>"- For more information, refer to How Hyperdrive works.
- To connect to PostgreSQL, refer to Connect to PostgreSQL.
- For troubleshooting guidance, refer to Troubleshoot and debug.