Sessions
The default API uses a fresh database session for every call. Here is when you need to open a session yourself, and why.
Default behavior: a fresh session per call
await env.db.exec("select 1");That single line connects, runs the query, syncs, and disconnects. As a result, no session state survives between calls.
BEGINdoes not carry over to the next call.SETsettings do not apply to the next call.- Prepared statements and temporary tables do not survive either.
Why connections are not reused
A reused connection can only be safe if every trace of the previous request — SET settings, temporary tables, prepared statements — is provably gone from it. Rather than share a connection and risk one request's state reaching another user's, runlot closes the connection and opens a new one for every call. No session state passes between requests.
Performance characteristics
| Step | Cost |
|---|---|
| Baseline cost of a new session | 2.6 ms |
$1 parameter | +0.6 ms |
| Real table lookup (catalog warm-up) | +5 ms |
| Write (commit + flush) | +9 ms |
The 5 ms on a table lookup is the cost of opening a new session and warming the catalog on every call. If you connect directly over the PostgreSQL protocol and keep the session open, the same query takes 0.55 ms. Opening a session handle, below, gets you the same saving inside a worker when several statements belong together.
Using a session directly
If you need to run several SQL statements in the same session, use a session handle.
const s = await env.db.session();
try {
await s.exec("begin");
await s.exec("insert into a values ($1)", [1]);
await s.exec("insert into b values ($1)", [2]);
await s.exec("commit");
} finally {
await s.close();
}In @runlot/pg, pool.connect() plays the same role.
Rules
close()is idempotent. You can call it more than once.close()rolls back an open transaction. To commit, you must sendCOMMITyourself.- Running SQL on a closed handle raises an
08003error. - After 30 seconds without a SQL statement, the session is reclaimed and any open transaction is rolled back.
Keep it inside the request scope
Do not store a session handle in a global variable or pass it outside the request. It can hit the 30-second limit and have its transaction rolled back, and while it holds the session other requests have to wait.
Concurrent requests are processed in order
Database requests are handled one at a time per project. If a request holds a session for a long time, other requests wait. A long transaction can increase response times across the whole project.
The default execution limit for a single SQL statement is 10 seconds. Queries that exceed it are aborted.