Every app and PostgreSQL 17 ran on the same machine as the load generator, each held to 2 CPUs and 2 GiB, in the framework's documented production server. Each scenario was run five times; the tables show the median. The percentage beside a throughput figure is how far apart the slowest and fastest run were, as a share of the median, so a large one means the runs disagree.
The apps are ours, written from a shared contract, and the maintainers of the other projects have not reviewed them yet. If you maintain one and the app is not idiomatic or is badly configured, open an issue or a pull request and the runs are redone. This page ranks nothing: effort and capability are separate tracks, and a framework that is faster here may give you less to start with. A run on dedicated cloud machines has not been made yet.
Gin, FastAPI and Sinatra were added later and measured in a second session on the same laptop (2026-10-17 and 2026-10-18), so compare them to the other rows with the same caution as any two rows here.
Throughput
Requests per second, higher is better. n/a means the app has no admin to measure; none means the admin was measured and no request succeeded.
| App | list | pet | order | admin | mix |
|---|---|---|---|---|---|
| Go baseline (chi, pgx, sqlc) | 3,822 (40%) | 22,589 (5%) | 10,126 (4%) | n/a | 6,936 (3%) |
| tanGO v0.5.0 | 3,138 (34%) | 20,707 (17%) | 9,746 (36%) | 1,418 (11%) | 6,427 (9%) |
| Fuego v0.18.0 | 3,569 (33%) | 18,984 (24%) | 8,871 (18%) | n/a | 6,882 (7%) |
| Gin v1.12.0 | 3,680 (43%) | 21,899 (29%) | 10,034 (10%) | n/a | 6,450 (12%) |
| togo, 32 idle connections | 3,019 (50%) | 23,199 (3%) | 11,014 (2%) | n/a | 6,472 (3%) |
| togo, as scaffolded (2 idle connections) | 627 (22%) | 1,351 (21%) | 722 (4%) | n/a | 856 (5%) |
| Django 6.1.2 | 1,334 (10%) | 1,988 (32%) | 1,682 (2%) | 108 (2%) | 1,805 (1%) |
| Rails 8.1.4 | 1,774 (2%) | 2,405 (3%) | 1,885 (1%) | n/a | 2,197 (12%) |
| Laravel 13.37 | 309 (2%) | 324 (2%) | 240 (2%) | n/a | 302 (3%) |
| FastAPI 0.143.3 | 1,012 (3%) | 1,334 (6%) | 658 (2%) | n/a | 1,099 (7%) |
| Sinatra 4.2.1 | 3,524 (3%) | 5,313 (1%) | 3,588 (1%) | n/a | 4,290 (3%) |
| uAdmin 0.10.2 | 775 (16%) | 1,352 (6%) | 623 (3%) | none | 786 (17%) |
p95 latency
Milliseconds, lower is better.
| App | list | pet | order | admin | mix |
|---|---|---|---|---|---|
| Go baseline (chi, pgx, sqlc) | 63.0 | 1.9 | 4.0 | n/a | 6.6 |
| tanGO v0.5.0 | 67.9 | 2.8 | 5.3 | 30.4 | 19.6 |
| Fuego v0.18.0 | 60.1 | 2.4 | 5.9 | n/a | 10.6 |
| Gin v1.12.0 | 62.9 | 2.4 | 4.1 | n/a | 17.3 |
| togo, 32 idle connections | 71.6 | 2.4 | 4.3 | n/a | 10.0 |
| togo, as scaffolded (2 idle connections) | 103.6 | 82.9 | 92.9 | n/a | 92.7 |
| Django 6.1.2 | 26.8 | 15.5 | 22.4 | 320.1 | 21.1 |
| Rails 8.1.4 | 20.8 | 15.9 | 19.2 | n/a | 17.8 |
| Laravel 13.37 | 177.5 | 174.4 | 193.7 | n/a | 181.1 |
| FastAPI 0.143.3 | 55.6 | 52.9 | 77.3 | n/a | 56.9 |
| Sinatra 4.2.1 | 11.9 | 8.3 | 11.1 | n/a | 9.9 |
| uAdmin 0.10.2 | 99.7 | 90.1 | 101.5 | none | 100.2 |
Memory and cold start
| App | Peak memory, MB | Cold start, ms |
|---|---|---|
| Go baseline (chi, pgx, sqlc) | 21 | 108 |
| tanGO v0.5.0 | 22 | 57 |
| Fuego v0.18.0 | 24 | 107 |
| Gin v1.12.0 | 16 | 108 |
| togo, 32 idle connections | 22 | 213 |
| togo, as scaffolded (2 idle connections) | 22 | 211 |
| Django 6.1.2 | 104 | 1,389 |
| Rails 8.1.4 | 201 | 2,002 |
| Laravel 13.37 | 96 | 2,143 |
| FastAPI 0.143.3 | 253 | 1,946 |
| Sinatra 4.2.1 | 85 | 643 |
| uAdmin 0.10.2 | 37 | 215 |
Effort
What it took to build the same pet shop. Fewer lines is not a score.
| App | Authored lines | Authored tokens | Generated lines | Dependencies | Image, MB |
|---|---|---|---|---|---|
| Go baseline (chi, pgx, sqlc) | 505 | 4,511 | 277 | 3 | 17 |
| tanGO v0.5.0 | 396 | 3,818 | 165 | 3 | 34 |
| Fuego v0.18.0 | 478 | 4,453 | 277 | 3 | 21 |
| Gin v1.12.0 | 493 | 4,353 | 277 | 3 | 32 |
| togo | 605 | 6,098 | 5,709 | 25 | 59 |
| Django 6.1.2 | 312 | 2,809 | 83 | 3 | 204 |
| Rails 8.1.4 | 151 | 1,519 | 369 | 4 | 624 |
| Laravel 13.37 | 253 | 2,351 | 2,439 | 3 | 593 |
| FastAPI 0.143.3 | 183 | 2,201 | 0 | 4 | 280 |
| Sinatra 4.2.1 | 170 | 1,867 | 0 | 6 | 561 |
| uAdmin 0.10.2 | 294 | 2,564 | 0 | 1 | 134 |
Reading p95 on list
The Go apps have a typical list request of about 3.5 ms but a p95 of 60 to 70 ms, while Rails and Django, at a third of the throughput, show 21 and 27 ms. Every list request counts 10,000 rows on a PostgreSQL limited to 2 CPUs, and a Go app with a large connection pool runs many of those counts at once, so some requests wait far longer than others. Django and Rails run only a few queries at a time and queue the rest in order, which gives a narrower spread. On the plain Go baseline, list only, five runs each, the p95 was 62 and 61 ms with the default pool, 51 ms capped at 6 connections and 9 ms capped at 2, with throughput no lower. The tables use each framework's defaults, so they are unchanged: read p95 on list as a measure of concurrency, not of speed.
The two-connection default
database/sql keeps two idle connections by default, so under concurrent load a Go app closes and reopens PostgreSQL sessions on almost every request. togo as scaffolded lands near 1,350 requests per second on a single-pet read for this reason. tanGO v0.5.0 defaults to 32 idle connections; togo with DB_MAX_IDLE=32 is the second togo row.
The full method, the apps and the raw tables are in the benchmarks repository, including results.md.