The web server leaves Nagle's algorithm on, so a response sent in parts can wait for the browser's acknowledgement #170

Closed
opened 2026-10-09 06:18:15 -07:00 by rays · 1 comment
Owner

axum 0.8 no longer sets TCP_NODELAY on accepted connections (0.7 did), and serve in src/web.rs does not set it, so every browser connection has Nagle on. See https://brooker.co.za/blog/2024/05/09/nagle.html.

Measured on production 2026-10-09: no stall today. /api/me and /api/tokens take 0.7-1.4 ms over one keep-alive connection, and /api/feeds (97 KB) arrives within a fraction of a millisecond of its first byte, because hyper sends each response in one write.

The risk is under load: when the send buffer is full, or a body goes out in several writes (large JSON, audio in chunks, live updates on /api/events in a burst), the last small segment waits for the client's delayed ACK, up to about 40 ms. Turning Nagle off costs almost nothing here, since ipx does not make tiny writes.

Fix: tap_io on the listener, set_nodelay(true) on each connection. Check with node tests/load/run.js before and after.

axum 0.8 no longer sets TCP_NODELAY on accepted connections (0.7 did), and `serve` in src/web.rs does not set it, so every browser connection has Nagle on. See https://brooker.co.za/blog/2024/05/09/nagle.html. Measured on production 2026-10-09: no stall today. /api/me and /api/tokens take 0.7-1.4 ms over one keep-alive connection, and /api/feeds (97 KB) arrives within a fraction of a millisecond of its first byte, because hyper sends each response in one write. The risk is under load: when the send buffer is full, or a body goes out in several writes (large JSON, audio in chunks, live updates on /api/events in a burst), the last small segment waits for the client's delayed ACK, up to about 40 ms. Turning Nagle off costs almost nothing here, since ipx does not make tiny writes. Fix: `tap_io` on the listener, `set_nodelay(true)` on each connection. Check with `node tests/load/run.js` before and after.
rays added the enhancement label 2026-10-09 06:18:15 -07:00
Author
Owner

Fixed in 8687e4f, deployed as 0.10.4-dev: serve sets TCP_NODELAY on each accepted connection with tap_io. tests/load before and after: media range requests median 41.8 ms to 10.3 ms, p95 44.4 to 13.2 ms, throughput 39 to 149 MB/s. Browse and sign-in unchanged within noise. Flag saves vary 80-195 ms on either version across three runs each (waiting behind the scan's writes), so no change there.

Fixed in 8687e4f, deployed as 0.10.4-dev: `serve` sets TCP_NODELAY on each accepted connection with `tap_io`. tests/load before and after: media range requests median 41.8 ms to 10.3 ms, p95 44.4 to 13.2 ms, throughput 39 to 149 MB/s. Browse and sign-in unchanged within noise. Flag saves vary 80-195 ms on either version across three runs each (waiting behind the scan's writes), so no change there.
rays closed this issue 2026-10-09 06:41:46 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rays/ipx#170