mapsrv: speak HTTP/2, and measure what that is actually worth #33
No reviewers
Labels
No labels
claude-wip
map-report
map:is
map:se
needs-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
art/ismap!33
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "night/issue-5-http2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #5
Issue #5 — mapsrv is http/1.1
Branch:
night/issue-5-http2(commitb1c62d5, not pushed, not merged)State: done
What changed
mapsrv/main.go—main()no longer callshttp.ListenAndServe. There is now aserve()method that builds an explicithttp.ServerwithProtocolsset toHTTP/1.1 + HTTP/2 + unencrypted HTTP/2, plus two new optional config keys,
tls_cert/tls_key, which switch it toListenAndServeTLS. Set only one of thetwo and it dies at startup with a clear message rather than silently serving plaintext.
No new dependency:
http.Protocolsand the HTTP/2 server are stdlib as of Go 1.24(go.mod says 1.25, the toolchain here is 1.26), so
golang.org/x/net/http2is neitherneeded nor imported.
Docs:
CLAUDE.md"Running the server", a newdocs/performance.mdsection("Serving: HTTP/2 moves the queue, it does not shorten it"), a commented block in
conf/mapsrv.yamlincluding the openssl one-liner for a local cert, and one sentencein the
README.mdcomponent list.The finding, which is the actual deliverable
A browser only speaks HTTP/2 over TLS. None has ever implemented cleartext HTTP/2
and none is going to, so turning on h2c alone would have closed this issue while
changing nothing at all for the map page —
http://localhost:8080stays HTTP/1.1however much the server supports. That is why the change includes TLS config keys
rather than just a one-line
Protocolsflip. Non-browser clients (curl--http2-prior-knowledge,bench/, a reverse proxy) do get h2c on the plain listener.It is worth about 9%, and the reason is instructive. Headless Chrome over CDP, 121
z12 tiles over Stockholm fired at once with
fetch, cache-busted, same server on bothorigins, alternating, 5 runs with the first discarded as a PostgreSQL warmup:
http://:8080)https://:8443)The six-connection limit is real and exactly six. But the two rows are the same total
in different places: over HTTP/1.1 115 tiles wait in the browser, over HTTP/2 all 121
reach the server and wait on the 8-connection pgx pool and on PostGIS instead.
ST_AsMVTwas always the bottleneck (the section above it indocs/performance.mdmeasures a heavy tile at ~250 ms, 86% of the request), so multiplexing mostly relocates
the wait. It would be worth more over a real network with real RTT than over loopback.
The risk was cancellation, and it is fine. HTTP/2 makes the abandoned-tile problem
structurally worse — every abandoned tile now reaches a handler instead of dying in the
browser's queue. 121 tiles abandoned after 60 ms, then one tile we do want: 86 ms over
HTTP/2 against a 59 ms idle baseline (71/89 ms over HTTP/1.1). No regression. That is
entirely down to
tiles.gopassingr.Context(), which is now load-bearing for ~4×as many requests; I noted that in CLAUDE.md next to the existing "do not drop this".
How it was verified
make build,make test(mapsrv handler tests pass),go vet,gofmtclean on thefile I touched. (
gofmt -l mapsrvalso listssprite.go— pre-existing, untouched,left alone.)
make restart+make smoke-test: OK, 221 bytes.curldefault →1.1;curl --http2-prior-knowledge→2on/tiles/*,/style.json,/api/me,/sprite@2x.png,/fonts/Go%20Regular/0-255.pbf— every route family, and the gzip middleware works over h2 unchanged.
openssl s_client -alpn h2,http/1.1reportsALPN protocol: h2; curl negotiates HTTP/2 and--http1.1stillfalls back cleanly.
tls_certwithouttls_key) exits with the intended error.none is needed — this change does not touch the schema, the importer or the style.
Left undone / uncertain
mainand restarted at the end, so therunning server is HTTP/1.1-only again until the branch is merged.
make smoke-testOK.conf/mapsrv.yamlships the keys commented out and the box still serves plaintexton :8080, i.e. the map page in a browser is still HTTP/1.1. Enabling it is a decision
for you: a self-signed cert means clicking through a browser warning on every fresh
profile, which for ~9% on loopback may well not be worth it. The openssl line is in
the config comment when you want it. If the map ever gets served over a real hostname
with a real certificate, this now gives HTTP/2 for free.
bench/was not taught to use HTTP/2. It would be a two-line change(
Transport.Protocols), but the browser measurement answered the question and I didnot want to widen the diff on a night run. Worth a small follow-up issue if you want
the numbers reproducible from the repo's own tool rather than from a scratchpad script.
MaxConnections: 8inmain.go), where before the browser's six connections were the tighter of two. I didnot change it: 8 matches the core count and a tile is single-threaded CPU, so
raising it would just move the queue again. Flagging it because the tuning question is
now live in a way it was not before.
method is described in
docs/performance.md("How to measure any of this") and themeasurement is reproducible from it; note that
--disable-gpuis fine for a rawfetchburst but kills MapLibre, as that section already warns.b1c62d5124tod1fb31c414