StatsHub Docs
Reports

Go API performance against the legacy API

A client-ready comparison of throughput, latency, and memory from 46 controlled load-test runs against the Go and legacy Next.js API implementations.

The Go API materially improves the cost and speed of serving StatsHub requests, but the size of the improvement depends on where the request spends its time. For runtime-bound work, Go serves between 4.2× and 39.1× as many requests per second in this test. A small database read reaches 8.4× higher peak throughput. Queries dominated by Postgres converge, and one aggregate query remains slower in Go.

The clearest resource result is memory: the Go process finished the test at 262 MB resident memory, compared with 2,095 MB for the legacy Next.js server. That is an 87.5% reduction, or almost exactly 8× less memory per API instance.

Executive results

MeasureGo APILegacy APIResult
Resident memory after the run262 MB2,095 MB87.5% less
Peak health-check throughput24,833 req/s5,206 req/s4.8× higher peak
Health throughput at one connection11,323 req/s289 req/s39.1× higher
Health p50 latency at eight connections0.3 ms3.8 ms12.7× lower
Peak small-read throughput14,096 req/s1,673 req/s8.4× higher
Successful health requests across all levels747,01188,482Zero failures on both
Successful small-read requests across all levels426,27238,581Zero failures on both

What the measurements support

The Go service provides substantially more API capacity per instance and needs about one eighth of the memory. The strongest gains appear when application runtime overhead is a meaningful part of the request. This supports fewer or smaller API containers for the same runtime-bound traffic.

Runtime-bound throughput

/api/health does not query the database. It isolates the cost of accepting a request, routing it, building a response, and writing it back to the client. Go reaches its throughput ceiling at 32 concurrent connections and remains near that ceiling through 2,048 connections without errors.

The multiplier narrows as the legacy server fills its event-loop queue, but Go still serves 4.2× more requests per second at 2,048 connections. At one connection, before batching or deep queueing can help either runtime, the gap is 39.1×.

ConcurrencyGo req/sLegacy req/sGo advantage
111,32328939.1×
822,91392224.9×
3224,8331,71414.5×
12821,7252,4568.8×
51221,9383,8865.6×
2,04821,6505,2064.2×

Runtime-bound latency

Higher throughput does not come from making callers wait in a deeper queue. Go also holds a materially lower median response time at every tested load level.

At 2,048 concurrent connections, the Go median is 89.9 ms versus 386.6 ms for legacy. At eight connections, a more representative shallow queue, the medians are 0.3 ms and 3.8 ms.

Small database reads

/api/models adds a small Postgres query. The database reduces the runtime-only margin, but Go still reaches 14,096 requests per second compared with the legacy server's peak of 1,673.

At eight connections, Go returns the small read with a 0.5 ms p50. Legacy takes 8.3 ms, a 16.6× latency difference. Both implementations complete every request at every level, so this comparison is not inflated by failed work.

Memory footprint

The measured difference is 1,833 MB per running instance. If a deployment kept four equivalent API instances, the same ratio would represent about 7.3 GB less resident memory. That projection assumes each instance behaves like the measured processes; it is a capacity-planning illustration, not a cloud-billing quote.

CPU usage was not sampled during this run, so this report does not claim a CPU percentage reduction. Throughput shows how much work each server completed, but it is not a substitute for per-process CPU telemetry.

Database-bound requests

The runtime stops deciding performance when Postgres dominates the request. Both implementations use a ten-connection database pool, so requests beyond ten in-flight queries wait for the same limited resource.

At eight concurrent connections:

EndpointGo req/sLegacy req/sGo p50Legacy p50Finding
Indexed search15.012.9365.7 ms456.2 msGo is modestly faster.
Referee aggregate5.412.01,365.5 ms635.7 msGo is slower; the query needs optimization.

This is the main trade-off. Replacing the runtime removes application overhead; it cannot make an expensive SQL plan cheap. The referee result is a measured regression, not an outlier hidden from the headline. It is also actionable: the query analysis separates runtime work from database and indexing work.

At 512 concurrent search requests, both services cross the client's 30-second timeout. The Go run records 322 failed requests and legacy records 238. Neither process dies; both are queueing behind the database. The health endpoint still answers after saturation.

Test design

The benchmark ran on 2026-08-28 with the following controls:

VariableSetting
Host16 CPU cores, 61 GB memory
NetworkLoopback; client and both servers on the same host
DatabaseSame local Postgres mirror for both implementations
Database poolTen connections per implementation
TargetsGo on port 8080; legacy Next.js API on port 3000
OrderOne target at a time to avoid direct CPU competition
Warm-upEight requests before each endpoint run
Load levels1, 8, 32, 128, 512, and 2,048 keep-alive connections
DurationSix seconds per level
Client timeout30 seconds
Recorded fieldsRequests, successes, failures, throughput, p50, p90, p99, maximum latency

The four endpoints deliberately progress from no database work to an aggregate query. This separates runtime capacity from database capacity instead of blending them into one average that would explain neither.

Limits of the evidence

  • Client and servers shared one host. At high concurrency, the load generator competed for CPU with the target under test.
  • The local Postgres mirror served other services. Absolute query latency is less portable than the side-by-side comparison.
  • Six-second levels establish throughput plateaus and saturation behavior. They do not establish long-term garbage-collection behavior or memory leaks.
  • Resident memory was sampled after the run, not continuously at each load level. CPU was not sampled.

Client conclusion

The Go migration delivers a clear infrastructure benefit for the API layer: about one eighth of the memory, 4.2× to 39.1× the runtime-bound throughput, and substantially lower median latency under every tested runtime-only load. Small database reads retain a large advantage.

The remaining performance work is concentrated rather than systemic. Complex queries need SQL and database-cache improvements, especially the referee aggregate. That distinction is useful operationally: the service runtime is no longer the general bottleneck, and the next gains can target measured query paths instead of scaling every API container.

On this page