fiber, fiber-tuned: prefork, maintainers, a correctness pass, and a tuned sibling - #1428
Merged
Merged
Conversation
fiber (standard)
----------------
- meta.json carried an empty maintainers array, so nobody was pinged on a
PR touching the entry. It now names the gofiber/maintainers team from
the Fiber repo's CODEOWNERS.
- EnablePrefork is on: one Go runtime per logical CPU behind one
SO_REUSEPORT socket per worker. The master binds nothing and loads
neither dataset nor Postgres pool nor Redis client, and the pool is
DATABASE_MAX_CONN divided by the worker count, since that budget
belongs to the container rather than to a process.
- Static files pick up the pre-compressed .br/.gz sibling the harness
ships beside each file, chosen with Fiber's AcceptsEncodings. The
static profiles send "br;q=1, gzip;q=0.8" and fasthttp's matcher
compares whole tokens, so the entry was answering the whole 20-file
rotation uncompressed: 1.21 MB per rotation becomes about 318 KB.
- GET /delay/{ms} implemented, async subscribed.
Fixes:
- TLS took Fiber's CertFile/CertKeyFile path, which installs
TLSHandler.GetClientInfo as GetCertificate and writes clientHelloInfo
on a shared struct with no synchronisation on every handshake
(fiber/v3@v3.5.0 ctx.go:95-98). Three profiles drive :8081 with
overlapping handshakes; reproduced under -race with 120 concurrent
ones. ListenConfig.TLSConfig takes the branch that installs no handler,
with the same TLS posture. This affects every Fiber application on that
path, not just this entry.
- A listener bind failure could hang the worker instead of exiting.
- SIGTERM did not drain: Listen returns straight away, so main exited and
took the in-flight response with it.
- /delay/{ms} overflowed int64 past roughly 1e14 ms and answered
immediately, the one answer that profile forbids. Over an hour is 404.
- queryItems swallowed a mid-iteration row error behind a short 200.
- crudCreate is an upsert but never invalidated its Redis key.
- loadDataset, loadPgPool and a malformed REDIS_URL failed silently.
Rules:
- The async-db pool was clamped to NumCPU()*4 on top of the budget, which
that profile's standard rule forbids. The clamp is gone.
- Hand-rolled c.Query + strconv.Atoi helpers give way to fiber.Query[int]
and fiber.Params[int].
The leaderboard description was 494 characters against a board median of
136; it is 284 now and names the pre-compressed static path. Every
remaining claim in README.md, meta.json and the comments was checked
against the code and corrected where wrong.
fiber-tuned (tuned, new)
------------------------
The fiber entry with the four things it leaves at Fiber's defaults:
- sonic behind Config.JSONEncoder/JSONDecoder, pretouched at startup.
- compress middleware at LevelBestSpeed (brotli 0, gzip 1). Only
json-comp is affected; the static twins are pre-compressed.
- fasthttp pinned to master c96f600 for valyala/fasthttp#2366, which
replaced the unmaintained andybalholm/brotli with molecule-man/go-brrr.
Fiber 3.5.0 builds against it unchanged.
- Postgres pool filled at startup (MinConns = MaxConns). The size is
unchanged, so what is tuned is when the connections open, not how many.
Routes, prefork, static and TLS paths are fiber's, unchanged. Measured in
a 4-vCPU sandbox (one worker per build pinned to a core, the generator on
two others, CPU per request from /proc, two rounds alternating), json-comp
goes from 367-378 us to 97-98 us; the control endpoint spreads 11-16 us
across identical code, so nothing smaller is called a difference. Both
READMEs carry the isolated benchmarks and a level-by-level go-brrr table.
docs
----
site/content/docs/test-profiles/h1/static-tls/implementation.md has said
the rotation is "~842 KB across 20 files" since before the fixtures grew.
Measured over data/static it is 1,271,603 B, of which 1,170,227 B is
compressible text.
Validated with the real suite on this tree, Postgres sidecar and TLS:
fiber 73 passed 0 failed, fiber-tuned 73 passed 0 failed. JSON bodies are
byte-identical between the two builds.
ReneWerner87
requested review from
Kaliumhexacyanoferrat and
MDA2AV
as code owners
September 2, 2026 15:19
Collaborator
|
/benchmark-multiple -f fiber,fiber-tuned --save |
Contributor
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Contributor
|
|
Collaborator
|
/benchmark-multiple -f fiber,fiber-tuned --save |
Contributor
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Contributor
Benchmark ResultsFrameworks: 2 | Test: ✅
|
| Test | Conn | RPS | Rate | CPU/req | p99 | p99.9 | CPU | Mem | Δ RPS | Δ Mem |
|---|---|---|---|---|---|---|---|---|---|---|
| baseline | 4096 | 2,569,980 | — | — | — | — | 6401.9% | 335MiB | +139.9% | +51.6% |
| pipelined | 4096 | 17,195,479 | — | — | — | — | 6729.7% | 338MiB | +44.7% | +158.0% |
| limited-conn | 4096 | 2,049,170 | — | — | — | — | 6465.0% | 770MiB | +439.0% | +1066.7% |
| json-comp | 4096 | 178,046 | — | — | — | — | 6197.0% | 758MiB | +38.2% | +87.2% |
| json-comp | 16384 | 179,380 | — | — | — | — | 6101.2% | 1.5GiB | +35.0% | +36.4% |
| json-tls | 4096 | 848,171 | — | — | — | — | 6410.3% | 653MiB | +68.5% | +69.6% |
| 8gbit | 512 | 49,172 | 0.9834 | 73.44us | 147.0us | 3311.0us | 340.4% | 298MiB | -0.3% | +259.0% |
| static-tls | 1024 | 557,319 | — | — | — | — | 6497.8% | 514MiB | +555.7% | +188.8% |
| async-db | 1024 | 238,426 | — | — | — | — | 5456.0% | 574MiB | +142.5% | +254.3% |
| latency-1m | 1024 | 997,607 | 0.9976 | 37.39us | 256.0us | 505.0us | 4251.9% | 271MiB | ~0% | +158.1% |
| latency-10k | 1024 | 9,983 | 0.9983 | 42.78us | 105.0us | 248.0us | 40.8% | 260MiB | ~0% | +217.1% |
| async | 32000 | 1,519,706 | — | — | — | — | 4245.8% | 995MiB | NEW | NEW |
✅ fiber-tuned
| Test | Conn | RPS | Rate | CPU/req | p99 | p99.9 | CPU | Mem | Δ RPS | Δ Mem |
|---|---|---|---|---|---|---|---|---|---|---|
| baseline | 4096 | 2,495,624 | — | — | — | — | 6398.6% | 908MiB | ~0% | ~0% |
| pipelined | 4096 | 16,909,280 | — | — | — | — | 6730.6% | 895MiB | ~0% | ~0% |
| limited-conn | 4096 | 2,004,197 | — | — | — | — | 6271.2% | 1.0GiB | ~0% | ~0% |
| json-comp | 4096 | 659,490 | — | — | — | — | 6303.9% | 1.0GiB | ~0% | ~0% |
| json-comp | 16384 | 641,662 | — | — | — | — | 6345.8% | 1.6GiB | ~0% | ~0% |
| json-tls | 4096 | 1,052,542 | — | — | — | — | 6259.1% | 962MiB | ~0% | ~0% |
| 8gbit | 512 | 49,337 | 0.9867 | 72.33us | 142.0us | 4174.0us | 348.4% | 840MiB | ~0% | ~0% |
| static-tls | 1024 | 546,827 | — | — | — | — | 6493.1% | 824MiB | ~0% | ~0% |
| async-db | 1024 | 242,289 | — | — | — | — | 4625.2% | 908MiB | ~0% | ~0% |
| latency-1m | 1024 | 998,228 | 0.9982 | 37.50us | 249.0us | 4110.0us | 4257.8% | 868MiB | ~0% | ~0% |
| latency-10k | 1024 | 9,982 | 0.9982 | 43.15us | 106.0us | 206.0us | 41.1% | 871MiB | ~0% | ~0% |
| async | 32000 | 1,517,624 | — | — | — | — | 4238.2% | 1.2GiB | ~0% | ~0% |
MDA2AV
approved these changes
Sep 3, 2026
Contributor
Author
|
thx |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
fiberentry was answering the whole static rotation uncompressed, running a data race on every TLS handshake, and carrying a set of claims about itself that the code did not support. This fixes those, turns prefork on, and addsfiber-tunedbeside it.fiber(standard)Static files were going out uncompressed
The static profiles send
br;q=1, gzip;q=0.8. fasthttp's Accept-Encoding matcher compares whole tokens, so that reads to it as "no encoding accepted at all" and the entry was sending all 20 files of the rotation raw.It now picks the pre-compressed
.br/.gzsibling the harness ships next to each file, chosen with Fiber's ownAcceptsEncodings. 1.21 MB per rotation becomes about 318 KB. Bodies are still read from disk per request, so a replaced file is served from the next request onwards.TLS was a data race
Fiber's
CertFile/CertKeyFileconvenience path installsTLSHandler.GetClientInfoasGetCertificate, and that callback writest.clientHelloInfoon a shared struct with no synchronisation, on every handshake (fiber/v3@v3.5.0ctx.go:95-98, wired atlisten.go:233-241). Three subscribed profiles drive:8081with overlapping handshakes. Reproduced withgo build -raceand 120 concurrent handshakes.The entry now passes
ListenConfig.TLSConfig, which takes the branch that installs no handler: same TLS 1.3, same cipher, same certificate, same ALPN posture, and the race build reports nothing.This one is worth a separate upstream issue. It affects every Fiber application on that path, not just this entry.
Prefork
EnablePreforkis on: one Go runtime per logical CPU behind one SO_REUSEPORT socket per worker. The master binds nothing and loads neither the dataset nor the Postgres pool nor the Redis client. The pool isDATABASE_MAX_CONNdivided by the worker count, because that budget belongs to the container rather than to a process.Whether that stays in standard mode is the call worth arguing about, so the README puts both sides in
## Preforkrather than leaving it to be discovered:For.
standard.mdallows it twice, "worker/thread counts matching available CPU cores" under Allowed and "setting worker count to match CPU cores" under deployment tuning, and its Not-allowed list covers none of it:EnablePreforkis a documented field with Fiber deployment guidance behind it. 34 standard non-infrastructure entries already carry the same mechanism in their own code or build files, counted as SO_REUSEPORT / a prefork manager /cluster.forkacrossframeworks/*, and 46 if entries that only describe per-core workers in prose are counted too. express, fastify and koa fork per core through Node'scluster; aiohttp calls itself "one forked worker per core sharing the port with SO_REUSEPORT". go-fasthttp, standard on the same profiles, calls the identicalreuseport.Listen, and axum added a per-core reuseport listener in #1361 and stayed standard.Against. Read literally,
baseline's standard string says "no worker count beyond framework defaults", which contradictsstandard.md. That reading is available, it just reclassifies the other 33 entries as well as this one.If you read it the other way, flipping this entry is a one-line
meta.jsonchange.Other fixes
err != nil && drained == nilis never true in a serving process.SIGTERMdid not actually drain.GracefulContextcloses the listener from a goroutine whileListenreturns straight away, somainexited and took the in-flight response with it. A request signalled 0.4s into a 1.5s handler now answers 200 at 1.5s./delay/{ms}multiplied an unbounded int bytime.Millisecond, so past roughly 1e14 ms it overflowed int64 and answered immediately, the one answer that profile forbids. Delays over an hour are 404 now.queryItemsswallowed a mid-iteration row error and returned a short list behind a 200.crudCreateis an upsert, so it can move a row a previous read has cached, but it never invalidated the Redis key.loadDataset,loadPgPooland a malformedREDIS_URLwere discarded silently.Rule compliance
runtime.NumCPU()*4on top of the budget, which that profile's standard rule explicitly forbids: "size the pool fromDATABASE_MAX_CONN, not from CPU count". The clamp is gone.c.Query+strconv.Atoihelpers give way tofiber.Query[int]/fiber.Params[int], since standard mode asks for the documented framework API where one exists. Behaviour is identical on every input the harness sends.New profile
GET /delay/{ms}implemented,asyncsubscribed.Maintainers and description
meta.jsoncarried an emptymaintainersarray, so nobody was pinged when a PR touched the entry. It now names thegofiber/maintainersteam that the Fiber repo's CODEOWNERS assigns: ReneWerner87, gaby, sixcolors, efectn, Fenny.The leaderboard description was 494 characters against a board median of 136, and most of it was prefork mechanics no other entry puts in the popup. It is 284 now, in line with gin (191), chi (202) and aiohttp (213), and it names the pre-compressed static path, which is the part of a static result a reader actually wants named. The mechanics stay in the README.
Every remaining sentence in
README.md,meta.jsonand the code comments was checked against the code, and the ones that were wrong are corrected: "one child per core" is one per logical CPU (64 workers on the board's 32c/64t cpuset), the static payload totals were stale, a 25 MB body limit justified itself with a profile that no longer exists (fiber.New()now takes no config at all), and a shutdown path was described thatdocker stopnever reaches.fiber-tuned(tuned, new)Same routes, same prefork, same static and TLS paths, same pool size as
fiber. Four deltas, each one a thing the standard entry leaves at the framework default because its line is "default configuration":Config.JSONEncoder/JSONDecoderinstead ofencoding/json, with the encoders pretouched at startup so the JIT cost lands off the hot path. Tuned mode's first bullet is "alternative JSON serializers".compressmiddleware atLevelBestSpeed(brotli 0, gzip 1) instead of the defaults (4 / 6). Only json-comp is affected; the static twins are pre-compressed, so the level never applies there.c96f600(2026-08-31) for valyala/fasthttp#2366, which replacedandybalholm/brotli(no longer maintained, its own author points at the successor) withmolecule-man/go-brrr. Fiber 3.5.0 builds against it unchanged and andybalholm drops out of the module graph. The reason is on therequireline.MinConns = MaxConns). The size is unchanged, so what is tuned is when the connections open, not how many.Same pairing shape the board already uses for helidon, trillium and fulmine.
Measured
Sandbox numbers, not board numbers: 4 vCPUs, one prefork worker of each build pinned to a core, the load generator on two others, CPU per request read from
/proc, two rounds in alternating order.fiberfiber-tuned/json/50?m=6,Accept-Encoding: gzip, br(json-comp)/json/50?m=6, no encoding (json-tls minus TLS)/baseline11?a=13&b=42(control, identical code both sides)The control row is the noise floor and spreads over 11-16 µs, so read the rest against it. json-comp moves by hundreds of microseconds and is real; nothing smaller is claimed as a difference. Both READMEs carry the isolated
go test -benchbreakdown (sonic vsencoding/json, go-brrr vs andybalholm at each level, gzip 6 vs 1) and a level-by-level go-brrr table, so the level choice is visible rather than asserted.What those tables also say about the standard entry: brotli at fasthttp's default level 4, on the library the release ships, is about two thirds of its whole json-comp cost, and four times what gzip at its own default takes for a body 2% larger. That is the default, and the standard entry ships the default.
When fasthttp releases #2366 and Fiber picks it up, that row of the pair closes on its own.
static-tls docs
site/content/docs/test-profiles/h1/static-tls/implementation.mdhas said "~842 KB across 20 files" since before the fixtures grew. Measured overdata/statictoday, the 20 files inrequests/static-rotate.luaare 1,271,603 B, of which 1,170,227 B is compressible text. The binary figure and the brotli total were still right. The sentence also now says that an entry serving the twins answers the whole rotation with about 318 KB, because the two woff2 and three webp files ship no pre-compressed sibling.site/leaderboard/search.jsindexes that page and is deliberately left alone. Regenerating it here would rungen_leaderboard_data.pyover a tree where fiber subscribes to one profile more than main's board data reflects, which would rewrite about 1055 achievement values for unrelated entries from a sandbox rather than from a benchmark run. It is regenerated on every result save and on deploy.Validation
Real suite on this tree, Postgres sidecar and TLS:
fiber: 73 passed, 0 failedfiber-tuned: 73 passed, 0 failedJSON bodies are byte-identical between the two builds, and the json-comp brotli output decodes to the same JSON.
Suggested run: