Cache and consistency
Respect transaction pooling boundaries
Keep session assumptions inside a transaction and release connections so pooled origin capacity remains healthy.
Hyperdrive uses transaction pooling. Your client borrows a physical connection to the origin for the length of one transaction. When the transaction completes, the connection goes back to the pool for someone else. The next statement you send may run on a different connection entirely.
That breaks a habit from direct connections: session state. You can’t assume a session setting is still there after a transaction ends. Hyperdrive resets connections when they return to the pool, so this pattern lies to you:
SET search_path TO tenant_42;
-- connection returned to pool and reset
SELECT * FROM invoices; -- some later query, different connection:
-- ERROR: relation "invoices" does not exist
The cruel part is that it often works in local development. There you connect directly and keep one session. Then it fails now and then through Hyperdrive, only when statements land on different pooled connections. Intermittent search_path or missing-setting errors after deploying behind Hyperdrive are this bug until proven otherwise.
Scope settings to the transaction
Keep related statements inside an explicit transaction, and set what they need there with SET LOCAL:
const results = await sql.begin(async (sql) => {
await sql`SET LOCAL statement_timeout = '5s'`
const invoices = await sql`select id, total from invoices where tenant_id = ${tenantId}`
return invoices
})
Inside BEGIN/COMMIT you hold one connection. The setting applies to every statement in the block and disappears cleanly at commit. Anything a query needs should travel with the query or its transaction, never with the client object.
Resist the opposite temptation too: wrapping a whole request in one long transaction just to keep settings alive. That pins a pooled connection for the full request and starves other isolates. Keep transactions as short as the related writes require, no longer.
Concurrency is part of the contract
Every Worker isolate that opens a client turns into origin connections. Bound your query concurrency so one deployment can’t exhaust the database’s connection budget through many parallel requests. Keep transactions brief. Avoid firing large Promise.all batches of queries per request. Size the Hyperdrive connection count to what the origin can serve.
Try this to prove the boundary to yourself: run a transaction that sets a local option, then confirm later unrelated work does not depend on that setting. If anything outside the transaction needed it, you found a correctness bug before production did.
Lesson completed