ClickhouseProblem & solution
Chasing that 32 ms tail latency in ClickHouse
Summary
A customer observed an extra ~32 ms tail latency on simple SELECT queries after idle periods in ClickHouse’s Native TCP protocol. The root cause was that after a TCP idle period (RTO), the congestion window resets to initcwnd (8 MSS), and with TCP_NODELAY enabled, each metadata packet (ProfileEvents, Logs, Progress) becomes a separate TCP segment, exceeding the small window and forcing an additional round trip. The ClickHouse Native protocol sends these metadata packets between Data and EndOfStream, and the unbatched writes caused the problem. The team reproduced the issue deterministically using Linux network namespaces and tc to simulate 32 ms RTT and initcwnd 8. They fixed it by batching those metadata packets into a single buffer write at the application layer, reducing tail latency from ~32 ms to ~1 ms, as confirmed by a Go client program that measured the time between the last Data packet and EndOfStream.
Context
A customer observed an extra ~32 ms tail latency on simple SELECT queries after idle periods in ClickHouse’s Native TCP protocol. The root cause was that after a TCP idle period (RTO), the congestion window resets to initcwnd (8 MSS), and with TCP_NODELAY enabled, each metadata packet (ProfileEvents, Logs, Progress) becomes a separate TCP segment, exceeding the small window and forcing an additional round trip.
Approach / What changed
The team fixed the issue by batching the metadata packets (ProfileEvents, Logs, Progress) into a single buffer write at the application layer before flushing to the socket, reducing the TCP segment count and eliminating the extra round trip.
Takeaways
After a TCP idle period (RTO), the Linux congestion window resets to initcwnd, which can be as low as 8 MSS, limiting the number of unacknowledged packets per round trip.
With TCP_NODELAY enabled, each application write() becomes a separate TCP segment; multiple small metadata writes can exhaust the congestion window and cause an extra round trip.
Batching metadata packets into a single buffer write at the application layer eliminates the extra round trip and reduces tail latency from ~32 ms to ~1 ms on high-RTT connections.
FAQ
Why does tail latency spike after idle periods in ClickHouse?
After an idle period longer than the RTO, the TCP congestion window resets to its initial value (initcwnd). With TCP_NODELAY enabled, each metadata packet becomes a separate TCP segment, and if the total exceeds the window, the kernel waits for an ACK before sending the rest, adding a full round trip.
How was the issue reproduced?
The team used Linux network namespaces (netns) and tc to simulate a 32 ms RTT and set initcwnd to 8. A Go client program ran simple SELECT queries with a 2-second idle period and measured the time between the last Data packet and EndOfStream.
What was the fix?
The fix was to batch the metadata packets (ProfileEvents, Logs, Progress) into a single buffer write at the application layer before flushing to the socket, reducing the number of TCP segments and preventing the congestion window from being exceeded.
Source and freshness
- Source
- clickhouse.com
- Published
- Added to Yomu
Summaries are generated from the original by a language model. Check the original before relying on it.
Keep reading
Related reading
The Good Stuff
Paid picks we use ourselves. Links carry a referral code — a subscription through one earns us a commission at no extra cost to you.
The best engineering reads, once a week
One useful email. Unsubscribe anytime.