Оптимизация Nginx: параметры sendfile, tcp_nodelay и tcp_nopush

Большинство статей про оптимизацию производительности Nginx рекомендуют использовать параметры sendfile, tcp_nodelay и tcp_nopush в конфигурационном файле nginx.conf. Но, к сожалению, они не говорят ни о том как эти параметры влияют на веб-сервер, ни о том как они работают.

tcp_nodelay

Как мы можем заставить сокет отправлять данные в его буфер? Решение лежит в использовании опции TCP_NODELAY в стеке TCP. Активация TCP_NODELAY заставляет сокет отправлять данные в буфер вне зависимости от размера пакета. Опция tcp_nodelay в настройках Nginx добавляет опцию TCP_NODELAY при открытии нового сокета.

Чтобы избежать перегруженности сети стек TCP реализует механизм который ожидает данные в течение 0,2 секунды вследствие чего слишком маленький пакет не будет отправлен. Этот механизм обеспечивается алгоритмом Нейгла и 200 миллисекунд - это значение в UNIX реализации.

Чтобы понять назначение алгоритма Нейгла вам необходимо помнить что интернет используется не только для отправки веб-страниц и больших файлов. Представьте себя снова в 90х использующего telnet для подключения к удаленной машине через модемное соединение со скоростью 14400 бит. Когда вы нажимаете ctrl+c, вы отправляете на сервер сообщение в один байт. Для отправки этого сообщения вам необходимо добавить IP-заголовки (20 байт для IPv4 и 40 байт для IPv6), а также TCP-заголовки (20 байт). То есть после нажатия на ctrl+c вам в действительности необходимо отправить 41 байт данных через сеть. Алгоритм Нейгла позволяет дождаться нажатия каких-либо еще клавиш до отправки данных в сеть.

Это здорово, но алгоритм Нейгла более не соответствует современному интернету. Более того, это контр продуктивно когда вам необходимо отправлять данные по сети. Шансы, что ваш файл заполнит полностью определенное количество пакетов, стремятся к нулю, что значит, что Нейгл создаст задержку в 200 миллисекунд на стороне клиента для каждого файла, который он скачивает.

Опция TCP_NODELAY позволяет не использовать Нейгла и отправлять данные так быстро, как они поступают.

Nginx использует TCP_NODELAY на HTTP-соединениях с keepalive. keepalive-соединения - это сокеты, которые остаются открытыми в течение некоторого времени после отправки данных. keepalive позволяет отправить больше данных без инициализации нового соединения и повтора тройного рукопожатия TCP для каждого HTTP-запроса. Это сохраняет как время, так и сокеты, так как они не переключаются в FIN_WAIT после каждой передачи данных. Connection: Keep-alive используется по умолчанию для HTTP 1.0 и HTTP 1.1.

При загрузке полной веб-страницы, TCP_NODELAY может сэкономить до 200 миллисекунд на каждый HTTP-запрос, что очень даже не плохо. Когда дело касается онлайновых игр и высокочастотного трейдинга, то снижение задержек критично даже ценой относительного снижения пропускной способности сети.

tcp_nopush

В Nginx опция tcp_nopush работает как противоположность tcp_nodelay. Вместо оптимизации задержек, она оптимизирует количество данных переданных за раз.

Чтобы сохранить логику, Nginx при включенном tcp_nopush активирует опцию TCP_CORK в TCP-стеке Линукс, так как TCP_NOPUSH существует только в FreeBSD.

Хорошо названная опция TCP_CORK (cork - пробка) блокирует данные до тех пор, покак размер пакета не достигнет MSS, который равен MTU за вычетом 40 или 60 байтов IP-заголовка.

TCP_CORK

Все довольно хорошо объяснено в коде ядра Линукс:

/* Return false, if packet can be sent now without violation Nagle's rules:
* 1. It is full sized. 
* 2. Or it contains FIN. (already checked by caller)
* 3. Or TCP_CORK is not set, and TCP_NODELAY is set.
* 4. Or TCP_CORK is not set, and all sent packets are ACKed.
*  With Minshall's modification: all sent small packets are ACKed.
*/

static inline bool tcp_nagle_check(const struct tcp_sock *tp, const struct sk_buff *skb, unsigned int mss_now, int nonagle)

  return skb-\>len \< mss_now && ((nonagle & TCP_NAGLE_CORK) (!nonagle && tp-\>packets_out && tcp_minshall_check(tp))); }

Если вы хотите отправить полупустой (или на половину полный) пакет то вам точно нужно убрать TCP_CORK.

Страница man TCP(7) объясняет, что TCP_NODELAY и TCP_CORK взаимно исключающие, но могут быть объединены начиная с Linux 2.5.9.

В настройках Nginx tcp_nopush должен быть активирован совместно с sendfile, который является действительно интересной штукой.

sendfile

Изначально Nginx прославился отличной работой по отправке статических файлов. Это связано с совместной работой sendfile, tcp_nodelay и tcp_nopush в nginx.conf. Sendfile, опция Nginx, включает использование sendfile(2) для всего связанного с... отправкой файлов.

Sendfile(2) позволяет передавать данные из одного файлового дескриптора в другой прямо внутри пространства ядра. Sendfile позволяет сохранить много ресурсов:

  • Sendfile - это системный вызов, что значит, что выполнение происходит внутри пространства ядра, следовательно без затрат на переключение контекста.
  • Sendfile заменяет комбинацию чтения и записи.
  • Наконец, sendfile позволяет осуществлять нулевое копирование, то есть записи напрямую в буфер ядра с блочного устройства посредством DMA.

К сожалению, sendfile требует файлового дескриптора с поддержкой mmap и подобных, что исключает UNIX-сокеты, например, как способ отправить данные на локальный бекенд Rails без сетевых задержек.

В зависимости от ваших задач sendfile может быть либо полностью бесполезным, либо абсолютно необходимым.

Если вы раздаете локально расположенные статические файлы, то sendfile крайне необходим для ускорения вашего веб-сервера. Но если вы используете nginx как обратный прокси для выдачи страниц с сервера приложений, то sendfile можно выключить. До тех пор, пока вы не начнете использовать микрокеширование на tmpfs.

Давайте объединим все вместе

Все становится намного интереснее когда вы объединяете senfile, tcp_nodelay и tcp_nopush. Удивительно, как кто-то может использовать одновременно две взаимоисключающие опции? Ответ лежит глубоко в треде 2005 года в почтовой рассылке Nginx.

tcp_nopush, объединенный с sendfile, гарантирует, что пакеты будут наполнятся до конца перед отправкой клиенту. Это значительно снижает оверхед и ускоряет отправку файлов. Затем, когда достигается последний пакет, Nginx убирает tcp_nopush. И теперь tcp_nodelay заставляет сокет отправить данные экономя 200 миллисекунд при отправке каждого файла.

Очень хорошо, не так ли?

Источник: https://t37.net/nginx-optimization-understanding-sendfile-tcp_nodelay-and-tcp_nopush.html

Сие опубликовал в день года в разделах nginx, tcp, translation

Наверх