Каждый, кто хоть раз разбирался с проблемами latency на продакшн-серверах в три часа ночи, знает это чувство: трафик льётся, мониторинг кричит, а стандартные настройки ядра словно созданы для домашнего ноутбука, а не для машины, обрабатывающей десятки тысяч соединений в секунду. После восьми лет работы с высоконагруженными системами в крупных дата-центрах я накопил достаточно шишек и инсайтов, чтобы поделиться реально работающими подходами к тюнингу sysctl.
Почему дефолтные значения ядра вас подводят
Честно говоря, когда я впервые столкнулся с серьёзной нагрузкой, то был уверен, что проблема в приложении. Профилировал код, оптимизировал запросы к базе, менял веб-серверы. А потом случайно заглянул в dmesg и увидел сообщения о переполнении буферов. Ядро Linux по умолчанию настроено консервативно, и это разумно для универсальности. Но когда на сервер приходит 50 000 одновременных соединений, консервативность превращается в узкое горлышко.
Проверьте текущие значения простой командой:
sysctl -a | grep -E "(tcp_|net.core)"
Скорее всего, вы увидите буферы в несколько сотен килобайт и параметры keepalive, рассчитанные на спокойную офисную сеть. Для серьёзной нагрузки этого катастрофически мало.
TCP Keepalive: тонкая грань между надёжностью и ресурсами
Механизм keepalive часто недооценивают, а зря. Когда соединение повисает из-за сетевого сбоя или проблем на стороне клиента, сервер продолжает держать сокет открытым, расходуя память и файловые дескрипторы. На загруженных системах это может съедать гигабайты RAM.
Три ключевых параметра определяют поведение keepalive:
# Время до первой проверки (по умолчанию 7200 секунд - 2 часа!)
sysctl -w net.ipv4.tcp_keepalive_time=600
# Интервал между проверками
sysctl -w net.ipv4.tcp_keepalive_intvl=60
# Количество проверок до закрытия
sysctl -w net.ipv4.tcp_keepalive_probes=5
Два часа ожидания до первой проверки в дефолте - это наследие эпохи, когда серверы обслуживали десятки клиентов. На практике я выставляю tcp_keepalive_time в диапазоне 300-900 секунд в зависимости от специфики приложения. Для API-серверов с короткими запросами подходит агрессивная настройка в 300 секунд. Для систем с долгоживущими соединениями, например стриминговых сервисов, лучше оставить 900-1200 секунд.
После внедрения оптимизированных keepalive на кластере из 40 серверов мы наблюдали снижение потребления памяти на 15-20% за счёт своевременного освобождения зависших соединений. Мелочь? Когда речь идёт о терабайтах RAM суммарно, экономия получается весомой.
Буферы: где заканчивается теория и начинается практика
Сетевые буферы в Linux работают на нескольких уровнях. Есть буферы ядра (net.core), есть буферы протокола TCP, и разобраться в их взаимодействии поначалу непросто. Ключевые параметры, которые я настраиваю в первую очередь:
# Максимальный размер буфера приёма для всех соединений
sysctl -w net.core.rmem_max=16777216
# Максимальный размер буфера отправки
sysctl -w net.core.wmem_max=16777216
# Буферы TCP: min, default, max
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
Значение 16 МБ для максимальных буферов может показаться чрезмерным, но на 10-гигабитных интерфейсах это вполне оправдано. Формула расчёта оптимального буфера основана на bandwidth-delay product: умножаем пропускную способность канала на RTT. Для канала 10 Гбит/с и RTT в 10 мс получаем примерно 12.5 МБ.
Отдельного внимания заслуживает очередь входящих соединений:
# Очередь для сокетов в состоянии SYN_RECV
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# Общая очередь входящих соединений
sysctl -w net.core.somaxconn=65535
На одном из проектов мы долго искали причину периодических отказов при пиках трафика. Приложение справлялось, сеть работала, но часть клиентов получала connection refused. Виновником оказался somaxconn со стандартным значением 128. После увеличения до 65535 проблема исчезла полностью.
Congestion Control: выбор алгоритма управления перегрузкой
Здесь начинается самое интересное. Алгоритм управления перегрузкой определяет, как TCP реагирует на потерю пакетов и изменение условий сети. Дефолтный cubic хорош для большинства случаев, но не всегда оптимален.
Проверить доступные алгоритмы:
sysctl net.ipv4.tcp_available_congestion_control
Для современных дата-центров я рекомендую рассмотреть BBR (Bottleneck Bandwidth and Round-trip propagation time), разработанный инженерами Google:
# Загрузка модуля
modprobe tcp_bbr
# Активация BBR
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
BBR работает принципиально иначе, чем loss-based алгоритмы. Вместо реакции на потерю пакетов он строит модель канала и пытается поддерживать оптимальную скорость передачи. На практике это даёт значительный прирост производительности в сетях с потерями.
Бенчмарки с реальных серверов показывают следующую картину: при переходе с cubic на BBR на транконтинентальных соединениях (RTT около 150 мс) пропускная способность выросла на 40-60%. На локальных соединениях внутри дата-центра разница минимальна, порядка 2-5%. Поэтому для внутренней коммуникации между серверами в одной стойке BBR избыточен, а вот для edge-серверов, раздающих контент пользователям, он незаменим.
Дополнительные параметры для тонкой настройки
За годы экспериментов я сформировал набор параметров, которые применяю практически везде:
- net.ipv4.tcp_fin_timeout=15 сокращает время нахождения сокета в состоянии FIN_WAIT_2
- net.ipv4.tcp_tw_reuse=1 позволяет повторно использовать сокеты в TIME_WAIT
- net.ipv4.tcp_slow_start_after_idle=0 отключает сброс окна после простоя
- net.ipv4.tcp_mtu_probing=1 включает автоматическое определение MTU
- net.ipv4.tcp_fastopen=3 активирует TCP Fast Open для клиента и сервера
- net.ipv4.ip_local_port_range="1024 65535" расширяет диапазон локальных портов
TCP Fast Open особенно интересен для веб-серверов. Он позволяет передавать данные уже в SYN-пакете, экономя один RTT на установление соединения. При среднем RTT в 50 мс и миллионах запросов в сутки суммарная экономия времени получается колоссальной.
Как правильно применять изменения
Никогда, слышите, никогда не применяйте все изменения разом на продакшене. Я видел ситуации, когда неаккуратный тюнинг приводил к деградации вместо улучшения. Правильный подход выглядит так: вносите изменения постепенно, измеряйте метрики после каждого шага, держите возможность отката.
Для постоянного применения настроек создайте файл:
cat > /etc/sysctl.d/99-network-tuning.conf << EOF
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
EOF
sysctl -p /etc/sysctl.d/99-network-tuning.conf
Обязательно мониторьте состояние сетевого стека после изменений. Команда ss -s покажет статистику по сокетам, а netstat -s выведет детальные счётчики протоколов. Обращайте внимание на retransmits, overflows и dropped connections.
Заключение: системный подход вместо магических чисел
Тюнинг sysctl - это не про копирование чужих конфигов и надежду на чудо. Каждая система уникальна: разные паттерны нагрузки, разные характеристики сети, разные требования к latency и throughput. Параметры, которые творят чудеса на стриминговом сервере, могут навредить базе данных с короткими транзакциями.
Начинайте с профилирования текущего состояния. Поймите, где именно узкое место: в буферах, в очередях, в алгоритме congestion control. Только после этого вносите точечные изменения и измеряйте результат. Со временем вы выработаете интуицию, позволяющую быстро определять оптимальные настройки для конкретного случая. А пока - экспериментируйте, измеряйте, учитесь на собственном опыте. Сетевой стек Linux невероятно гибок, и при правильном подходе способен выжать максимум из любого железа.