Skip to content

Spine internal lab tests ​

2026-11-01, Spine 0.5.2, GOGC=400

We compared HTTP throughput, response latency and memory usage under the same CPU and memory limits. These are measurements by the Spine project itself, with reference to the public measurement method of Web Frameworks Benchmark.

Comparison results

We compare throughput under saturated load, and latency and memory at a common rate of 20,000 requests per second. Each value is the median of 3 measurements, with a fresh server start for each.

Saturated load, 256 concurrent connections

ConfigurationThroughput (req/s)p95 (ms)p99 (ms)
Spine163,559Observed range: 161,621–164,7393.1594.398
NestJS / Express93,977Observed range: 92,999–94,4333.5674.770
Spring MVC118,943Observed range: 117,960–121,5043.1193.891
FastAPI / Uvicorn50,173Observed range: 50,124–50,1905.4717.026

Common load, 20,000 requests per second

ConfigurationMean (ms)p95 (ms)p99 (ms)Observed memory (MiB)
Spine0.1690.3991.062Observed range: 0.825–1.15441.8
NestJS / Express0.2100.5281.098Observed range: 0.956–1.509479.3
Spring MVC0.1990.5111.115Observed range: 1.093–1.235608.8
FastAPI / Uvicorn0.4111.0591.744Observed range: 1.563–1.814156.5

At the common rate of 20,000 requests per second, Spine had a mean response time of 0.169ms, a p95 of 0.399ms and a p99 of 1.062ms.

Latency at a fixed target rate

This test compares response latency at a target of 118,000 requests per second. In the separate test under saturated load, throughput was 163,559 req/s for Spine and 118,943 req/s for Spring MVC.

For both implementations, every recorded request returned HTTP 2xx; request errors and timeouts were zero across all three runs.

Configurationp95 (ms)p99 (ms)
Spine0.9442.739
Spring MVC60.92789.248
View completion rates and accounting details

These completion rates were recorded under the fixed target load and do not measure maximum throughput. Completion rate is successful responses divided by the recorded elapsed time for each run. Elapsed times can vary between runs. Each column reports its own median across three independent starts. Completion rate is not a request-loss rate.

ConfigurationActual completion rate (req/s)
Spine116,777
Spring MVC117,305

In the third run, which supplies the median completion rate for both implementations, Spine recorded 1,768,230 successful responses over 15.142s and Spring MVC 1,759,690 over 15.001s. A longer reporting interval can yield a lower per-second rate despite more successful responses.

The rate of 118,000 requests per second is close to the saturated throughput of Spring MVC. The observed p99 ranges were 2.707–2.931ms for Spine and 10.724–206.784ms for Spring MVC. In 1 of its 3 runs, Spring MVC fell below 98% of the target request rate; we retained that sample.

Estimated annual savings

This forecast applies measured CPU and memory usage to publicly listed vpsGOD plans. Read the fees below separately from actual VM performance measurements.

A scenario calculated for 20,000 requests per second

If you use Spine instead of NestJS / Express
₩73,440/ year in estimated savings
Estimated monthly equivalent: about ₩6,120 saved
3 vCPU / 6GB$80 per year, paid upfront
If you use Spine instead of Spring MVC
₩46,734/ year in estimated savings
Estimated monthly equivalent: about ₩3,895 saved
2 vCPU / 4GB$60 per year, paid upfront
If you use Spine instead of FastAPI / Uvicorn
₩126,851/ year in estimated savings
Estimated monthly equivalent: about ₩10,571 saved
4 vCPU / 8GB$120 per year, paid upfront

Calculated from the difference in vpsGOD annual upfront fees. Monthly savings are the annual difference divided by 12. Cheaper publicly listed discounted plans were also used for the comparison frameworks.

The Spine estimate uses a plan with 2 vCPU and 256MB of memory. This configuration costs $25 per year, paid upfront.

These forecasts assume CPU performance and headroom. Throughput and p99 were not measured on the VM, and extra traffic and taxes are excluded.

View the plans and calculation basis

Assumptions used in the calculation

Per-core performance was assumed to be 50% of that in the measurement environment, with 20% CPU headroom. Core counts were calculated from the highest average CPU usage across 3 measurements.

The largest observed memory usage, including startup and warmup, was multiplied by 1.25, then 128MiB was added for a lightweight OS. This assumes adjusted worker counts and direct execution of prebuilt apps. It is not a measured minimum RAM requirement.

Among the verified plans that meet the required CPU and RAM, those with the lowest annual base fees were selected. Larger discounted plans were also used for comparison frameworks when they cost less.

Discounted plans allocate CPU on a fair-share basis. Sustained CPU allocation percentages have not been published. The regions, final stock availability and renewal prices of discounted plans were not verified.

Included traffic is 100GB for Spine, 2TB for NestJS, 1TB for Spring MVC and 4TB for FastAPI. Excess transfer costs extra. The listed fees do not imply a full month of traffic at 20,000 requests per second.

Increasing CPU headroom to 30% means Spring MVC also needs 3 cores. FastAPI needs 5 cores, but the public listings checked had no suitable single VM, so fees for that scenario were not calculated.

Fees checked on 2026-11-01. The exchange rate is $1 = ₩1,335.27, calculated from European Central Bank reference rates dated 2026-10-30. No first-invoice discount code was applied.

Estimated annual savings = (comparison plan annual fee − Spine plan annual fee) × exchange rate

Measurement environment and method

HostARM64, 14 cores (10 performance, 4 efficiency), RAM 24GB
Reference clockAbout 4.5GHz. We did not record or fix the actual operating clock during measurement.
Runtime environmentDocker Desktop, Linux/arm64, HTTP/1.1 keep-alive, loopback
Server resource limits3 vCPU, 3GiB and no additional swap for every implementation
Load generatorzrk 2.4.2, a separate set of 9 CPUs, 1GiB of memory
Task43-byte JSON parsing → field validation → JSON response
Repetitions3 fresh server starts per implementation, 15 seconds per window, 30 samples in total
WarmupAt least 135 seconds for Spring MVC and 45 seconds for the others. Throughput coefficient of variation of 5% or less across the last 3 windows
Spine settingsGOGC=400, GOMAXPROCS=3, GOMEMLIMIT=2700MiB

Runtime versions

Spine0.5.2 · Echo 4.15.0 · Go 1.27.3
NestJS / ExpressNestJS 12.3.2 · Express 5.3.0 · Node.js 26.13.0 · 3 workers
Spring MVCSpring Boot 4.1.2 · Java 25.0.5 · Tomcat · virtual threads
FastAPI / UvicornFastAPI 0.162.0 · Uvicorn 0.54.0 · Python 3.15.2 · uvloop · httptools · 3 workers

Why we chose these representative configurations

NestJS / ExpressAdapter

We referred to the default ExpressAdapter configuration used when no adapter is specified, along with npm package download counts. For 2026-10-01~30, @nestjs/platform-express had 44,534,475 downloads and @nestjs/platform-fastify had 7,725,701. Download counts are a reference for the scale of package use, not user market share.

Spring MVC

We referred to the State of Spring 2024 survey, in which 70% of 1,464 respondents said they used WebMVC. Respondents could select multiple answers. We used Tomcat and virtual threads for the measurement.

FastAPI / Uvicorn

We chose Uvicorn, the default ASGI server for the official fastapi run command. We used 3 workers, uvloop and httptools.

How to read the results

  • JSON is the common task used for measurement. Memory is the observed total for the server container, including the runtime, processes and workers, rather than just the size of the JSON data.
  • We checked the same test field constraints using direct field checks in Spine, ValidationPipe/class-validator in NestJS, Jakarta Validation in Spring MVC and Pydantic in FastAPI.
  • Latency at a fixed request rate is calculated from the scheduled send time, including the wait to send. Under saturated load, the next request is sent after a response, so the measurement method differs. Compare latency under the same load conditions.
  • The results come from 3 measurements of 15 seconds each on a single host. DB, external APIs, CORS, authentication, TLS, per-request DI service calls and stability during long-running operation were outside the test scope.
  • For memory, we took the median of memory.current readings collected at one-second intervals within each measurement window, then the median across the 3 runs. This does not represent minimum required RAM or process RSS.

Public data and verification

We verified 30 primary measurement samples, 168 HTTP contract checks and 30 HDR histograms. There were no request errors or Docker OOMKilled flags. The ZIP contains the app source, measurement code, original zrk JSON and histograms.

Sources for selection and measurement criteria

← Back to home