TLS и TCP
Как известно, данные в Интернете передаются с использованием многослойного стека протоколов. Сейчас нас интересует взаимодействие TCP и TLS. Основная задача TCP — надёжная доставка пакетов в исходном порядке. Если у нас есть сервис, использующий TLS (HTTPS-сайт), то все зашифрованные TLS данные будут отправляться с помощью TCP.
На уровне TCP: cразу после подключения, сервер может отправить не больше, чем initcwd пакетов (для старых систем это 3 пакета, для новых — 10). Далее сервер будет ждать подтверждения (ACK) от клиента и постепенно количество пакетов в окне отправки будет расти, а соединение будет увеличивать свою пропускную способность.
В случае с обычным HTTP-трафиком все отлично: с каждым новым пакетом приходят данные, которые браузер может использовать.
Проблема c TLS
Если мы используем TLS, то Nginx использует специальный буфер (размер задаётся директивой ssl_buffer_size), который управляет размером TLS record size. Браузер (клиент) может использовать данные только после получения TLS record полностью. При этом максимальный (и дефолтный в Nginx) размер ssl_buffer_size составляет 16k.
Так как начальное окно для отправки пакетов = 10, то мы можем получить примерно 14k трафика, что меньше TLS record (16k). Это может вызывать задержки в получении полезного контента.
А если вы используете HTTP/2, то стоит обратить внимание на настройку http2_chunk_size (по умолчанию 8k) — она устанавливает максимальный размер части, на которое делится тело ответа. При этом используется только одно подключение к серверу, поэтому в этом TCP соединении передаётся одновременно множество ресурсов, что увеличивает вероятность возникновения задержек.
Что можно сделать?
Самое простое, что можно сделать — уменьшить ssl_buffer_size, например до 8k или 12k. Это можно сделать в стандартной версии Nginx. Однако, при пересылке большого количества данных эффективность будет ниже (выше накладные расходы).
Получается, что идеального ssl_buffer_size не существует.
Динамический размер TLS record
Здесь на помощь приходит Cloudflare со своим набором патчей.
С использованием этих патчей мы получаем поддержку динамического размера TLS record.
На свежих соединениях размер записи устанавливается не больше размера одного пакета, после прохождения некоторого количества записей размер можно увеличить до 3 TCP-пакетов, а далее уже до максимального размера (16k). После простоя соединения процесс начинается снова. Все параметры этого процесса настраиваются.
Применение патчей
Чтобы получить новую функциональность, нужно применить патчи и собрать Nginx. О сборке Nginx с OpenSSL я уже писал ранее, поэтому остановимся на процессе применения патчей.
Для применения патчей нужно зайти на страницу github.
На этой странице нужно вычленить отдельные патчи для каждого файла. Запись самого патча начинается так:
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
Из этой записи понятно, к чему относится этот патч (в данном случае это src/event/ngx_event_openssl.c).
Копируем текст патча в файл (например, openssl.c.patch) и кладём рядом с файлом исходника.
Применяем патч следующей командой:
patch ngx_event_openssl.c < openssl.c.patch
Так проходимся по всем файлам патча (всего должно быть 4 файла).
Ну и собираем Nginx как обычно (я использовал 1.11.2, всё получилось).
Настройка Nginx
С патчем приходят новые настройки. Получаем примерно такие значения:
# Начальный размер записи, примерно 1 пакет
ssl_dyn_rec_size_lo 1369;
# Промежуточный размер записи, 3 пакета
ssl_dyn_rec_size_hi 4229;
# Количество записей для перехода к следующему размеру
ssl_dyn_rec_threshold 20;
# Время простоя для сброса размера до начального
ssl_dyn_rec_timeout 10;
# Стандартный буфер, ставим максимальное значение
ssl_buffer_size 16k;
Подробно можно почитать в исходной статье блога Cloudflare.
О самом принципе оптимизации TLS record size можно почитать в книге HPBN.
На этом у меня всё, пока внедрили у себя, тестируем. Если у вас уже есть опыт настройки, просьба поделиться в комментариях.
Комментарии (10)
VBart
21.07.2016 18:39+1А если вы используете HTTP/2, то есть еще буферизация с использованием http2_chunk_size (по умолчанию 8k).
Директива `http2_chunk_size` не имеет никакого отношения к буферизации.trong
21.07.2016 21:12+1А сам Nginx не планирует принять эти патчи в основную ветку?
VBart
21.07.2016 22:34Чтобы иметь 5 директив, которые невозможно настроить правильно в сколь угодно сложном окружении, которым является интернет?
Если очень хочется, то можно уменьшить
ssl_buffer_size
до 6-8k и этого более чем достаточно для обеспечения минимального TTFB в большинстве случаев.
rrpv
22.07.2016 08:21Альтернативный путь — можно изменить размер initcwd. В Linux это делается командой «ip ro», настраивается для маршрутов по отдельности. Также заодно можно подкрутить параметр initrwnd.
Доступно на ядрах 2.6.33+.
Пример команды:
ip route change default via 192.168.0.1 dev eth0 proto static initrwnd 10 initcwnd 10
Вместо 192.168.0.1 и eth0 укажите адрес своего роутера и сетевой интерфейс, посмотреть их можно командой «ip route show». Можно задать различные значения для различных маршрутов (подсетей).
В Windows также это можно настроить. Гуглится по «Increasing the TCP Initial Congestion Window on Windows 2008 Server R2».Nickmob
22.07.2016 08:24В большинстве современных систем и так стоит initcwd 10. Если бездумно увеличивать дальше, могут возникнуть проблемы (иначе везде уже стояло бы большее значение). Здесь речь идёт о том, как согласовать TLS и TCP для сокращения TTFB.
rrpv
22.07.2016 10:54+2Я понимаю, о чем идет речь в данной статье.
Для согласования TLS и TCP выходит достаточным поставить значение initcwd 12. Не думаю, что увеличение на два пакета приведет к проблемам, хотя я этого и не проверял.
bolk
Почему не сложить все патчи в корень папки с nginx и потом patch -p1 < *.patch?
Nickmob
Если сработает — пожалуйста, можно так.
bolk
Почему оно должно не сработать? Это ж не магия. Утилита с понятными ключами, исполняющаяся в понятном окружении.