Benchmarks
Measured on Turbine 0.92.0, October 2026Turbine adds 9% to the time of hand-written SQL. Drizzle adds 59% and Prisma adds 113%.
- 8.64x faster than Prisma and 3.25x faster than Drizzle on createMany, 1,000 rows
- 1.87x faster than Drizzle on findUnique, user + posts + comments, in one round trip
- 6.88x faster on a five-query dashboard 25 ms from the database
Each library runs the same queries as a hand-written pg control, on the same data, in the same rounds. It sends the same SQL with no ORM in between, so the distance from it is what each library costs you.
Every scenario#
24 common queries, from a primary-key lookup to a 50,000-row read. Each mark is that library's median time divided by Turbine's, so anything to the right of the blue line is slower than Turbine.
Over a network, the gap grows#
On a local socket every query is cheap and the differences are the libraries' own CPU time. In production the database is milliseconds away, and round trips decide the result. A five-query dashboard shows it: Turbine's pipeline() sends all five queries in one round trip, and a transaction sends about seven.
Streaming#
findManyStreamBatches finishes in 48.22 ms against 46.99 for the Drizzle 1.0 release candidate, a tie, and both Turbine APIs beat Drizzle 0.45.3. Turbine reads through a server-side cursor inside a transaction, while the Drizzle arms and the control page by key (WHERE id > $1 ORDER BY id LIMIT 1000), so the database work differs.
Where Turbine loses#
- Deep pagination. A sorted 20-row page at offset 9,000 takes 0.759 ms on Turbine, 0.620 on Drizzle and 0.614 on Prisma. Turbine names its prepared statements, and PostgreSQL gives a named statement a generic plan that cannot see the bound offset. Set
forceCustomPlan: trueon that query and it runs in 0.568 ms. - Per-row streaming against Drizzle 1.0.
findManyStreamtakes 52.54 ms against the release candidate's 46.99. UsefindManyStreamBatcheswhen you don't need rows one at a time. relationLoadStrategy: 'batched'over a network. It is fast on a local socket and slower once the database is a network hop away, because it spends a round trip per relation. The default,'auto', uses the join.- Ties. Scenarios marked tie, mostly single-row writes, are too close to call between Turbine and Drizzle across runs.
How it is measured#
- Same rounds. Every arm, the hand-written control included, runs once per round, and the arm order rotates every round, so machine drift lands on all arms alike.
- Medians. 200 rounds after 20 warmup for the core scenarios, fewer for the slowest ones, each taken as the median of 3 full runs.
- Each ORM as its docs prescribe. Prisma 7.10.0 with
@prisma/adapter-pgand therelationJoinspreview, Drizzle 0.45.3 with its relational API,pg8.23.1 under every arm. Drizzle has no relation_countor relation-filter primitive, so those two arms are subqueries through its query builder. - Same work in the control. Where a query returns nested rows, the hand-written version turns them into objects with
Datetimestamps, as every ORM does. On the 50-users-with-posts read Turbine takes 1.26x the hand-written time and Drizzle 1.74x. Turbine's widest gap to the control is createMany, 1,000 rows, at 1.49x. - Machine. A dedicated benchmark box, run by the repository's
benchmarks.ymlworkflow: AMD Ryzen 9 7950X host, 4-vCPU 16 GB Firecracker VM, client and PostgreSQL 17 (Docker) on the same VM over a Unix socket. Node v24.18.0 and PostgreSQL 17.11, seeded with 5 orgs, 1,000 users, 10,000 posts and 50,000 comments, thenANALYZEd. - Large reads beat the control. On 50,000 rows Turbine takes 42.39 ms and hand-written
pg53.86. Turbine parses timestamps with its own faster parser, while the control uses node-postgres's default. Given the same parser, the control is faster than Turbine. - Small numbers. Figures under 0.15 ms carry about a third uncertainty in their absolute value. Their order held across runs; their margins are not quotable.
Run it yourself#
From the repository root, against any PostgreSQL database:
createdb turbine_bench
export DATABASE_URL="postgresql:///turbine_bench?host=/tmp"
npm install && npm run build # the harnesses import the built package
cd benchmarks && npm install && npx prisma generate
npx tsx seed-neon.ts
cd .. && node dist/cli/index.js generate && cd benchmarks
npx tsx bench-interleaved.ts # core scenarios
npx tsx bench-extended.ts # more scenarios
npx tsx bench-stream-parity.ts # streaming
npx tsx bench-latency.ts # simulated round-trip times
npx tsx bench-network.ts # a real link: use a direct, non-pooler endpointOr run the whole round on a fresh machine with gh workflow run benchmarks.yml, which uploads every log and the results file as an artifact. Expect this shape on a local socket, and a flatter one on a remote database, where the round trip dominates. The full write-up, with per-run figures: RESULTS-0.92.0.md.