Cache and consistency

Respect transaction pooling boundaries

Keep session assumptions inside a transaction and release connections so pooled origin capacity remains healthy.

8 minute lesson

~~~

Hyperdrive uses transaction pooling. Your client borrows a physical origin connection for the duration of one transaction, and 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 model breaks a habit from direct connections: session state. Session settings cannot be assumed to remain on one physical origin connection after a transaction completes. 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, where you connect directly and keep one session. Then it fails intermittently 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 transaction-scoped behavior 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, so the setting applies to every statement in the block and vanishes cleanly at commit. Do not depend on process-global client state for correctness — anything a query needs should travel with the query or its transaction.

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 duration and starves other isolates. Transactions should be as short as the related writes require, no longer.

Concurrency is part of the contract

Each Worker isolate opening clients multiplies into origin connections. Bound query concurrency so one Worker deployment cannot exhaust the database’s connection budget through many parallel requests — keep transactions brief, avoid firing large Promise.all batches of queries per request, and size the Hyperdrive connection count to what the origin can serve.

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’ve found a correctness bug before production did.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →