Sizing & Tuning

For operators who want to know how the engine sizes itself, or who need to override it. You don’t need this page to run Dwarf: the decisions are in Deployment, and the engine applies these numbers for you. The measurements behind them are in the Cloud Benchmarks.

Connection Pool

Each shard’s connection budget is a constant adjusted for distance and capped by the server: 250 connections up to 0.25 ms of round-trip time, rising evenly to 500 at 1 ms, and never more than half the server’s own max_connections (a fixed 8 on SQLite). The budget is shared by the shard’s two dispatching replicas, about 125 connections each at the default. The rules for overriding it are in Deployment.

Why distance matters. A connection held while a packet is in flight does the database no good, so a distant database needs more connections to stay as busy. At 1 ms the default rises to 500 (or half the server’s limit, if lower), which recovered most of what the distance cost where it was measured.

Why a constant, not a ratio per vCPU. Measured on Cloud SQL for PostgreSQL 16 (1 TB SSD, each database driven to saturation, one run per cell), the default sits at or near the best of the pools tried on every size up to 32 vCPU; at 64 vCPU, set MaxConnections: 500:

databasedefault max_connectionsdefault budget nearbest measured nearrecommendation
4 vCPU500250125-250 (alike, ~2,050 steps/s)default
8 vCPU600250150-300 (~4,100-4,500 steps/s)default
16 vCPU800250200 (~8,500 steps/s)default
32 vCPU1,000250250 (500 gained 2% on the same instance: 18,852 to 19,221 steps/s)default
64 vCPU1,000250500 (29,340 against 24,593 at 250 on the same instance, +19%)MaxConnections: 500

Throughput keeps climbing slowly as connections are added, because more transactions share each write to the log, so a pool past the budget is not a cliff, only diminishing returns. Under-sizing costs throughput; it does not destabilize the database.

Compensation for distance stops at 1 ms by design. Past roughly 1.5-2 ms a large database cannot be kept busy at any reasonable pool, and the answer is to place the engine closer, or to add shards. For context, same-zone round trips run 0.05-0.96 ms and cross-AZ same-region about 1.1 ms; cross-region (10-40 ms) is far outside what any pool can compensate.

Workers

Workers need no configuration, and their count is not derived from how long your tasks take, which the engine cannot know.

What bounds it is the crash-recovery lease. If every in-flight task is blocked on the same downstream (an LLM provider having an outage, say) and that downstream recovers, every task finishes at once and their completion transactions all queue for the shard’s connections. A completion that waits longer than its remaining lease margin has its step re-claimed by a peer — correct, but the task runs a second time, which for a two-minute LLM call is real money. So the engine derives the largest pool that keeps such a storm inside the margin: N_max = M × margin ÷ txTime × safety, where txTime ≈ k·L + 3 ms is the post-task database phase, k its round trips (4 on PostgreSQL, 6 on SQLite, 7 on MySQL and SQL Server), and L the round-trip time it measures with a few SELECT 1s at startup. The worst shard’s number wins.

The pool grows into that ceiling on demand: it starts at a resident set sized by the connection budget (which is what dispatch is actually bound by) and adds a worker whenever taking a candidate left none free. Growth stops itself under load without asking the database anything: before a worker picks up a candidate it waits for a turn at its shard, and a worker waiting for one is holding no work, so it counts as free and the next check declines to spawn. A worker takes a turn only for the calls that use a connection, never across the task itself, so a workload of long tasks grows to fit its own concurrency while a database-bound one stops growing at the point extra workers would only queue. Short-task deployments stay small; no knob either way.

Next: Dialect Benchmarks.