backend / polyglot-bench
Polyglot Checkout Bench
One database-bound checkout transaction implemented ten ways in TypeScript, Go and Rust, proved output-identical, then load-tested on pinned cores with significance tests.
2026 · research · open source
- [*]LanguagesGo · Rust · TypeScript · Python · SQL
- [*]Frameworksnet/http · Fiber · Gin · GORM · pgx · hyper · Axum · Actix Web · tokio-postgres · deadpool-postgres · Bun.serve · Bun.sql · Hono · Fastify · node-postgres · SciPy
- [*]StoresPostgres
- [*]AsyncGo goroutine scheduler (open-loop arrival schedule) · pgx batch pipelining
- [*]InfraDocker · docker-compose · taskset / cpuset core pinning · Linux /proc sampling
The backend here is ten HTTP services that implement one written contract (spec/SPEC.md): POST /api/quote does six reads and runs a discount engine with stacking rules, a 60% cap per line and a coupon split pro rata with remainder cents to the largest line; POST /api/checkout runs the same engine inside one transaction that locks inventory with SELECT ... FOR UPDATE in product-id order, returns 409 on short stock, and writes the order, a multi-row line insert, reservations, two ledger entries, a coupon bump and a loyalty row; GET /api/customers/:id/summary aggregates the last 50 orders. Integer cents throughout.
The stacks are Bun with a 52-line hand-rolled router over Bun.sql, Bun + Hono and Node + Fastify over node-postgres, Go on net/http, Fiber and Gin over pgx/v5, Gin over GORM, and Rust on hyper, Axum and Actix over tokio-postgres with deadpool. Inside each language the pricing engine is one shared module, so framework rows differ only by framework. Postgres 18 holds 11 tables seeded deterministically (50,000 customers, 200,000 orders, 600,000 lines).
The harness is the larger half: a Go load generator with closed-loop and open-loop modes, where open-loop latency is measured from each request's scheduled send time to avoid coordinated omission; a Python orchestrator that resets and vacuums the database before every run, pins Postgres, service and generator to separate cores, and shuffles run order per repetition; a conformance harness that diffs canonical JSON across all ten stacks before any timing; a statement-count oracle that reads Postgres's own statement log; and a stats pipeline (Friedman, Nemenyi, Holm-corrected Mann-Whitney, bootstrap CIs) that regenerates the README, RESULTS.md and a LaTeX paper from the 450 run files.
The final suite logged 6,029,371 requests across 450 runs with zero errors. At a fixed 300 req/s the ten stacks sit within about 1 ms of p99 on quote; at saturation Rust and Go are indistinguishable (rust-axum vs go-fiber 0.99x, p=0.71), and swapping pgx for GORM under the same Gin router costs 1.84x throughput, more than the gap between languages.
How it holds up
- Auth
- None by design: the services are unauthenticated benchmark targets bound to loopback, so the spec measures only the transaction.
- Data
- 11 tables in db/schema.sql (customer_tier, customer, product, inventory, discount_rule, tax_rate, coupon, order, order_item, ledger_entry, loyalty_transaction), generated from fixed seed 20260921 by db/seed.py; autovacuum is disabled on the six churn tables and bench/reset.sh deletes rows above order id 200000, zeroes reservations and coupon counters, resets sequences and runs VACUUM (ANALYZE) before every run.
- Consistency
- Checkout runs in one transaction that takes row locks on inventory with SELECT ... FOR UPDATE ordered by product_id in all ten stacks (consistent lock order prevents deadlocks between concurrent checkouts), checks availability as on_hand - reserved, and only then writes; in Go the reservation, ledger, coupon and loyalty writes go out as one pgx.Batch round trip before COMMIT.
- Failure
- Short stock returns a typed 409 insufficient_stock with the SKU and rolls back via a deferred tx.Rollback; the orchestrator catches a failed run, prints the service's last log lines and stops the service in a finally block, and each run records a fixed-CPU machine probe so runs taken on a busy box can be flagged rather than averaged in (58 of 450 flagged per README).
- Testing
- No unit-test files (git ls-files finds none). Verification is a cross-implementation conformance harness (bench/verify.py: 10 stacks x 150 requests x 3 endpoints by default, from a 2,000-request price corpus and 2,000-id summary corpus, diffing canonical JSON against the reference stack) plus a statement-count oracle (bench/count_statements.sh) that checks 6 statements per quote and 15 per checkout against Postgres's log_statement output.
Techniques
- Coordinated-omission-free open-loop loadA scheduler goroutine emits intended send times at a fixed interval and workers record latency from that intended time, not the actual send, so a server that falls behind cannot hide its queueing delay in its own slowness.
bench/loadgen/main.go:7bench/loadgen/main.go:233bench/loadgen/main.go:251 - Deadlock-safe inventory lockingEvery implementation sorts product ids and takes SELECT ... FOR UPDATE in product_id order inside the checkout transaction, rejects short stock with 409 before any write, then pipelines the remaining writes in one batch before COMMIT.
services/go/internal/store/store.go:234services/go/internal/store/store.go:259services/go/internal/store/store.go:314services/go/internal/store/store.go:328services/rust/common/src/db.rs:139services/ts/bun-only/src/db/queries.ts:121 - Cross-implementation conformance oracleBefore timing, a fixed corpus is replayed against all ten stacks and canonicalised JSON (sorted keys, orderId dropped, timestamps normalised to instants) is diffed against the reference stack, so a stack that rounds differently fails before it can win.
bench/verify.py:13bench/verify.py:60 - Statement-count oracle from the database's own logTurns on log_statement=all, replays one fixed request, and counts LOG lines that reached Postgres, which is how GORM's extra Preload SELECT (7/16 vs 6/15 statements) was measured rather than inferred.
bench/count_statements.sh:17bench/count_statements.sh:21services/go/internal/gormstore/gormstore.go:192 - Deterministic per-run database resetBefore every one of the 450 runs the orchestrator deletes new orders, zeroes reservations and coupon counters, resets sequences and vacuums explicitly, with autovacuum turned off on the churn tables so it cannot fire mid-measurement.
bench/run.py:123bench/reset.sh:5bench/reset.sh:19db/schema.sql:128 - Integer-cent pricing with pro-rata coupon splitDiscounts are capped at 60% of a line in basis points and the coupon is divided across lines pro rata with the remainder cents assigned to the largest line, so all ten implementations can agree to the cent.
services/go/internal/domain/domain.go:193services/go/internal/domain/domain.go:244
Counted
- 34commits
git rev-list --count HEAD - 10 (3 TypeScript, 4 Go, 3 Rust)service implementations
bench/stacks.json - 450 (45 per stack: 3 workloads x 3 load profiles x 5 reps)measured runs
ls results | grep __ | wc -l - 6,029,371 with 0 errorsrequests served in the final suite
sum of requests/errors over results/*.json - 11Postgres tables
grep -c 'CREATE TABLE' db/schema.sql - 2,000 price requests + 2,000 summary ids; 150 per endpoint replayed by defaultconformance corpus
bench/corpus.json; bench/verify.py:9 - Go 1,524 / Rust 804 / TypeScript 850service LOC
git ls-files 'services/*.go|*.rs|*.ts' | xargs wc -l - Go load generator 498, Python 1,937, shell 229harness + analysis LOC
git ls-files ... | xargs wc -l - rust-axum / go-fiber 0.99x [0.86-1.07], p=0.71Rust vs Go at saturation
analysis/stats.json plannedContrasts - go-gin / go-gin-gorm 1.84x [1.54-2.56] throughput; 2.65x on checkoutORM cost (same Gin router)
analysis/stats.json plannedContrasts - 2.64 ms (go-nethttp) to 3.60 ms (ts-bun-hono)p99 spread at fixed 300 req/s (quote)
README.md p99 table / RESULTS.md:37