Send-Q растёт, Recv-Q пустая: как читать очереди сокетов и что они говорят о канале
Две колонки в выводе ss или netstat — Send-Q и Recv-Q — почти всегда пролистывают взглядом в поисках столбца с адресом или состоянием соединения. А зря: эти два числа прямо говорят, где именно застряли данные и на чьей они стороне — у вашего приложения, у собеседника на другом конце или у сети между вами. Разбираем, что означают эти колонки в разных состояниях сокета и по какому алгоритму читать их изменение, а не разовое значение.
Содержание
- Одни и те же колонки, разный смысл в зависимости от состояния
- LISTEN: Recv-Q — это очередь на accept(), а не данные
- ESTABLISHED, Send-Q: отправлено, но ещё не подтверждено ACK
- ESTABLISHED, Recv-Q: получено, но ещё не прочитано вами
- Диагностический алгоритм: куда смотреть по тому, что растёт
- Практический сценарий: медленный мобильный клиент раздувает Send-Q на сервере
- На что не стоит полагаться при чтении этих колонок
Одни и те же колонки, разный смысл в зависимости от состояния
Первое, что стоит держать в голове: Send-Q и Recv-Q — это не два фиксированных счётчика с одним и тем же значением независимо от контекста. Их смысл меняется в зависимости от состояния сокета — LISTEN это или ESTABLISHED. Именно поэтому чтение этих колонок «на глаз», без привязки к состоянию строки, — источник половины неверных выводов.
Типичный вызов для TCP-соединений:
ss -tn
# или, если нужны ещё и слушающие сокеты
ss -tan
и эквивалент через netstat:
netstat -tn
netstat -tan
Формат столбцов в ss и в netstat практически идентичен по смыслу (хотя визуально порядок и заголовки чуть отличаются между утилитами и версиями) — Recv-Q идёт первой колонкой, Send-Q второй, дальше локальный и удалённый адрес, и состояние соединения. Вот на это состояние — LISTEN или ESTABLISHED — и нужно смотреть в первую очередь, прежде чем интерпретировать цифры рядом.
LISTEN: Recv-Q — это очередь на accept(), а не данные
Для строки в состоянии LISTEN обе колонки говорят не про переданные байты, а про очередь входящих соединений, ожидающих, пока процесс заберёт их системным вызовом accept():
Recv-Q— текущая длина этой очереди: сколько уже установленных TCP-соединений сейчас стоят и ждут, пока приложение до них доберётся.Send-Q— здесь это не объём данных на отправку, а верхний предел этой очереди: то самое числоbacklog, которое приложение передало в вызовlisten(), ограниченное сверху системнымnet.core.somaxconn.
Если Recv-Q для LISTEN-сокета стабильно растёт и приближается к Send-Q (то есть к лимиту) — это прямой признак того, что приложение не успевает вызывать accept() так же быстро, как приходят новые соединения. Причины разные: однопоточная обработка без асинхронности, исчерпанный пул воркеров, процесс упёрся в CPU или завис на медленном даунстриме — но общий диагноз один: узкое место не в сети, а в том, с какой скоростью код приложения освобождает эту очередь.
Пример вывода:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 498 512 0.0.0.0:8080 0.0.0.0:*
Вторая строка — тревожный сигнал: очередь почти забита (498 из 512), и при следующем всплеске нагрузки новые клиенты либо получат явный отказ, либо застрянут в молчаливом ожидании — зависит от настройки net.ipv4.tcp_abort_on_overflow. Механику самой этой очереди, разницу между SYN-очередью и accept-очередью, поведение при переполнении и настройку backlog на уровне ядра и веб-сервера подробно разбирали в статье про backlog соединений и отказ на живом сервере — здесь важно унести одну мысль: для LISTEN растущий Recv-Q относительно своего Send-Q — это про приложение, а не про сеть.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверESTABLISHED, Send-Q: отправлено, но ещё не подтверждено ACK
Для установленного соединения смысл колонок меняется на противоположный привычной интуиции про «отправку» и «приём»:
Send-Q для ESTABLISHED-сокета — это объём данных в байтах, которые ядро уже приняло от приложения (через send()/write()) и попыталось передать по сети, но которые ещё не подтверждены ACK-пакетом от удалённой стороны. Проще говоря — данные, которые физически ушли (или готовятся уйти) из буфера отправки, но собеседник ещё не сказал «получил».
Разовое ненулевое значение — это нормально: почти всегда в моменте что-то летит по проводу и ждёт подтверждения, особенно на соединениях с заметным RTT. Проблема начинается, когда Send-Q не колеблется около небольшого значения, а стабильно растёт от снимка к снимку и не опустошается:
watch -n1 "ss -tn dst 203.0.113.10"
Устойчивый и не убывающий рост Send-Q на ESTABLISHED-соединении означает одно из двух (а чаще — комбинацию обоих):
- Удалённая сторона не читает данные достаточно быстро. Получатель занят чем-то другим, не успевает вычитывать свой сокет, и его объявленное окно приёма (receive window) сжимается — отправитель физически не может протолкнуть больше данных, пока получатель не заберёт уже доставленные и не расширит окно снова.
- Сеть между вами не пропускает данные с нужной скоростью. Узкий канал, потери пакетов, из-за которых включается пересчёт
cwndв congestion control, или просто длинный маршрут с большим RTT, где окно физически не успевает раскрыться до нужного размера — механика этого разобрана в статье про TCP-окно и длинные толстые каналы.
С точки зрения отправителя оба сценария выглядят одинаково — растущий Send-Q. Разница только в том, что происходит на другом конце и по дороге, а различать эти два случая приходится уже дополнительными инструментами, а не одним ss.
ESTABLISHED, Recv-Q: получено, но ещё не прочитано вами
Recv-Q для ESTABLISHED-сокета — зеркальная история, но уже про вашу собственную сторону: это объём данных, которые уже пришли по сети, подтверждены на уровне TCP и лежат в буфере приёма ядра, но приложение ещё не забрало их вызовом recv()/read().
Если это число стабильно растёт — сеть и удалённая сторона тут ни при чём, проблема локальная: ваш собственный процесс не успевает обрабатывать входящий поток данных. Типичные причины совпадают с теми, что вызывают переполнение accept-очереди у LISTEN-сокета, только уже применительно к чтению данных, а не к приёму новых соединений: перегруженный event loop, медленный обработчик на пути данных, блокировка на диске или на внешнем вызове внутри цикла обработки, нехватка CPU у процесса.
Внутреннюю механику двух независимых буферов — отправки и приёма, — что на самом деле гарантирует успешный send() и почему растущий Recv-Q получателя не видно из кода отправителя, подробно разбирали в статье про буферы сокета и куда деваются данные. Здесь же фиксируем практический вывод: если видите растущий Recv-Q на своей стороне ESTABLISHED-соединения — не тратьте время на диагностику сети, идите смотреть на скорость обработки внутри собственного процесса.
Диагностический алгоритм: куда смотреть по тому, что растёт
Свести оба состояния и обе колонки в одну таблицу удобнее, чем держать в голове:
| Состояние | Колонка | Что означает рост | Куда смотреть дальше |
|---|---|---|---|
| LISTEN | Recv-Q растёт к пределу Send-Q | Приложение не успевает accept() новые соединения | Пул воркеров, CPU, блокировки в цикле приёма, даунстрим |
| LISTEN | Send-Q (сам предел) | Не рост, а конфигурация backlog | Согласованность listen() и somaxconn |
| ESTABLISHED | Send-Q растёт и не опустошается | Получатель не читает быстро или сеть не пропускает | Диагностика получателя и канала между вами |
| ESTABLISHED | Recv-Q растёт и не опустошается | Ваше приложение не успевает читать сокет | Профилирование собственного процесса |
Практический порядок действий:
- Не полагайтесь на один снимок. Разовое ненулевое значение почти ничего не говорит — в моменте на живом соединении почти всегда что-то в полёте. Смотрите на несколько последовательных срезов с интервалом в секунды:
watch -n1 ss -tnили простой цикл сsleep. Важен тренд — растёт ли число монотонно или колеблется около небольшой базовой величины.
- Определите состояние строки прежде, чем читать цифры.
LISTENиESTABLISHED— это, по сути, две разные таблицы с одинаковыми заголовками колонок. Спутать их — самая частая ошибка при быстром просмотре выводаss -tanцеликом, где строки обоих состояний идут вперемешку.
- Растущий
Send-Qна ESTABLISHED → смотрите наружу. Проверьте, не упирается ли получатель в собственный медленныйRecv-Q(если у вас есть доступ к обеим сторонам), прогонитеiperf3между теми же узлами, чтобы отличить проблему канала от проблемы конкретного приложения-получателя, посмотритеss -tinна то же соединение — поляrtt,cwnd,rcv_spaceдадут больше контекста, чем одни только очереди.
- Растущий
Recv-Qна ESTABLISHED → смотрите внутрь. Профилируйте собственный процесс: задержку event loop, время в обработчиках, блокирующие вызовы на пути от сокета до бизнес-логики. Сеть здесь почти наверняка ни при чём — данные уже долетели и лежат в буфере ядра, дело за вашим кодом.
- Растущий
Recv-Qна LISTEN → смотрите на приёмную способность приложения принимать новые соединения, а не на то, что происходит внутри уже установленных — это разные узкие места, и лечатся они по-разному.
Практический сценарий: медленный мобильный клиент раздувает Send-Q на сервере
Один из самых частых практических случаев растущего Send-Q — сервер отдаёт клиенту относительно большой ответ (файл, видеофрагмент, объёмный JSON из API), а клиент сидит на нестабильной мобильной сети с ограниченной и переменной полосой.
Механика простая: сервер honestly копирует данные в буфер отправки и начинает передавать их сегментами. Клиент физически не успевает вычитывать их с той же скоростью, с какой сервер готов отдавать, — либо из-за узкого канала, либо из-за высокой и рваной задержки на мобильной сети. Клиентский стек TCP реагирует штатно: объявляемое окно приёма сужается, ограничивая, сколько неподтверждённых данных сервер вообще может держать «в полёте». Сервер упирается в это окно — и его собственный буфер отправки, куда приложение продолжает класть новые данные (например, следующие чанки того же файла), начинает заполняться неотправленными и неподтверждёнными байтами.
На сервере в этот момент ss -tn покажет примерно такую картину для соединения с этим конкретным клиентом:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 184320 198.51.100.20:443 203.0.113.77:51422
Ненулевой и растущий от проверки к проверке Send-Q именно на этом соединении — при том что у остальных клиентов на том же сервере Send-Q держится около нуля — прямой указатель на то, что проблема не в сервере целиком и не в канале до дата-центра, а в конкретном получателе или в участке сети именно до него. Полезно перепроверить это как раз через сравнение: если у одного клиента Send-Q растёт, а у остальных нет, при одинаковой логике сервера — вывод почти всегда однозначный.
Важная оговорка: это не единственная возможная причина растущего Send-Q у одного конкретного соединения — тот же эффект даёт и потеря пакетов именно на маршруте до этого клиента, из-за которой congestion control снижает cwnd и придерживает отправку независимо от готовности получателя читать. Различить «получатель медленный» и «маршрут теряет пакеты» по одному Send-Q нельзя — нужен ss -tin на то же соединение и взгляд на счётчики ретрансмиссий, либо mtr/traceroute до конкретного клиентского IP.
На что не стоит полагаться при чтении этих колонок
Несколько практических оговорок, которые снимают типичные ложные тревоги:
- Единичный снимок с ненулевым значением — это норма, а не авария. На loaded-сервере с активным трафиком почти всегда найдётся десяток соединений с небольшим
Send-QилиRecv-Qв моменте — это данные в полёте, а не проблема. Тревожный сигнал — устойчивый рост без опустошения на протяжении нескольких проверок подряд. - Не путайте
Send-QLISTEN-сокета (лимит) сSend-QESTABLISHED-соединения (данные в полёте). Это разные по смыслу числа с одинаковым именем колонки — источник половины путаницы при быстром просмотре смешанного выводаss -tan. - Не выдумывайте универсальный порог «нормального» значения в байтах. Приемлемая величина зависит от размера буферов (
net.ipv4.tcp_wmem/tcp_rmem), от типичного размера полезной нагрузки конкретного приложения и от RTT до клиентов. Число, которое для одного сервиса — тревожный сигнал, для другого (например, с крупными файлами и BDP на мегабайты) — совершенно рабочая величина. Ориентируйтесь на тренд у себя, а не на абсолютную цифру из чужой статьи. ss -tnдаёт только эти два числа — для более глубокой картины нуженss -tinс полямиrtt,cwnd,rcv_space,retransна том же соединении. КолонкиSend-Q/Recv-Q— это отправная точка диагностики, точка «куда смотреть дальше», а не полный диагноз сама по себе.
Похожий по духу разбор — реальный инцидент, где растущий Send-Q на вебсокет-соединении несколько недель маскировался под «соединение живое, просто пинги работают», пока не завели отдельный мониторинг буфера, — можно посмотреть в статье про буфер отправки, который переполнялся молча. Там же — рабочий пример backpressure-контроля поверх такой же диагностики через ss.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Оба значения — Send-Q и Recv-Q — нулевые. Это нормально?
Да, для соединения без активного обмена данными в моменте это ожидаемая картина: всё, что было отправлено, подтверждено, а всё, что пришло, уже вычитано приложением. Нулевые значения на idle-соединении не говорят ни о какой проблеме.
ss и netstat показывают одно и то же для этих колонок?
По смыслу — да, обе утилиты берут эти цифры из одной и той же информации ядра о TCP-соединении. Расхождения бывают в порядке колонок, заголовках и наборе дополнительных полей (ss в целом даёт больше деталей через флаг -i), но сама пара Recv-Q/Send-Q и её смысл в зависимости от состояния соединения одинаковы в обеих утилитах.
Можно ли по одному только Send-Q понять, виноват получатель или сеть между вами?
Нет однозначно. Растущий Send-Q говорит, что данные не подтверждаются вовремя, но не говорит почему — медленный читатель на другом конце и потери пакетов на маршруте дают одинаковую картину на этой колонке. Различать нужно через ss -tin (счётчики ретрансмиссий, cwnd) и внешние инструменты вроде iperf3 или mtr до конкретного адреса.
Поможет ли увеличение буферов (SO_SNDBUF/SO_RCVBUF), если Send-Q или Recv-Q стабильно растёт?
Обычно нет, если причина — не в размере буфера, а в скорости чтения на одной из сторон. Больший буфер отодвигает момент, когда send() начнёт блокироваться или отдавать EAGAIN, но не устраняет саму причину: медленного получателя, медленного собственного обработчика или потери на маршруте. Это способ выиграть время на диагностику, а не решение проблемы.
Recv-Q на LISTEN-сокете то растёт, то падает волнами — это уже авария?
Не обязательно. Колебания в разумных пределах при всплесках нагрузки — ожидаемое поведение: очередь наполняется в момент пика и разгружается, когда accept() догоняет. Тревога — это устойчивый рост без возврата к низким значениям между всплесками, а тем более приближение к самому лимиту Send-Q этой же строки. Подробнее о том, что при этом видит клиент и как настроить backlog под свой профиль нагрузки, — в статье про очередь соединений и отказ на живом сервере.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →