Effect HttpApi cold start
effect npm 4.0.5 · node v24.21.0 · linux 6.18.54 · Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz (8 cores) · rolldown 1.2.14 · 30 fresh processes per size · Sat, 17 Oct 2026 09:53:24 GMT
Cold start = start the server and answer one request. Each sample launches a brand-new node process running a single rolldown bundle of an effect/http-api server (HttpRouter.serve on @effect/platform-node's NodeHttpServer), waits until it is listening, sends one real HTTP GET over loopback and stops the clock when the response has been fully received. The clock starts just before spawn(), so it covers everything: process creation, Node boot, parsing, compiling and evaluating the bundle, defining the API, building the handler, binding the socket, and serving a request with code V8 has never run before. APIs have 1, 10, 50, 100, 500 endpoints.
Summary. Starting a minimal HttpApi server and getting a response back takes 140 ms: 39 ms to spawn and boot Node, 74 ms to load the bundle, 19 ms to define the API, build the handler and listen, and 8 ms for the first request. Each endpoint adds ~154 µs, so 500 endpoints take 218 ms. About 17 ms of every Node server cold start is Node initializing undici for web APIs the server never uses (§4). The biggest finding is that most of the post-load time is not the work itself but running it cold: rebuilding the same API in the same process once V8 has compiled and optimized the code takes 1.0 ms instead of 15.5 ms at 1 endpoint and 43 ms instead of 90 ms at 500. Cold start is therefore driven as much by how much distinct code runs at startup as by how much work it does.
1 · Anatomy of a cold start
Every phase from spawn() to the HTTP response arriving back at the client, as medians and in execution order. Spawn is process creation plus the time for the "listening" line to reach the client (measured with an empty script: 37.9 ms to its first output line, which also includes Node boot). Define API is HttpApi.make(…).add(…) with all its schemas. Build handler + listen is HttpApiBuilder plus building the HttpRouter.serve layer on NodeHttpServer until the socket is bound. First HTTP request is the client's round trip for one GET, of which 6.2 ms is spent inside the server.
Server cold start: spawn → first HTTP response
median ms · hover for values
Table view
| Endpoints | Spawn | Node boot | Module load | Define | Build + listen | First HTTP request | Cold start p50 | p95 | CPU (all threads) | Bundle KB |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 11.9 | 27.3 | 73.8 | 2.2 | 17.1 | 7.6 | 140.3 | 142.9 | 146.1 | 765 |
| 10 | 12.0 | 27.3 | 73.6 | 3.4 | 19.1 | 7.5 | 142.5 | 145.9 | 150.1 | 768 |
| 50 | 12.5 | 27.3 | 72.9 | 7.9 | 20.3 | 7.5 | 148.6 | 158.6 | 157.4 | 782 |
| 100 | 13.0 | 27.3 | 74.2 | 10.8 | 24.8 | 7.6 | 158.5 | 162.3 | 175.4 | 800 |
| 500 | 13.1 | 27.3 | 76.1 | 35.5 | 54.8 | 10.1 | 217.9 | 227.4 | 308.4 | 944 |
- Before any API code runs, a server has already spent 113 ms on spawning, booting Node and loading the 765 KB bundle: 81% of a small server's cold start.
- Define + build + listen grow with the API: 19 ms at 1 endpoint, 90 ms at 500.
- The first HTTP request takes 7.6–10.1 ms. It is cold too: the server runs the request path (parsing, routing, decoding, encoding) for the first time.
Web-handler mode (Worker-style)
The same app as a web handler (HttpRouter.toWebHandler, platform-neutral bundle, request passed in-process as a Request), which is how it would run on Cloudflare Workers or another fetch-based runtime. There is no spawn, socket or Node server stack, and the clock runs from process start to the response body. This mode is also what the CPU profiles and the cold/warm experiment below use.
Web handler: process start → first response
median ms · module load split by CPU-profile shares · hover for values
Table view
| Endpoints | Node boot | Parse & compile | Module eval | undici (Node) | Define | Build | First request | Cold start p50 | p95 | CPU (all threads) |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 27.3 | 16.4 | 14.6 | 26.4 | 2.4 | 5.6 | 7.5 | 100.2 | 103.7 | 113.4 |
| 10 | 27.3 | 16.6 | 13.5 | 27.5 | 3.8 | 7.8 | 7.5 | 104.5 | 108.5 | 119.1 |
| 50 | 27.3 | 16.9 | 13.4 | 27.0 | 7.1 | 11.8 | 7.4 | 112.6 | 115.6 | 129.9 |
| 100 | 27.3 | 16.7 | 13.8 | 27.9 | 14.3 | 13.8 | 7.5 | 120.0 | 122.4 | 143.3 |
| 500 | 27.3 | 19.1 | 13.8 | 28.0 | 36.8 | 45.1 | 7.7 | 179.3 | 185.8 | 245.7 |
- Small APIs are dominated by fixed cost. At 1 endpoint, 85% of the cold start happens before any API code runs. Of the 57 ms module load, ~17 ms is parsing and compiling the 552 KB bundle and ~27 ms is Node initializing undici (Node-only, see §4).
- In large APIs, define + build become the largest part: 46% of the cold start at 500 endpoints, ahead of module load (34%) and Node boot (15%).
- The first request always pays ~8 ms regardless of size. A warm request takes 0.6 ms, so this is almost entirely first-time code execution (see §2).
- CPU exceeds wall time. V8's concurrent compiler and GC threads add CPU on top of the main thread: 246 ms CPU vs 179 ms wall at 500 endpoints. That matters wherever startup CPU is capped or billed.
2 · Cold code vs real work
To separate the cost of running code for the first time from the work itself, each process runs the cold pass and then rebuilds everything from scratch five more times in the same process: a new API definition, layer, handler and request each time. We report the last rebuild. The work is identical, but V8 has by then lazily compiled the functions, tiered them up from the interpreter and warmed inline caches, and any one-time lazy initialization has happened. Cold − warm is the cold-code penalty, a cost that exists only because it is a cold start.
Define + build + first request: work vs cold-code penalty
median ms, same process · hover for values
Table view
| Endpoints | Define cold / warm | Build cold / warm | First request cold / warm | Total cold / warm | Cold-code penalty |
|---|---|---|---|---|---|
| 1 | 2.4 / 0.2 (10×) | 5.6 / 0.3 (17×) | 7.5 / 0.4 (17×) | 15.5 / 1.0 | 14.5 ms (94%) |
| 10 | 3.8 / 0.6 (6.4×) | 7.7 / 0.7 (11×) | 7.4 / 0.5 (15×) | 18.9 / 1.8 | 17.1 ms (90%) |
| 50 | 8.1 / 2.6 (3.2×) | 11.7 / 2.8 (4.2×) | 7.4 / 0.5 (15×) | 26.4 / 5.8 | 20.6 ms (78%) |
| 100 | 14.5 / 3.7 (4.0×) | 13.9 / 8.8 (1.6×) | 7.6 / 0.5 (14×) | 35.8 / 13.0 | 22.8 ms (64%) |
| 500 | 36.9 / 16.2 (2.3×) | 44.8 / 12.6 (3.5×) | 7.7 / 0.6 (12×) | 90.0 / 42.7 | 47.3 ms (53%) |
- At 1 endpoint, 94% is penalty: the work takes 1.0 ms hot but 15.5 ms cold. The first request alone is 17× slower cold (7.5 vs 0.4 ms), and the handler build 17×.
- The penalty grows with API size too (47 ms at 500 endpoints) but its share shrinks to 53%: later endpoints reuse code paths the first ones already warmed. That is why the per-endpoint cost is higher for small APIs (396 µs/ep from 1→10) and settles at ~138 µs/ep.
- Implication. Algorithmic tuning of define/build only addresses the warm 82 µs/ep. Cutting the amount of distinct code executed at startup (fewer layers of indirection, fewer generic combinators on the build path, deferring per-route setup until a route is first hit) is what moves cold start.
Cold vs warm as the API grows
define + build + first request, median ms · hover for values
3 · How cold start grows with API size
Cold start grows linearly: ~154 µs per endpoint on top of ~103 ms fixed (R² 0.996). Define and build share the per-endpoint cost about evenly. Module load grows only with bundle size (+0.4 KB, +6.9 µs per endpoint), and the first request is flat.
Cold-start phases vs endpoint count
median ms · linear x axis · hover for values
Table view
| Endpoints | Module load | Define API | Build handler | First request | Cold start | CPU (all threads) | Bundle KB |
|---|---|---|---|---|---|---|---|
| 1 | 57.4 | 2.4 | 5.6 | 7.5 | 100.2 | 113.4 | 552 |
| 10 | 57.7 | 3.8 | 7.8 | 7.5 | 104.5 | 119.1 | 555 |
| 50 | 57.3 | 7.1 | 11.8 | 7.4 | 112.6 | 129.9 | 569 |
| 100 | 58.4 | 14.3 | 13.8 | 7.5 | 120.0 | 143.3 | 587 |
| 500 | 60.9 | 36.8 | 45.1 | 7.7 | 179.3 | 245.7 | 731 |
Linear fit
| Per endpoint | Fixed | R² | |
|---|---|---|---|
| Cold start (wall) | 153.7 µs | 103.0 ms | 0.996 |
| Cold start (CPU, all threads) | 260.6 µs | 115.8 ms | 0.999 |
| Define API | 67.0 µs | 4.0 ms | 0.979 |
| Build handler | 77.0 µs | 6.6 ms | 0.997 |
| Module load | 6.9 µs | 57.4 ms | 0.968 |
| First request | 0.4 µs | 7.5 ms | 0.672 |
| Define+build+first, warm | 82.4 µs | 2.0 ms | 0.991 |
| Bundle (unminified) | 0.4 KB | 551 KB | 1.000 |
Marginal cost (define + build + first)
µs per added endpoint between consecutive sizes. It is high for the first endpoints, while everything is still cold, then flat.
| Step | µs / endpoint |
|---|---|
| 1 → 10 | 396 |
| 10 → 50 | 186 |
| 50 → 100 | 178 |
| 100 → 500 | 138 |
What the per-endpoint cold cost is spent on
CPU profiles (5 per size) are split into phases using monotonic timestamps from the harness, and samples are attributed to Effect source files via sourcemaps. Category times are profile shares scaled to the un-profiled medians, and the per-endpoint figure is each category's linear-fit slope.
Cold per-endpoint cost by category
µs per added endpoint · hover for values
Table view
| Category | Define API µs/ep | Build handler µs/ep |
|---|---|---|
| Router (FindMyWay) | 0.0 | 22.2 |
| HttpApiBuilder | 0.0 | 12.9 |
| HttpApi definition | 12.0 | 2.4 |
| Schema | 22.6 | 12.5 |
| Effect runtime & Layer | 0.0 | 7.8 |
| pipe / dual / predicates | 9.0 | 7.8 |
| Garbage collector | 6.5 | 7.1 |
| Other | 16.7 | 4.2 |
| Total | 67.0 | 77.0 |
- Define (67 µs/ep): mostly Schema construction (23 µs:
schema/make.ts,SchemaAST.struct), then HttpApi bookkeeping (12 µs, includingvalidateResponseExclusivityon every endpoint) andpipe/dual(9 µs). - Build (77 µs/ep): the router is the largest single part (22 µs:
FindMyWay._ondoes string slicing,toLowerCaseandsplit/joinper segment), then per-route setup in HttpApiBuilder (13 µs:handlerToRoute, encoders, decoders), the Effect runtime running the layer (8 µs) and Schema codec derivation (13 µs). - GC adds 14 µs/ep: startup allocates many short-lived AST and closure objects.
Build handler, by size
ms (scaled)
Table view
| Category | 1 ep | 10 ep | 50 ep | 100 ep | 500 ep | µs / endpoint |
|---|---|---|---|---|---|---|
| Router (FindMyWay) | 0.6 | 1.2 | 1.7 | 2.4 | 11.8 | 22.2 |
| HttpApiBuilder | 0.8 | 1.2 | 2.1 | 2.5 | 7.5 | 12.9 |
| HttpApi definition | 0.0 | 0.3 | 0.3 | 0.6 | 1.4 | 2.4 |
| Schema | 0.3 | 0.5 | 1.3 | 1.9 | 6.7 | 12.5 |
| Effect runtime & Layer | 2.6 | 3.0 | 3.8 | 4.2 | 6.9 | 7.8 |
| pipe / dual / predicates | 0.4 | 0.6 | 0.7 | 0.9 | 4.3 | 7.8 |
| Garbage collector | 0.0 | 0.0 | 0.7 | 0.0 | 3.5 | 7.1 |
| Other | 0.8 | 1.0 | 1.2 | 1.3 | 3.0 | 4.2 |
Define API, by size
ms (scaled)
Table view
| Category | 1 ep | 10 ep | 50 ep | 100 ep | 500 ep | µs / endpoint |
|---|---|---|---|---|---|---|
| Router (FindMyWay) | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| HttpApiBuilder | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| HttpApi definition | 0.9 | 1.3 | 1.9 | 3.0 | 7.2 | 12.0 |
| Schema | 1.1 | 1.9 | 3.1 | 6.7 | 13.1 | 22.6 |
| Effect runtime & Layer | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| pipe / dual / predicates | 0.2 | 0.1 | 0.4 | 1.1 | 4.6 | 9.0 |
| Garbage collector | 0.0 | 0.0 | 0.3 | 1.3 | 3.3 | 6.5 |
| Other | 0.3 | 0.5 | 1.4 | 2.3 | 8.8 | 16.7 |
Define: hottest functions at 500 endpoints
| Function (source) | ms (profiled) |
|---|---|
(anonymous) (bundle):1 | 6.1 |
defineApi generated/api-50x10.mjs:7 | 5.8 |
(garbage collector) (garbage collector) | 4.6 |
make$16 schema/make.ts:46 | 3.3 |
(anonymous) src/Function.ts:1577 | 2.4 |
makeProto$2 http-api/HttpApiEndpoint.ts:899 | 2.2 |
(anonymous) src/Function.ts:1540 | 1.5 |
validateResponseExclusivity http-api/HttpApiEndpoint.ts:1328 | 1.5 |
struct src/SchemaAST.ts:3273 | 1.2 |
copy src/SchemaAST.ts:4341 | 1.1 |
Build: hottest functions at 500 endpoints
| Function (source) | ms (profiled) |
|---|---|
_on internal/router.ts:116 | 8.0 |
on internal/router.ts:72 | 4.5 |
(garbage collector) (garbage collector) | 4.4 |
(anonymous) http-api/HttpApiBuilder.ts:146 | 4.2 |
copy src/SchemaAST.ts:4341 | 2.3 |
(anonymous) src/Function.ts:1540 | 1.6 |
(anonymous) src/Function.ts:1577 | 1.4 |
handlerToHttpEffect http-api/HttpApiBuilder.ts:827 | 1.2 |
(anonymous) src/Function.ts:251 | 1.1 |
(anonymous) (bundle):1 | 1.1 |
4 · Module load: the fixed part
Module load takes ~57 ms at every size, before a single line of API code runs.
Inside module load
ms, profile shares scaled to the measured median, averaged over sizes
Table view
| Component | ms | share |
|---|---|---|
| Parse & compile bundle | 17.2 | 30% |
| Effect module evaluation | 10.1 | 17% |
| Node: undici init | 27.4 | 48% |
| Garbage collector / other | 3.8 | 7% |
- Parse & compile (~30%) scales with bundle size. HttpApi pulls in Schema, the HTTP server stack (request/response, cookies, multipart), streams and the router whatever the API uses, so the floor is 552 KB unminified.
- undici init (~48%, Node only). Node creates its fetch-related globals lazily, and the first touch loads undici and its dependencies (crypto, zlib, web streams). workerd has these natively. See below for what triggers it.
- Effect's own module evaluation (services, tags, schema singletons) is ~10 ms.
Measured levers
Module load under each lever (web-handler bundle)
median ms · each lever measured on its own · 1-endpoint app, except the FormData A/B (10x10 app; module load barely depends on size)
Table view
| Lever | Module load (1 ep) | Saving | Applies to |
|---|---|---|---|
| As shipped | 57.4 | ||
| Minify (rolldown/Oxc) | 44.5 | −13.0 ms | any runtime |
| Node compile cache (V8 code cache on disk) | 41.5 | −15.9 ms | Node (e.g. Lambda with a cache dir) |
No globalThis.FormData at module scope | 28.2 | −29.6 ms | moves to the first new Request() on Node; a server also needs the node:http fix (§4) |
All sizes
| Endpoints | As shipped | Minified | Compile cache | Bundle KB | Minified KB |
|---|---|---|---|---|---|
| 1 | 57.4 | 44.5 | 41.5 | 552 | 220 |
| 10 | 57.7 | 45.1 | 43.0 | 555 | 221 |
| 50 | 57.3 | 45.1 | 42.8 | 569 | 229 |
| 100 | 58.4 | 45.7 | 41.8 | 587 | 238 |
| 500 | 60.9 | 47.9 | 42.8 | 731 | 312 |
- Minify: −13 ms on average (552 → 220 KB). Works on any runtime.
- Node compile cache (
NODE_COMPILE_CACHE, V8 code cache on disk): −16 ms of module load. It leaves define/build unchanged, because the cache holds the code compiled at load time, while the functions that only run later during define and build are still compiled lazily on first call.
Why every Node server pays for undici
Two independent things load undici during startup, each costing about 30 ms in isolation (Node v24.21.0):
Schema.tsevaluatesinstanceOf(globalThis.FormData)at module scope. TouchingFormDatain an empty process costs 29.2 ms.- An ESM
import … from "node:http", which@effect/platform-nodedoes. Node 24'shttpmodule exposes lazy, undici-backedWebSocket,CloseEventandMessageEventgetters, and an ESM import snapshots every export, which triggers them:import("node:http")costs 32.2 ms, whileprocess.getBuiltinModule("node:http")(orrequire) costs 4.7 ms.
Server cold start with the undici triggers removed
10x10 API, median of 25 runs · each variant patches the bundle
Table view
| Server variant (10x10) | Module load | Cold start | undici loaded? |
|---|---|---|---|
| As shipped | 71.8 | 157.7 | yes |
| FormData ref removed | 71.3 | 157.8 | yes |
| node:http via getBuiltinModule | 71.4 | 156.5 | yes |
| Both removed | 54.1 | 140.6 | no |
Removing either trigger alone changes nothing, because the other one still loads undici. Removing both cuts server cold start by 17 ms (157.7 → 140.6 ms), and undici is never loaded. In web-handler mode the FormData reference is the only trigger: removing it drops module load from 58 to 28 ms, but the cost reappears in the first new Request() (7.5 → 34.4 ms), so on Node the total doesn't change; on workerd these globals are native and the cost doesn't exist.
A/B detail: web-handler mode
| Variant | Module load | Define | Build | First request | Total |
|---|---|---|---|---|---|
| As shipped | 57.8 | 14.3 | 13.3 | 7.5 | 120.3 |
| FormData ref removed | 28.2 | 15.7 | 14.4 | 34.4 | 121.1 |
What is in the bundle
KB of output per source category (unminified)
Table view
| Category | 1 ep | 500 ep |
|---|---|---|
| Router (FindMyWay) | 22.8 | 22.8 |
| HttpApi (definition + builder) | 66.2 | 66.3 |
| Schema | 156.6 | 156.6 |
| HTTP stack (effect/http) | 100.8 | 100.8 |
| Stream / Channel | 20.1 | 20.1 |
| Effect runtime & Layer | 89.1 | 89.1 |
| Other Effect modules | 92.7 | 92.7 |
| App code + harness | 3.9 | 182.5 |
5 · Where to cut cold start
- Defer per-route setup to the first hit on that route. Build is 77 µs/endpoint and is paid upfront for every route. Compiling a route's codecs and handler pipeline (
handlerToRoute,SchemaAST.copy/make) on first request would make cold start pay only for the routes a fresh instance actually serves. - Execute less distinct code at startup. 94% (small) to 53% (large) of post-load time is cold-code penalty. Fewer generic layers on the define/build path (
dual/pipewrappers, Effect-runtime-driven layer construction for what is synchronous setup) cut both the work and the code that has to be compiled. - Cheaper route registration.
FindMyWay._onis the top self-time function in build. Its per-segmentsplit/joinandtoLowerCasecould be skipped for plain segments, or the tree could be built lazily on first lookup. - Stop loading undici in Node servers (−17 ms per cold start, measured). Two changes are needed together: make the
FormData/Fileguards inSchema.tsread the global lazily inside the check, and have@effect/platform-nodeobtainnode:httpthroughprocess.getBuiltinModule("node:http")(Node ≥ 20.16 / 22.3) instead of a static ESM import. - For users: minify (−13 ms), enable the compile cache where the platform allows (−16 ms on Node), and reuse schema values across endpoints.
Methodology
- Server mode (headline). Harness + generated API +
@effect/platform-nodebundled by rolldown (platform: "node"). The runner, a long-lived Node process, records the time, spawns the server, waits for its "listening" line (port 0 on 127.0.0.1), sendsGET /r0/1(/r0for 1-endpoint groups) with a fresh connection, and stops the clock when the response body has been read. The server reports its own phase times and its CPU when the response is sent. - Web-handler mode. Harness + generated API bundled by rolldown into one ESM file (
platform: "neutral", JSDoc stripped, unminified unless stated).effectresolves from the published npm package exactly like an app. Each sample is a newnodeprocess: two discarded warmup processes per size (to warm the OS file cache only; every measured process is a cold V8 isolate), then 30 measured, run sequentially. Node's compile cache is off except in the lever experiment. - Cold start = time from process start (
performance.timeOrigin) until the first response body has been read. Node boot is the median time-to-first-line of an empty script (27.3 ms). CPU isprocess.cpuUsage()at the first response (all threads). - API shape.
Ggroups ×Eendpoints (1×1, 1×10, 5×10, 10×10, 50×10). Each group is a REST resource with an entity struct (int, strings, literal union, optional, array, nested struct), create/update payloads, aTaggedError404, a struct 409, and list/get/create/update/delete endpoints plusactionNendpoints with their own schemas. Handlers are trivial. - Cold vs warm. In-process rebuilds with
BENCH_WARM=5, last iteration reported, 15 processes per size. - Attribution.
--cpu-profat 100 µs, 5 profiles per size. Samples are assigned to phases withprocess.hrtimemarks (the profiler's own monotonic clock) and to sources through sourcemaps chained into Effect's.tsfiles. Before the harness runs, samples are classified as bundle compile (undercompileSourceTextModule), module code (bundle frame on the stack) or Node boot.node:frames under an undici frame count as undici init. - Caveats. Node is not workerd. V8 is the same, so the scaling, the cold-code penalty and the hot spots carry over, but absolute times, Node boot and the undici cost do not.
Appendix
Not part of cold start: OpenAPI generation. OpenApi.fromApi costs 151 µs/endpoint (84 ms at 500 endpoints), but HttpApiScalar, HttpApiSwagger and openapiPath all build it lazily and memoize it, so only the first /docs request on an instance pays it.
Raw medians / p95 (ms) for every phase
| Endpoints | eval | define | layer | handler | first | second | openapi | CPU @1st resp | heap MB |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 84.7 / 88.3 | 2.4 / 2.4 | 0.2 / 0.2 | 5.4 / 5.4 | 7.5 / 7.6 | 0.7 / 0.7 | 6.1 / 6.3 | 113.4 / 116.5 | 10.4 / 10.4 |
| 10 | 84.9 / 89.3 | 3.8 / 3.9 | 0.2 / 0.2 | 7.5 / 9.4 | 7.5 / 7.6 | 0.6 / 0.7 | 10.1 / 13.2 | 119.1 / 124.7 | 11.0 / 11.1 |
| 50 | 84.6 / 88.0 | 7.1 / 10.7 | 0.4 / 1.6 | 11.4 / 11.8 | 7.4 / 7.5 | 0.6 / 0.6 | 17.0 / 17.8 | 129.9 / 133.2 | 14.0 / 14.0 |
| 100 | 85.7 / 88.5 | 14.3 / 14.7 | 0.4 / 0.5 | 13.4 / 13.6 | 7.5 / 7.8 | 0.6 / 0.7 | 25.0 / 25.7 | 143.3 / 145.5 | 16.3 / 16.4 |
| 500 | 88.1 / 92.7 | 36.8 / 40.4 | 1.3 / 1.4 | 43.8 / 45.4 | 7.7 / 7.9 | 0.6 / 0.7 | 83.6 / 88.3 | 245.7 / 253.5 | 27.0 / 27.5 |
Reproduce: node scripts/collect.mjs && node scripts/experiment-undici.mjs && node scripts/render-report.mjs · source: github.com/mikearnaldi/benchmark-http-api-boot