Мониторинг очередей TCP и сокетов Linux. Listen queue, accept queue и переполнение backlog

10 минут чтения
Средний рейтинг статьи — 4.6

При анализе сетевых проблем большинство инженеров в первую очередь смотрят на загрузку процессора, использование памяти или сетевой трафик. Однако бывает так, что сервер практически не нагружен, а пользователи всё равно получают задержки и ошибки подключения.

Одна из причин — переполненные очереди TCP-соединений. Они живут в ядре, до того как приложение вообще узнало о новом клиенте, поэтому в обычных дашбордах с CPU и памятью проблема не видна.

Разберём, что такое SYN queue, accept queue и backlog, какими командами посмотреть их состояние и какие метрики стоит завести, чтобы поймать переполнение до массовых ошибок.

Две очереди, а не одна

Когда клиент подключается к серверу, соединение не попадает в приложение мгновенно. Ядро проводит его через две разные очереди:

  1. Пришёл SYN — ядро отвечает SYN+ACK и кладёт полуоткрытое соединение в SYN queue (её же называют listen queue).
  2. Пришёл финальный ACK — соединение считается установленным и переезжает в accept queue.
  3. Приложение вызывает accept() и забирает соединение из accept queue — только теперь запрос начинают обрабатывать.
SYN queueAccept queue
Что лежитполуоткрытые соединения, handshake не завершёнустановленные соединения, ждущие accept()
Размер задаётnet.ipv4.tcp_max_syn_backlogmin(backlog приложения, net.core.somaxconn)
Растёт когдашквал новых подключений, потери ACK, SYN-флудприложение медленно вызывает accept()
Где искать причинусеть, всплеск трафика, атакасамо приложение: воркеры, блокировки, долгие запросы
Счётчик переполненияTcpExtTCPReqQFullDrop, SyncookiesSentTcpExtListenOverflows

Разделение важно практически: рост SYN queue и рост accept queue — это две разные аварии с разными виновниками. Дальше самое частое из них.

Что такое backlog и почему он меньше, чем вы указали

Открывая слушающий сокет, приложение передаёт в системный вызов listen() параметр backlog — сколько установленных соединений ядро подержит, пока приложение их не забрало.

Реальный лимит — минимум из этого значения и net.core.somaxconn:

sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog
net.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 -ltn
State   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 TcpExtTCPReqQFullDrop
TcpExtListenOverflows           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_ListenOverflowsnode_exporterлюбой прирост — уже потерянные соединения
node_netstat_TcpExt_ListenDropsnode_exporterприрост быстрее, чем у overflows
Recv-Q слушающих сокетовss -ltn, свой экспортерустойчиво выше нуля
Соединения в SYN-RECVss, node_exporterрезкий рост при спокойном приложении
node_netstat_Tcp_RetransSegsnode_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 сразу: клиент увидит явную ошибку соединения. Второй вариант удобнее для диагностики и быстрого фейловера, но пользователю он даёт ошибку там, где раньше была всего лишь задержка.

Что почитать дальше

Заключение

SYN queue, accept queue и backlog — показатели, которые редко попадают в стандартные дашборды, но первыми сигнализируют о начинающейся деградации. Сервер ещё не нагружен по CPU и памяти, а новые соединения уже отбрасываются.

Для быстрой диагностики достаточно двух команд: ss -ltn покажет текущую длину accept queue и её лимит, nstat -az TcpExtListenOverflows — сколько соединений уже потеряно. Для постоянного контроля хватит счётчиков ListenOverflows и ListenDrops из node_exporter с алертом на любой прирост.

А дальше вывод почти всегда сводится к одному из двух: растёт accept queue — разбираться нужно в приложении, растёт SYN queue — в сети и характере входящего трафика.

Опубликовано 26 июля 202610 минут чтенияАнастасия Левина
Средний рейтинг статьи — 4.6

Настроить мониторинг за 30 секунд

Надежные оповещения о даунтаймах. Без ложных срабатываний