Содержание
ClickHouse исправил причину лишней задержки в 32 мс на простых SELECT-запросах через Native-протокол. После простоя соединения больше двух секунд окно перегрузки TCP сбрасывалось, а несколько мелких пакетов метаданных не помещались в него сразу. Это вызывало дополнительный круг обмена подтверждениями. Исправление убрало лишний RTT без изменения поведения протокола для клиента. RTT здесь означает время, которое требуется пакету, чтобы дойти от клиента до сервера и обратно; в сетях с задержкой 20-40 мс один такой круг заметно влияет на отклик. Для интерактивных клиентов и сценариев health-check это важно, потому что лишние 32 мс увеличивают p99 и портят впечатление от быстрых запросов. Обновление стоит рассмотреть, если в кластере преобладают короткие запросы после пауз и используется именно Native-протокол.
Как протокол и TCP создают лишний круг
Native-протокол требует, чтобы клиент дождался пакета EndOfStream, прежде чем считать запрос завершённым. Этот пакет сигнализирует об окончании передачи данных и метаданных. Между блоками Data и EndOfStream сервер отправляет пакеты Logs, Progress и ProfileEvents. Они содержат служебную информацию: логи выполнения, прогресс запроса и статистику профилирования. При включённом TCP_NODELAY каждое такое сообщение уходит отдельным сегментом TCP. TCP_NODELAY отключает алгоритм Нейгла, который обычно объединяет мелкие пакеты, поэтому каждое сообщение летит сразу. После простоя дольше Retransmission Timeout ядро сбрасывает congestion window до initcwnd, который в данном случае равнялся восьми MSS. Congestion window ограничивает объём данных, которые можно отправить без подтверждения; initcwnd — это начальное значение этого окна, а MSS — максимальный размер сегмента. Если мелких пакетов больше, чем помещается в окно, ядро останавливается и ждёт ACK от клиента. Получается ровно один дополнительный RTT, равный 32 мс в сети заказчика.
Проблема проявляется только на простых запросах, где результат мал и не заполняет окно сам по себе. На тяжёлых запросах объём данных Data-пакетов обычно уже превышает initcwnd, поэтому метаданные отправляются без задержки. Таким образом, эффект виден именно на коротких запросах после паузы, что типично для интерактивных клиентов и health-check сценариев. Перед обновлением стоит оценить долю таких запросов в нагрузке: если они составляют заметную часть трафика и RTT между узлами превышает 20 мс, исправление даст ощутимое снижение задержек.
Воспроизведение в контролируемых условиях
Для точного повторения поведения использовали сетевые пространства Linux без Docker. В одном namespace запускали сервер, во втором — клиент. С помощью tc между ними добавляли задержку 32 мс в обе стороны. Запросы отправляли по одному соединению с паузой две секунды. Это позволяло стабильно наблюдать переход от 32 мс к 64 мс на втором запросе. Измерения проводили на уровне приложения, фиксируя время между отправкой Query и получением EndOfStream. Такая схема показала, что виноваты именно три-четыре мелких пакета метаданных, а не само ядро или настройки сети. После отключения TCP_NODELAY проблема исчезала, но это снижало общую пропускную способность, поэтому выбрали другой путь. Воспроизведение помогает понять, стоит ли тратить время на обновление: если на тестовом стенде с похожей задержкой разница между первым и последующими запросами превышает 10 мс, эффект реален и исправление принесёт пользу.
Как изменили отправку пакетов
Разработчики сгруппировали все метаданные перед отправкой в одном системном вызове записи. Пакеты Logs, Progress и ProfileEvents теперь собираются в буфер и отправляются одним сегментом, пока окно ещё не сброшено или уже успело вырасти. Изменение касается только серверной стороны и не требует обновления клиентов. После правки повторные запросы после простоя укладываются в один RTT даже при initcwnd равном восьми. Для тех, кто использует JDBC/ODBC-драйверы или кастомные клиенты на Go и Python, это означает, что не нужно менять код или настройки соединений. В российских сетях, где RTT между зонами доступности часто составляет 20-40 мс, похожий эффект может появляться на кластерах с длинными idle-периодами. Обновление ClickHouse до версии с исправлением даёт измеримое снижение p99 без изменения настроек ОС или сети. Решение о rollout стоит принимать после проверки, что текущая версия действительно использует Native-протокол, а не HTTP, потому что HTTP-подключения не затрагиваются этой проблемой.
Что проверять перед обновлением
Перед rollout стоит убедиться, что в текущей версии действительно используется Native-протокол, а не HTTP. Также полезно измерить задержку на тестовом стенде с искусственной задержкой 30-40 мс и idle 2-3 секунды. Если разница между первым и последующими запросами превышает 10 мс, обновление снимет лишний RTT. В мониторинге стоит добавить метрику времени между Data и EndOfStream, чтобы отслеживать подобные эффекты в будущем. Эти проверки позволяют принять обоснованное решение: если метрика покажет стабильный лишний RTT, обновление снизит tail-латентность; если разницы нет, можно отложить обновление и сосредоточиться на других оптимизациях.
Источник
Это краткий разбор материала ClickHouse. Полная версия с примерами кода, схемами и деталями реализации — в оригинале: ClickHouse убрал лишний RTT в Native-протоколе.
Курс по теме — Построение DWH на ClickHouse


