Вопрос производительности работы сервера Angie и различных оптимизаций не раз поднимался в других статьях цикла. Поэтому здесь мы соберём основные приёмы и подходы к настройке Angie для высоких нагрузок, а за подробностями по отдельным вопросам будем направлять читателя в специализированные статьи.
Навигация по циклу
Настройка location в Angie. Разделение динамических и статических запросов.
Перенаправления в Angie: return, rewrite и примеры их применения.
Сжатие текста в Angie: статика, динамика, производительность.
Оптимизация Angie для высоких нагрузок.
Видеоверсия
Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.
Общий подход
Настройка сервера для работы с высокими нагрузками требует индивидуального подхода. Характер нагрузки, требования по оптимизации, особенности железа и приложения — всё это влияет на решения. Например, можно получить противоречивые рекомендации для сокращения времени ответа (latency) и пропускной способности системы (throughput). Цель данной статьи — провести обзор возможностей по настройке системы и сервера для различных применений. Исходя из этого, не стоит бездумно копировать настройки без понимания контекста и проверки на тестовом стенде.
В качестве операционной системы будем предполагать стандартный на сегодня вариант — Linux. Начнём с системных настроек.
Настройка системы Linux
Со стороны операционной системы при работе с высокими нагрузками нас интересует работа сетевого стека. Настройки, которые мы будем рассматривать, относятся к ядру системы, которое отвечает за обработку трафика вплоть до четвёртого уровня модели OSI. Это справедливо для работы системы по умолчанию, без перевода сетевого стека в пользовательское пространство (например, с помощью DPDK).
Посмотреть текущие значения настроек можно с помощью команды:
sysctl -a
Вносить изменения в настройки можно интерактивно (для проверки) или через конфигурационный файл (перманентно). Рекомендуемый путь — использовать отдельный файл для пользовательских настроек в директории /etc/sysctl.d/:
nano /etc/sysctl.d/99-custom.conf
Применить настройки из конфигурационных файлов можно с помощью команды sysctl:
sysctl --system
Временное изменение настроек выполняется также через sysctl:
sysctl -w net.ipv4.tcp_slow_start_after_idle=0
Теперь переходим к настройкам, которые могут быть полезны:
net.ipv4.tcp_slow_start_after_idle = 0— сохранение состояния slow start после простоя;net.ipv4.tcp_notsent_lowat = 65536— размер буфера в TCP для отправки;net.ipv4.ip_local_port_range = 1024 65535— диапазон локальных портов (ограничивает количество исходящих подключений к апстримам);net.ipv4.tcp_tw_reuse = 1— переиспользование сокетов в состоянии time wait;net.netfilter.nf_conntrack_max = 1048576— размер таблицы conntrack;net.core.netdev_max_backlog= 10000— очередь фреймов для обработки на сетевом интерфейсе;net.core.somaxconn = 16000— максимальный размер Accept‑очереди коннектов на сокет;net.ipv4.tcp_abort_on_overflow = 1— быстрый сброс соединений при переполнении Accept‑очереди;net.ipv4.tcp_max_syn_backlog = 1024— параметр, влияющий на максимальное число соединений в SYN‑очереди;net.core.rmem_max = 8388608— максимальный размер буфера приёма данных для TCP;net.core.wmem_max = 8388608— то же, но для отправки;net.ipv4.tcp_rmem = 4096 131072 8388608— настройки автотюнинга памяти для получения данных на TCP‑сокет (минимум, по умолчанию, максимум);net.ipv4.tcp_wmem = 4096 16384 8388608— то же, но для отправки.
Подробнее описание параметров можно найти в документации, а также в статье. Также можно погрузиться в тему различных алгоритмов контроля перегрузки (congestion control) в TCP, так как в современных ядрах доступны различные варианты. Но главная тема статьи это настройка сервера Angie, поэтому перейдём к его конфигурации.
Общие настройки
Основная работа сервера выполняется рабочими процессами (worker process), поэтому важно настроить их количество. По умолчанию количество рабочих процессов (worker_processes) равно количеству процессоров в системе:
worker_processes auto;
Для большинства применений эта настройка оптимальна. Однако, для больших систем с NUMA‑архитектурой может быть выгодно использовать для сервера только одну NUMA‑ноду, что предполагает ограничение количества процессов. Также существует возможность привязки процессов к отдельным процессорам, автоматическое распределение выглядит так:
worker_cpu_affinity auto;
Привязка к процессорам позволит сократить накладные расходы по переключению контекста. Однако, привязка к процессорам может повредить работе сервера при наличии в системе других приложений, конкурирующих за процессор.
Следующая настройка ограничивает количество открытых файловых дескрипторов (worker_rlimit_nofile) для рабочего процесса:
worker_rlimit_nofile 30000;
Эта же настройка также ограничивает сверху количество подключений для рабочего процесса. Сам лимит на количество подключений рабочего процесса задаётся директивой worker_connections:
events { worker_connections 8192; }
Напомню, что при использовании протоколов HTTP/2 и HTTP/3 в рамках одного подключения обрабатывается несколько параллельных запросов, количество которых регулируется директивами http2_max_concurrent_streams и http3_max_concurrent_streams соответственно.
Также при поддержке JIT‑компиляции в библиотеке PCRE можно включить эту возможность:
pcre_jit on;
Это были общие настройки сервера, переходим к сетевым параметрам конфигурации.
Сетевые параметры
Сервер позволяет настраивать различные опции TCP‑сокетов для сокращения времени ответа или оптимизации использования пропускной способности. Например, директива tcp_nodelay отправляет данные как можно раньше (по умолчанию включена):
http { tcp_nodelay on; }
При использовании механизма sendfile полезно включить опцию tcp_nopush, которая позволит отправлять отправлять файл полными пакетами:
http { tcp_nopush on; }
Логично здесь упомянуть директиву sendfile, которая разрешает использовать одноимённый механизм, значительно снижая нагрузку при отправке файлов.
http { sendfile on; }
Однако механизм sendfile будет работать только при использовании открытого HTTP, либо при активации Kernel TLS (модуль ядра, реализующий TLS).
В общем случае (без использования sendfile) большое значение для отдачи статических файлов имеет директива output_buffers, которая задаёт количество и размеры буферов при чтении файлов с диска. По количеству будет достаточно двух буферов, а размер стоит увеличить, чтобы получать данные с диска большими порциями.
http { output_buffers 2 128k; }
Следующая оптимизация: использование эффективного метода распределения соединений по рабочим процессам — reuseport, включается как опция сокета в директиве listen, например:
server { listen 80 default_server reuseport; }
Параметр reuseport в конфигурации сервера можно указывать только один раз для каждого сокета. Также в директиве listen существует множество опций, которые меняют тонкие настройки сокетов. Например, стоит обратить внимание на следующие опции:
fastopen=32— разрешение использования TCP Fast Open, число указывает на максимальное количество соединений, которые не завершили рукопожатие, обязательно проверить на безопасность для приложения;backlog=1024— длина очереди ожидающих приёма (accept) соединений, поможет пережить всплески новых подключений, работа этой настройки тесно связана с параметрами ядраnet.core.somaxconn, net.ipv4.tcp_max_syn_backlogиnet.ipv4.tcp_abort_on_overflow, подробнее можно ознакомиться в здесь и здесь;deferred— активация параметра TCP_DEFER_ACCEPT для сокета на Linux, который откладывает (максимум до 1 секунды) обработку подключения до получения данных от клиента, что снижает количество переключений контекста при ожидании данных в буфере сокета;rcvbuf=128k, sndbuf=128k— настройка буферов сокета для получения и отправки данных;so_keepalive=15:5:3— включение параметра SO_KEEPALIVE (TCP keep‑alive), не путать с механизмом HTTP keep‑alive, также указаны необязательные параметры (они перебивают системные настройки keep‑alive):keepidle:keepintvl:keepcnt(период бездействия, интервал отправки проб в секундах и их количество).
Отдельно для протокола HTTP/3 в Angie есть механизм маршрутизации пакетов QUIC при помощи eBPF (требует версию Linux 5.7 и выше). Подробнее технология описана в статье разработчиков. Кроме того, для QUIC можно активировать оптимизацию отправки UDP‑трафика: quic_gso (GSO — generic segmentation offload). Это позволит снизить нагрузку на процессор. В результате конфигурация выглядит так:
events {} quic_bpf on; http { quic_gso on; ... }
Подробнее про особенности настройки и использования протоколов HTTP/2 и HTTP/3 можно узнать из ранней статьи цикла.
При работе под нагрузкой сервер как правило поддерживает большое количество подключений, часть из которых может долго висеть в ожидании окончательного завершения, ускорить сброс таких соединений можно директивой reset_timedout_connection:
http { reset_timedout_connection on; }
Важнейшая оптимизация для работы HTTP/HTTPS это keep‑alive соединения. В этой части мы говорим про подключения клиентов к серверу. Для подключения к проксируемым серверам использование keep‑alive также полезно и будет рассмотрено ниже. Используя эту опцию мы значительно сокращаем количество TCP и TLS‑рукопожатий и эффективнее используем пропускную способность канала. Keep‑alive соединение будет поддерживаться до истечения таймаута (keepalive_timeout) или исчерпания лимита запросов (keepalive_requests). Пример настройки:
http { keepalive_timeout 120; keepalive_requests 2000; }
Также стоит обратить внимание на параметры таймаутов для работы с клиентами. Пределы для получения запроса (client_body_timeout, client_header_timeout) и отправки (send_timeout) регулируются отдельно. По умолчанию их значения выставлены в 60 секунд, что может быть избыточно в современных условиях. Конечно, при выборе значений стоит учитывать варианты медленных клиентов.
http { send_timeout 10; client_body_timeout 10; client_header_timeout 10; }
Переходим к настройкам HTTPS.
Настройка TLS
Нагрузку при работе с TLS можно разделить на две части: рукопожатие (асимметричная фаза) и передачу основного трафика (симметричная фаза).
При оптимизации настроек TLS стоит начать с кэширования сессий. Для версий TLS 1.2 и TLS 1.3 механизмы кэширования различаются: ssl_session_cache (1.2) и ssl_session_tickets (1.3). Также стоит поставить достаточно большое время жизни сессии:
http { ssl_session_cache shared:SSL:10m; ssl_session_tickets on; ssl_session_timeout 28h; }
Использование зоны разделяемой памяти (ssl_session_cache) можно контролировать в Angie Console Light или через API.
На фазе рукопожатия также можно применять сертификаты различного типа: RSA и ECDSA. Влияние типа сертификата существует, но зависит от множества факторов: версии TLS, варианта библиотеки и так далее. Подробнее результаты тестирования можно посмотреть в ранее опубликованной статье цикла.
Для симметричной фазы передачи данных важен выбор алгоритма шифрования. Однако в реальных условиях большинство клиентов будут использовать TLS 1.3, где сильно ограничен набор допустимых шифров. Опять же для симметричной фазы часто будет использоваться AES, который даёт максимальную пропускную способность. Сокращение списка доступных шифров скорее всего будет конфликтовать с диапазоном поддерживаемых клиентов, поэтому мало применимо. Сравнение различных вариантов симметричного шифрования также можно найти в указанной ранее статье.
Также на производительность TLS будет влиять размер буфера (директива ssl_buffer_size). По умолчанию директива имеет максимально допустимое значение (16 КБ), что соответствует минимальным накладным расходам при передаче больших массивов трафика. Снижать это значение стоит только для достижения минимального времени получения и расшифровки первой части документа (например, для начала парсинга HTML), поэтому в большинстве конфигураций оптимально использовать максимальный размер буфера:
ssl_buffer_size 16k;
Ранее мы затрагивали вопрос использования механизма sendfile для оптимизации отправки файлов. При использовании HTTPS использование sendfile требует работы TLS на уровне ядра. Если Kernel TLS поддерживается вашей системой, вы можете реализовать преимущества sendfile с HTTPS:
http { ssl_conf_command Options KTLS; }
Однако эффективно использовать sendfile с HTTPS возможно только с HTTP/1.1. Совмещение sendfile и kTLS с HTTP/2 приведёт к падению производительности.
Сервер Angie имеет множество вариантов оптимизации работы с файлами, которые заслуживают внимания.
Работа с файлами
В процессе работы сервер Angie активно взаимодействует с файлами различного назначения: отправка статических файлов, запись логов, временные файлы буферизации, файлы сертификатов и ключей, кэш ответов на диске.
Первое, что можно использовать для сокращения системных вызовов открытия файлов, это кэш открытых файлов (open_file_cache):
http { open_file_cache max=10000 inactive=10s; open_file_cache_valid 30s; open_file_cache_errors on; open_file_cache_min_uses 2; }
В этой конфигурации объявлен кэш на 10 000 файловых дескрипторов, время хранения 30 секунд. Файловый дескриптор попадает в кэш после двух использований. Также будут кэшироваться ошибки поиска файлов, что может приводить ошибкам 404 даже при наличии файла на диске (если отсутствие файла было закэшировано). Также проблемы могут возникать, если файлы перезаписываются не атомарно. В таком случае сервер будет отдавать битые файлы.
Если имена лог‑файлов задаются через переменные, то можно активировать кэш для них:
http { open_log_file_cache max=100 inactive=60s min_uses=2; }
Аналогичный кэш есть для TLS‑сертификатов, имена которых заданы через переменные (ssl_certificate_cache):
http { ssl_certificate_cache max=1000 inactive=20s valid=1m; }
Работу с локальным лог‑файлами также можно оптимизировать за счёт использования буферизации записи и сжатия:
access_log /var/log/angie/access.log main_ext buffer=16k gzip=3 flush=10s;
В этом примере используется буферизация по 16 КБ, при этом лог записывается в сжатом виде со степенью компрессии 3. Такой вариант позволяет сильно снизить нагрузку на дисковую подсистему, создав дополнительную нагрузку на процессор. Если сервер испытывает нехватку CPU‑ресурсов, то можно использовать буферизацию без сжатия. Другим методом снижение нагрузки на диск будет отправка логов по сети через syslog.
Angie часто проксирует запросы на другие серверы, поэтому обсудим оптимизации этого взаимодействия.
Работа с проксируемыми серверами
В этом разделе будем подразумевать работу проксирования по протоколу HTTP (модуль Proxy), для других протоколов обычно есть аналогичные директивы и настройки.
Первое, что стоит настроить: таймауты для работы с бэкендами. Конкретные значения будут зависеть от особенностей приложения, но более короткие настройки позволят быстрее выявлять нерабочий сервер и перенаправлять запрос на другой. Пример конфигурации:
location / { proxy_connect_timeout 5; proxy_send_timeout 10; proxy_read_timeout 10; }
При настройки этих таймаутов для отправки запроса (proxy_send_timeout) и получения ответа (proxy_read_timeout) стоит изучить реакцию ваших серверов. Возможно, разрыв соединения со стороны Angie не приведёт к завершению работы со стороны бэкенда.
Следующий аспект работы с проксируемыми серверами: буферизация ответа (включена по умолчанию, директива proxy_buffering). Этот механизм позволяет максимально быстро освободить сервер, не ожидая отправки ответа клиенту. Для буфера в оперативной памяти выделяется лимит (proxy_buffers), после превышения которого ответ будет записан на диск и отдаваться клиенту из временного файла. Чтобы избежать частой буферизации на диск, стоит увеличить размер буфера для попадания в него большинства ответов. Пример конфигурации:
location / { proxy_temp_file_write_size 64k; proxy_buffer_size 4k; proxy_buffers 64 4k; proxy_busy_buffers_size 32k; }
Общий размер буфера здесь 64×4 КБ, начальный буфер для заголовка (proxy_buffer_size) 4 КБ, включена запись во временный файл (proxy_temp_file_write_size) блоками по 64 КБ.
Работа с бэкендами обычно осуществляется по сети, поэтому подключения как и в случае с клиентскими соединениями будут работать эффективнее с механизмом keep‑alive. Рассмотрим следующий пример:
upstream http_backend { server 192.168.0.100:8080; keepalive 16; keepalive_requests 1000; keepalive_timeout 60s; } server { location /http/ { proxy_pass http://http_backend; proxy_http_version 1.1; proxy_set_header Connection ""; } }
Здесь используется протокол HTTP/1.1 и блок upstream для настройки постоянных подключений к серверу (директива keepalive). При включении постоянных соединений к серверам стоит удостовериться, что они могут поддерживать достаточное количество одновременных подключений. В указанной выше конфигурации количество постоянных подключений от каждого рабочего процесса 16, общее количество получим, умножив это число на количество процессов. Также возможна ситуация, когда один рабочий процесс Angie займёт все возможные подключения к бэкенду и другие процессы не смогут к нему подключиться.
Для снижения нагрузки на проксируемые сервера можно применить серверное кэширование, о нём пойдёт речь далее.
Кэширование
При работе проксирующего сервера можно выделить два вида кэширования: серверное и клиентское.
Начнём с серверного кэширования. Angie умеет сохранять ответ сервера в файл на диске и отдавать его вместо отправки запроса на сервер. Такой кэш обеспечивает резкое снижение нагрузки на сервер, но принцип его работы основан на времени жизни элемента (TTL) и не имеет других механизмов инвалидации. Подробнее серверное кэширование разобрано в одной из статей цикла.
Приведём здесь следующий пример конфигурации:
http { proxy_cache_valid 1m; proxy_cache_key $scheme$host$request_uri; proxy_cache_path /cache levels=1:2 keys_zone=one:10m:file=/etc/angie/cache.state inactive=48h max_size=800m; server { location / { proxy_cache one; proxy_cache_valid 200 1h; proxy_cache_lock on; proxy_cache_min_uses 2; proxy_ignore_headers "Cache-Control" "Expires"; proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_504; proxy_cache_background_update on; } }
В этой конфигурации создаётся одна зона кэширования (proxy_cache_path), используется сохранение статуса в файл (file=/etc/angie/cache.state), а также разрешено отдавать устаревший элемента кэша в некоторых случаях (proxy_cache_use_stale). Время кэширования запросов установлено в одну минуту, хотя даже короткое время жизни (микрокэширование, 1–5 секунд) может быть полезно для снижения нагрузки без серьёзного влияния на логику работы сайта. Блокировка кэша (proxy_cache_lock) обеспечивает отправку только одного запроса на обновление элемента кэша, но может приводить к дополнительной задержке в 500 мс для запросов, ожидающих появления ответа в кэше, дополнительно можно выставить максимальное время такой блокировки через proxy_cache_lock_timeout. Точечное удаление элементов кэша доступно с помощью стороннего модуля Cache Purge (подробнее в статье по серверному кэшированию).
Второй тип: клиентское кэширование также может снижать нагрузку с сервера за счет сокращения объёма трафика и количества запросов. За хранение клиентского кэша отвечает браузер, а управление со стороны сервера заключается в установке корректных заголовков для ответов. Эта тема также разбиралась подробно в статье при клиентское кэширование. Часто достаточно установки одного заголовка для разрешения «вечного кэша» статических файлов. Например, так:
location /static/ { add_header Cache-Control "max-age=31536000, public, no-transform, immutable"; }
Получив такой заголовок, клиент сохранит ответ в кэши и не будет отправлять повторных запросов. Для сброса такого кэша необходимо изменить URI запроса.
При передаче текстовых ресурсов практически всегда используется компрессия, давайте посмотрим на этот вопрос подробнее.
Компрессия текстовых ресурсов
При работе типичного веб‑приложения большинство запросов имеют текстовые ответы. Текст прекрасно поддаётся сжатию, за счет чего можно сократить трафик примерно в 3 раза или более.
Однако, сжатие ресурсов в Angie доступно в виде динамического и статического режимов. И если статическое сжатие однозначно позволяет экономить ресурсы, то динамическое сжатие требует затрат ресурсов CPU.
Стандартно Angie поддерживает только сжатие формата Gzip, с помощью сторонних модулей — Brotli и Zstandard. Начнём с примера конфигурации для Gzip‑сжатия:
http { gzip on; gzip_static on; gzip_types text/plain text/css text/xml application/javascript application/json image/svg+xml application/font-ttf; gzip_comp_level 3; gzip_proxied any; gzip_min_length 1000; gzip_vary on; }
Здесь используется оба режима сжатия: динамический (gzip) и статический (gzip_static). Также указаны MIME‑типы контента для сжатия (gzip_types), минимальный размер ответа для сжатия (gzip_min_length) и включён заголовок Vary для сжатых ответов (gzip_vary).
Важнейшая настройка для производительности — степень компрессии (gzip_comp_level). Для большинства случаев оптимальным компромиссом между размером ответа и нагрузкой на процессор будут низкие настройки (от 1 до 3). Устанавливать высокие значения (7–9) не стоит — нагрузка возрастёт значительно, а компрессия повысится лишь на несколько процентов.
При использовании стороннего модуля Brotli конфигурация аналогична:
load_module modules/ngx_http_brotli_static_module.so; load_module modules/ngx_http_brotli_filter_module.so; http { brotli_static on; brotli on; brotli_comp_level 3; brotli_types text/plain text/xml text/css application/javascript application/json image/x-icon image/svg+xml; }
Для статического режима можно применить еще одну оптимизацию: использовать директиву (gzip_static или brotli_static) только в тех локациях, которые имеют статически сжатые файлы, например так:
location /js/ { brotli_static on; gzip_static on; }
Подробнее тонкости настройки сжатия ресурсов разобраны в этой статье. Также отдельно исследованы популярные варианты сжатия текста с точки зрения результирующей производительности.
Итоги
В этой статье мы провели обзор различных аспектов оптимизации конфигурации Angie для высоких нагрузок. Таким образом, вы можете провести собственный аудит настроек сервера и найти новые возможности для улучшения производительности. Более подробный разбор отдельных директив и тонкостей доступен в виде специализированных статей, ссылки на которые указаны по ходу текста.
