Мониторинг очередей TCP и сокетов Linux. Listen queue, accept queue и переполнение backlog
При анализе сетевых проблем большинство инженеров в первую очередь смотрят на загрузку процессора, использование памяти или сетевой трафик. Однако бывает так, что сервер практически не нагружен, а пользователи всё равно получают задержки и ошибки подключения.
Одна из причин — переполненные очереди TCP-соединений. Они живут в ядре, до того как приложение вообще узнало о новом клиенте, поэтому в обычных дашбордах с CPU и памятью проблема не видна.
Разберём, что такое SYN queue, accept queue и backlog, какими командами посмотреть их состояние и какие метрики стоит завести, чтобы поймать переполнение до массовых ошибок.
Две очереди, а не одна
Когда клиент подключается к серверу, соединение не попадает в приложение мгновенно. Ядро проводит его через две разные очереди:
- Пришёл
SYN— ядро отвечаетSYN+ACKи кладёт полуоткрытое соединение в SYN queue (её же называют listen queue). - Пришёл финальный
ACK— соединение считается установленным и переезжает в accept queue. - Приложение вызывает
accept()и забирает соединение из accept queue — только теперь запрос начинают обрабатывать.
| SYN queue | Accept queue | |
|---|---|---|
| Что лежит | полуоткрытые соединения, handshake не завершён | установленные соединения, ждущие accept() |
| Размер задаёт | net.ipv4.tcp_max_syn_backlog | min(backlog приложения, net.core.somaxconn) |
| Растёт когда | шквал новых подключений, потери ACK, SYN-флуд | приложение медленно вызывает accept() |
| Где искать причину | сеть, всплеск трафика, атака | само приложение: воркеры, блокировки, долгие запросы |
| Счётчик переполнения | TcpExtTCPReqQFullDrop, SyncookiesSent | TcpExtListenOverflows |
Разделение важно практически: рост SYN queue и рост accept queue — это две разные аварии с разными виновниками. Дальше самое частое из них.
Что такое backlog и почему он меньше, чем вы указали
Открывая слушающий сокет, приложение передаёт в системный вызов listen() параметр backlog — сколько установленных соединений ядро подержит, пока приложение их не забрало.
Реальный лимит — минимум из этого значения и net.core.somaxconn:
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlognet.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 1024Это классическая ловушка: в конфиге nginx стоит backlog=8192, а ядро молча урезало очередь до somaxconn. До Linux 5.4 значение по умолчанию было 128 — на старых ядрах и в контейнерах с унаследованными настройками очередь заканчивается гораздо раньше, чем ожидает конфиг.
Поднять лимит и закрепить его между перезагрузками:
sudo sysctl -w net.core.somaxconn=8192
echo 'net.core.somaxconn = 8192' | sudo tee /etc/sysctl.d/99-somaxconn.confВ самом приложении backlog задаётся отдельно, и его тоже нужно поднять — иначе повышение somaxconn ничего не изменит:
server {
# backlog действует только при первом создании сокета,
# для применения нужен полный перезапуск nginx, не reload
listen 443 ssl backlog=8192;
}Аналоги в других сервисах: listen.backlog в php-fpm, --backlog у gunicorn и uvicorn, SOMAXCONN в Node.js передаётся вторым аргументом server.listen(port, host, backlog).
Как посмотреть очереди прямо сейчас
Главный инструмент — ss. Для слушающих сокетов колонки Recv-Q и Send-Q означают не то же самое, что для установленных соединений:
ss -ltnState Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 4096 0.0.0.0:443 0.0.0.0:*
LISTEN 127 511 0.0.0.0:80 0.0.0.0:*Для сокета в состоянии LISTEN:
Recv-Q— сколько соединений уже установлено и ждётaccept(), то есть текущая длина accept queue;Send-Q— максимальный размер этой очереди, уже с учётом урезания поsomaxconn.
В примере выше сокет на 443 порту здоров: очередь пуста при лимите 4096. А вот 80 порт — готовая авария: в очереди 127 соединений при лимите 511, приложение явно не успевает их забирать. Устойчиво ненулевой Recv-Q — это и есть тот сигнал, который стоит искать.
Чтобы сразу видеть, чей это сокет:
sudo ss -ltnpСодержимое SYN queue видно отдельно — это соединения в состоянии SYN-RECV:
ss -tn state syn-recv | wc -lСотни таких соединений с разных адресов при спокойном приложении — повод проверить трафик на всплеск или SYN-флуд.
Счётчики переполнений: сколько соединений уже потеряно
ss показывает мгновенный срез, а очередь может переполняться короткими всплесками между двумя запусками команды. Факт переполнения фиксируют счётчики ядра:
nstat -az TcpExtListenOverflows TcpExtListenDrops TcpExtTCPReqQFullDropTcpExtListenOverflows 1843 0.0
TcpExtListenDrops 1901 0.0
TcpExtTCPReqQFullDrop 0 0.0Как это читать:
ListenOverflows— accept queue была полна, соединение отброшено. Любое ненулевое значение означает, что клиенты уже получили отказ;ListenDrops— все отброшенные на слушающем сокете соединения, включая overflow. Разница между ним иListenOverflowsуказывает на другие причины отказа, например нехватку памяти;TCPReqQFullDrop— переполнилась SYN queue при выключенных syncookies. Если syncookies включены, вместо этого растётSyncookiesSent.
Те же значения можно взять из netstat, если nstat недоступен:
netstat -s | grep -iE 'listen|syn'Важная деталь: счётчики кумулятивные с момента загрузки. Само по себе большое число ни о чём не говорит — смотреть нужно на прирост. Проверить, идёт ли переполнение прямо сейчас, помогает nstat без -a, который печатает дельту с прошлого запуска:
nstat -n; sleep 10; nstat | grep -i listenЕсли за десять секунд ListenOverflows прибавил — проблема активна, а не осталась в прошлом.
Что вынести в мониторинг
Разовая диагностика хороша, когда вы уже знаете, что искать. Чтобы узнать о переполнении заранее, нужны постоянные метрики. Минимальный набор:
| Метрика | Откуда | На что смотреть |
|---|---|---|
node_netstat_TcpExt_ListenOverflows | node_exporter | любой прирост — уже потерянные соединения |
node_netstat_TcpExt_ListenDrops | node_exporter | прирост быстрее, чем у overflows |
Recv-Q слушающих сокетов | ss -ltn, свой экспортер | устойчиво выше нуля |
Соединения в SYN-RECV | ss, node_exporter | резкий рост при спокойном приложении |
node_netstat_Tcp_RetransSegs | node_exporter | рост вместе с очередями |
| Время ответа приложения | приложение, балансировщик | рост одновременно с Recv-Q |
Алерт на переполнение accept queue формулируется просто, потому что порог здесь честно нулевой — любое переполнение означает отказ живому клиенту:
- alert: TcpAcceptQueueOverflow
expr: rate(node_netstat_TcpExt_ListenOverflows[5m]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: 'Переполнение accept queue на {{ $labels.instance }}'
description: 'Ядро отбрасывает новые соединения: приложение не успевает вызывать accept() либо backlog меньше нагрузки'Настроить сам сбор метрик с нуля разбирали в статье про мониторинг серверов на Prometheus и Grafana.
Как интерпретировать сочетания метрик
Отдельная метрика редко даёт вывод — полезны именно комбинации.
Растёт Recv-Q слушающего сокета, растёт время ответа приложения, ListenOverflows прибавляет. Приложение не успевает принимать соединения. Смотреть надо не в сеть, а в воркеры: их количество, блокировки, долгие запросы к базе, залипшие внешние вызовы.
Растёт число соединений в SYN-RECV, Recv-Q при этом пустой. Handshake не доходит до конца. Это либо всплеск новых подключений, либо потери на пути к клиенту, либо SYN-флуд. Здесь помогут метрики retransmits и resets.
Очереди чистые, но клиенты видят таймауты. Проблема выше или ниже по стеку: балансировщик, лимиты воркеров веб-сервера, сам апстрим. Полезно свериться с метриками nginx и здоровьем бэкендов балансировщика.
Частые причины переполнения
Переполнение TCP-очередей далеко не всегда означает атаку. На практике чаще встречается прозаичное:
- backlog приложения оставлен по умолчанию — у многих фреймворков это 128 или 511;
somaxconnне поднят, и он молча урезает backlog из конфига;- nginx перезапускали через
reload, а изменениеbacklogтребует полного рестарта — сокет остался старым; - воркеров меньше, чем нужно под текущий трафик, либо они заняты долгими запросами;
- деплой или рестарт: старый процесс уже не принимает соединения, новый ещё не прогрелся, очередь копится в эти секунды;
- балансировщик открывает соединения быстрее, чем бэкенд успевает их забирать.
Отдельно стоит помнить про net.ipv4.tcp_abort_on_overflow. По умолчанию он выключен, и при переполнении ядро просто отбрасывает финальный ACK — клиент подождёт и повторит, получив задержку вместо ошибки. Включённый параметр отправляет RST сразу: клиент увидит явную ошибку соединения. Второй вариант удобнее для диагностики и быстрого фейловера, но пользователю он даёт ошибку там, где раньше была всего лишь задержка.
Что почитать дальше
- Утилита ss: современная альтернатива netstat
- Мониторинг TCP-соединений: retransmits, resets, SYN backlog
- Что такое load average в Linux и как его правильно интерпретировать
- Connection pooling: как работают пулы соединений в PostgreSQL и MySQL
Заключение
SYN queue, accept queue и backlog — показатели, которые редко попадают в стандартные дашборды, но первыми сигнализируют о начинающейся деградации. Сервер ещё не нагружен по CPU и памяти, а новые соединения уже отбрасываются.
Для быстрой диагностики достаточно двух команд: ss -ltn покажет текущую длину accept queue и её лимит, nstat -az TcpExtListenOverflows — сколько соединений уже потеряно. Для постоянного контроля хватит счётчиков ListenOverflows и ListenDrops из node_exporter с алертом на любой прирост.
А дальше вывод почти всегда сводится к одному из двух: растёт accept queue — разбираться нужно в приложении, растёт SYN queue — в сети и характере входящего трафика.
Настроить мониторинг за 30 секунд
Надежные оповещения о даунтаймах. Без ложных срабатываний