Query caching
Hyperdrive automatically caches all cacheable queries executed against your database when query caching is turned on, reducing the need to go back to your database (incurring latency and database load) for every query which can be especially useful for popular queries. Query caching is enabled by default.
Because Hyperdrive uses database protocols, it can differentiate between a mutating query (a query that writes to the database) and a non-mutating query (a read-only query), allowing Hyperdrive to safely cache read-only queries.
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 behaviour for Hyperdrive is defined as below:
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.
Disable caching on a per-Hyperdrive basis by using the Wrangler CLI to set the --caching-disabled option to true.
For example:
# wrangler v3.11 and above requirednpx wrangler hyperdrive update my-hyperdrive-id --origin-password my-db-password --caching-disabled trueYou can also configure multiple Hyperdrive connections from a single application: one connection that enables caching for popular queries, and a second connection where you do not want to cache queries, but still benefit from Hyperdrive's latency benefits and connection pooling.
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); // ... 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, }); // ... 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.