Sessions and transactions
A FlightSQL connection maps to a session on the edge that tracks which Quack node, if any, the connection's open transaction is bound to. This page explains the session model, how transactions pin to a node, when a pin is invalidated, and how prepared statements behave. It builds on Routing and statement classification.
The session
A session is opened lazily on the connection's first statement and holds:
connectionIdanduserpoolKey- the(tenant, tenantDb, pool)the connection handshook ontopinnedNodeId- the node an open transaction is bound to, or nonetxOpen- whether a transaction is currently open
Sessions are keyed by connection and dropped when the connection closes. The number of live sessions and how many are mid-transaction are surfaced as metrics (flightsql_sessions_active, and the in-transaction count).
Transaction pinning
Routing normally picks the least-loaded node per statement. That is wrong for a transaction: a BEGIN on node A followed by a load-routed COMMIT could land on node B, where the transaction does not exist. To prevent that, the session pins:
- A
Beginstatement setspinnedNodeIdto the node it executed on and markstxOpen = true. - While
txOpenis true, every subsequent statement short-circuits the load-based selection and routes to the pinned node. - A
CommitorRollbackclears the pin and setstxOpen = false, returning the connection to per-statement load routing.
The router only honors a pin while a transaction is open, so a stale pinnedNodeId can never misroute a non-transactional statement.
Pin invalidation
A pin is dropped (pin and txOpen both cleared) when the bound node can no longer serve the transaction:
- Pinned node disappeared. If the pinned node is gone from the pool snapshot, the statement fails with "pinned node disappeared; transaction lost" and the pin is invalidated. The transaction cannot be continued; the client must start over.
- Transient failure mid-transaction. As covered on the routing page, a transient node failure inside an open transaction does not retry on another node (a partially-applied transaction cannot move). The pin is invalidated and the statement fails.
- Pool vanished. If the target pool is no longer found while a transaction was open, the pin is invalidated.
In each case the failure is explicit: there is no silent re-execution of a transaction's statements on a different node.
Retry and re-pin
The one case where a transactional statement moves nodes is a BEGIN that hit a transient failure before the transaction was established. Because no work has been committed yet, the router retries the BEGIN once on a different node and pins the session to that fallback node, so the rest of the transaction (and its COMMIT) follows it. This is the only interaction between retry-once and pinning.
Prepared statements
The edge supports prepared statements with a behavior shaped by Arrow JDBC and DBeaver, which need the result schema known at prepare time to dispatch a handle as a query rather than an update:
- On
createPreparedStatementthe edge pre-executes the SQL through the normal route-and-authorize path, caches the resulting stream by a generated handle, and returns the result's Arrow schema in the prepare response. - On the following
getStreamthe edge replays the cached stream once and the caller closes it. The handle is effectively single-use: the result is produced at prepare time, not re-run per execution. closePreparedStatementis a safety net that frees a handle the client opened but never executed.- Parameter bindings are not supported in this version; a prepared statement is a fixed SQL string.
Because a prepared statement executes at prepare time, it passes through the same classification, ACL gate, routing, and (if it is a BEGIN) pinning as a directly executed statement.