Потеря пакетов, которой не видно в ping: как мы её нашли
Заявка выглядит стандартно: «сайт подтормаживает», «видеозвонки рвутся», «API иногда отваливается на несколько секунд». Дежурный инженер открывает терминал и пускает ping до проблемного сервера. Пакеты идут ровно, задержка стабильная, потерь ноль. Тикет закрывается пометкой «с сетью всё в порядке, смотрите на стороне приложения». Проходит ещё пара недель повторных жалоб и один впустую потраченный спринт на профилирование бэкенда — и выясняется, что потери были всё это время. Просто ping в принципе не способен их увидеть. Ниже — разбор конкретного случая: как ложное чувство «сеть здорова» увело расследование не туда и какими инструментами мы всё-таки нашли реальную картину.
Содержание
Версия номер один: «раз ping чистый, дело в приложении»
Первая реакция на жалобы клиентов была логичной с виду. Ping до сервера — стабильные несколько миллисекунд, 0% потерь на выборке в сотни пакетов. Traceroute тоже выглядел спокойно: маршрут не менялся, задержка росла плавно от хопа к хопу, без аномальных скачков. Раз с сетью всё чисто, значит проблема в коде: где-то блокирующий вызов, где-то долгий запрос к базе, где-то утечка соединений в пуле.
Дальше команда потратила заметное время именно на эту гипотезу: включили расширенное логирование в приложении, добавили трейсинг запросов, посмотрели медленные запросы в базе, обновили драйвер ORM, поигрались с таймаутами keep-alive на веб-сервере. Часть находок действительно оказалась полезной — нашли пару неоптимальных запросов, — но жалобы клиентов не прекратились. Обрывы соединений и «зависания» продолжались с той же периодичностью, обычно ближе к вечеру и в моменты пиковой нагрузки.
Именно этот паттерн — жалобы привязаны к конкретным часам, а не размазаны равномерно по суткам — и стал первым звоночком, что дело не в приложении. Логика простая: если бы тормозил именно код (например, из-за накопления мусора в памяти или деградации кэша), характер проблемы был бы более случайным и не так жёстко привязан к часам пиковой нагрузки на сеть. А вот поведение «всё нормально в 3 часа ночи и плохо в 19:00» — классический признак того, что где-to не хватает пропускной способности или буфера именно под нагрузкой.
Почему ping ничего не показывает
Здесь стоит на минуту отступить от конкретного инцидента и разобрать, почему ping в принципе плохо подходит для диагностики проблем с TCP-трафиком приложений, даже если это первое, что все привыкли запускать.
Ping тестирует ICMP, а не TCP. Обычные пользовательские приложения — веб-сервер, API, видеозвонок, база данных — работают поверх TCP или UDP с собственной логикой: скользящее окно, повторные передачи, контроль перегрузки, порядок доставки сегментов. ICMP echo request/reply — это отдельный протокол на том же IP-уровне, и он не проходит ни через одну из этих механик. Чистый ответ на ICMP ничего не говорит о том, как ведёт себя TCP-сессия с реальным объёмом данных.
Сетевое оборудование часто отдаёт ICMP приоритет. На многих маршрутизаторах и коммутаторах служебный трафик управления (в том числе ICMP) обрабатывается отдельной очередью или вообще на другом процессоре/ASIC — так называемый control-plane путь, — тогда как пользовательский TCP/UDP-трафик идёт через data-plane и общие буферы интерфейса. Под нагрузкой это означает: ICMP echo reply долетает без проблем, потому что для него отдельная, менее загруженная очередь, а обычные TCP-сегменты в это же время стоят в очереди на переполненном буфере и теряются. Ping в такой ситуации будет показывать идеальную картину просто потому, что измеряет не то, что реально страдает.
Ping слишком редкий и слишком лёгкий. По умолчанию ping шлёт один маленький пакет в секунду. Реальные потери часто случаются в виде микровсплесков — за десятки миллисекунд буфер интерфейса переполняется, теряется пачка сегментов, затем всё возвращается в норму. Между двумя тиками ping в секунду укладывается множество таких микровсплесков, и обычный ping их просто не видит. Плюс размер ICMP-пакета обычно намного меньше, чем у реальных TCP-сегментов приложения (особенно с учётом TLS-заголовков), поэтому ping не нагружает канал и не провоцирует те же условия, при которых теряются пакеты приложения.
Подробно о том, какую методику измерения задержки стоит использовать вместо голого ping, мы разбирали в статье как измерить реальный ping до сервера: методика — там же объясняется разница между «средним» и «worst case» RTT, которая тоже часто вводит в заблуждение.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверИнструменты, которые смотрят на TCP, а не на ICMP
Как только стало понятно, что проверять нужно именно поведение TCP-сессий, а не ICMP-эхо, набор инструментов сменился полностью.
mtr в режиме TCP. Обычный mtr по умолчанию тоже шлёт ICMP. Но у него есть режим, который шлёт реальные TCP-пакеты (SYN) на конкретный порт приложения:
mtr --tcp -P 443 example.com
Это сразу меняет картину: маршрутизаторы по пути обрабатывают такой пробник так же, как обычный пользовательский TCP-трафик, а не как служебный ICMP, и если конкретный хоп или интерфейс сбрасывает именно data-plane трафик, это будет видно в процентах потерь на этом хопе. Обратите внимание — mtr --tcp обычно требует повышенных привилегий (root либо setcap cap_net_raw), потому что работает с сырыми сокетами.
tcpdump и разбор ретрансмиссий. Для более глубокого анализа снимаем трафик прямо на сервере приложения в момент, когда клиенты жалуются:
tcpdump -i eth0 -w /tmp/incident.pcap host <ip_клиента> and port 443
Дальше файл анализируется либо в Wireshark, либо через tshark в консоли — ищем именно признаки повторной передачи и потери сегментов:
tshark -r /tmp/incident.pcap -Y "tcp.analysis.retransmission" | wc -l
tshark -r /tmp/incident.pcap -Y "tcp.analysis.lost_segment"
Наличие большого числа tcp.analysis.retransmission — это прямое доказательство того, что часть сегментов не дошла с первого раза и TCP-стек их перепослал. Именно это скрыто от ping, но прекрасно видно в захвате трафика.
netstat -s и счётчики ретрансмиссий уровня ОС. Ядро само ведёт статистику по TCP на уровне всего хоста, не привязанную к конкретному соединению:
netstat -s | grep -i retrans
Типичный вывод покажет что-то вроде количества сегментов, отправленных повторно, и количества таймаутов retransmission timeout (RTO). Важно смотреть не на абсолютное число (оно всегда растёт с аптаймом сервера), а на дельту во времени — снимаем счётчик до инцидента и после, и смотрим на прирост за конкретный интервал.
ss с TCP-информацией по конкретному соединению. Если нужно посмотреть не общую статистику, а конкретную «живую» сессию:
ss -ti
В выводе будет видно retrans, текущее значение RTO, размер congestion window (cwnd) — если cwnd внезапно проседает, а retrans растёт именно для сессий с конкретным клиентом или в конкретные часы, это прямая улика.
Для сравнения — что видит и не видит каждый инструмент:
| Инструмент | Что измеряет | Что покажет потерю на реальной нагрузке |
|---|---|---|
ping | ICMP echo, 1 пакет/сек | Почти никогда — другая очередь на оборудовании, слишком редкие пробы |
mtr --tcp | TCP SYN на конкретный порт, по хопам | Да, если потеря есть на конкретном хопе/интерфейсе |
tcpdump + tshark | Реальные сегменты приложения | Да, точное количество ретрансмиссий и потерянных сегментов |
netstat -s | Агрегированные TCP-счётчики хоста | Да, но без привязки к конкретному хопу или клиенту |
ss -ti | Состояние конкретной TCP-сессии | Да, для отдельных «плохих» соединений в реальном времени |
Если тема захвата и разбора трафика новая — стоит посмотреть на tcp-рукопожатие: почему всё виснет, там подробнее про то, как читать флаги SYN/ACK и на что обращать внимание при затянутом установлении соединения — похожая механика ретрансмиссий там тоже разбирается, только на этапе handshake, а не в середине сессии.
Где нашлась потеря
Вооружившись правильными инструментами, запустили mtr --tcp -P 443 в фоне на протяжении суток с логированием в файл каждые несколько минут. Картина стала видна почти сразу: в дневные и вечерние часы пиковой нагрузки один конкретный хоп на маршруте — интерфейс аплинка между внутренним коммутатором и пограничным маршрутизатором — начинал показывать заметный процент потерь именно для TCP-пробников, тогда как для обычного ICMP-пинга на тот же хоп потерь не было вообще. Ночью, когда нагрузка спадала, потери на этом же хопе пропадали полностью.
Чтобы подтвердить находку независимо от mtr, посмотрели статистику самого сетевого интерфейса на этом узле:
ip -s link show eth1
Счётчики dropped и errors на этом интерфейсе действительно росли — причём прирост совпадал по времени именно с часами пиковой нагрузки, а не был равномерным. Это укладывалось в одну из двух рабочих версий: либо на интерфейсе не хватает размера буфера приёма/передачи под всплески трафика, либо там настроена QoS-политика, которая ставит обычный TCP в очередь с более низким приоритетом, чем управляющий трафик — и под нагрузкой эта очередь начинает сбрасывать пакеты раньше, чем закончится физическая пропускная способность канала.
Здесь важна честная оговорка: точный процент потерь, который мы увидели в конкретном инциденте, был заметным именно в часы пик и практически нулевым в остальное время — но приводить здесь точную цифру было бы нечестно, потому что у вас в похожей ситуации она будет своя, в зависимости от оборудования, объёма трафика и настроек буферов. Смысл не в конкретном проценте, а в самом факте: потеря была локализована на одном интерфейсе и проявлялась только под нагрузкой — то, что в принципе невозможно было увидеть, глядя только на графики ICMP-ping.
Дополнительно проверили счётчик отброшенных пакетов на самом коммутаторе (через его собственный CLI) и увидели рост output drops на порту аплинка в те же временные окна — что подтвердило: узкое место именно в переполнении очереди на выходном интерфейсе под нагрузкой, а не где-то в приложении или на стороне клиента.
Что чинили
Диагноз был не про приложение и не про маршрут в целом, а про конкретный узел с недостаточной пропускной способностью очереди под пиковую нагрузку. Дальше — стандартный, но не универсальный набор шагов; конкретное решение всегда зависит от того, что именно нашли на своём оборудовании:
- Пересмотрели QoS-политику на пограничном маршрутизаторе — убрали избыточный приоритет служебного трафика за счёт обычного пользовательского TCP, выровняв очереди так, чтобы под нагрузкой не страдал именно клиентский трафик.
- Увеличили размер приёмного и передающего буфера на проблемном интерфейсе через
ethtool:
ethtool -g eth1
ethtool -G eth1 rx 4096 tx 4096
(конкретные значения зависят от модели сетевой карты и максимума, который она поддерживает — стоит сначала посмотреть текущие и максимальные значения командой ethtool -g без флагов записи).
- Проверили физический уровень — кабель и трансивер (SFP) на этом порту, поскольку рост
errorsнаравне сdroppedиногда указывает не на переполнение буфера, а на деградацию сигнала. В нашем случае основная причина оказалась именно в буферах и QoS, но исключать физику стоит всегда, это быстрая проверка. - Добавили запас пропускной способности на аплинке — там, где узкое место оказалось не в настройках, а в реальном исчерпании канала под пиковую нагрузку, простое перераспределение приоритетов не помогает, нужен физически больший канал или второй аплинк с балансировкой.
Стоит подчеркнуть: это не рецепт «сделайте так же и у вас пройдёт». Правильный фикс всегда следует из диагностики конкретного узла, а не копируется по чек-листу — то оборудование, тот трафик и тот профиль нагрузки, что были у нас, у вас могут отличаться полностью.
Как теперь мониторим
Главный вывод из этого инцидента — не в конкретной находке, а в том, что мониторинг только по ICMP-ping в принципе не мог показать эту проблему, и не покажет похожую в будущем. Поэтому набор мониторинга расширили именно TCP-ориентированными метриками:
- node_exporter + Prometheus для сбора TCP-статистики уровня ядра. Метрика
node_netstat_Tcp_RetransSegs(счётчик ретрансмиссий TCP) снимается автоматически, и на неё настроено правило алерта на резкий прирост за короткий интервал:
- alert: HighTcpRetransmitRate
expr: rate(node_netstat_Tcp_RetransSegs[5m]) > 50
for: 5m
labels:
severity: warning
annotations:
summary: "Резкий рост TCP-ретрансмиссий на {{ $labels.instance }}"
(порог 50 здесь условный пример для ориентира — реальный порог нужно откалибровать по своей базовой линии трафика, у разных серверов она разная).
- Регулярный mtr --tcp по расписанию через cron, с логированием результата в файл или отправкой в тот же Prometheus через textfile collector — это даёт возможность через недели/месяцы посмотреть историю по конкретным хопам, а не полагаться на то, что проблему поймают именно в момент жалобы.
- ss -s раз в минуту с фиксацией снимка в лог, чтобы можно было при следующей жалобе клиента сразу поднять историю retransmit-счётчиков за нужный период, а не гадать постфактум.
Подробнее про то, как собрать похожий стек мониторинга с нуля, разбирали в статье мониторинг VPN через Prometheus и Grafana — там же пример дашборда, куда можно добавить панель именно с TCP retransmit rate, а не только с классическим uptime/ping.
Ping из мониторинга при этом никуда не убрали — он по-прежнему полезен как самая быстрая и дешёвая проверка «жив ли хост вообще». Но теперь это не единственный сигнал, а один из нескольких, и в спорных случаях решение принимается по TCP-метрикам, а не по нему одному.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если ping бесполезен, зачем его вообще использовать?
Он не бесполезен — он просто отвечает на другой вопрос: жив ли хост и какая у него базовая задержка. Это быстрая первая проверка, но не финальный вердикт по качеству TCP-трафика. Держите его как один из сигналов, а не единственный.
Нужны ли root-права для mtr --tcp?
Да, обычно нужны root или как минимум setcap cap_net_raw+ep на бинарник mtr, потому что режим TCP работает с сырыми сокетами так же, как и обычный ICMP-режим.
Как быстро отличить проблему в сети от проблемы в приложении, не поднимая весь этот арсенал сразу?
Самый быстрый первый шаг — сравнить счётчики netstat -s на сервере и (если доступно) на клиенте за один и тот же интервал времени жалоб. Если ретрансмиссии растут с обеих сторон синхронно — вероятнее сеть; если растут только с одной стороны и без корреляции с сетевыми интерфейсами — стоит смотреть приложение.
Можно ли доверять графикам провайдера или аплинк-партнёра, если у них «всё зелёное»?
Частично — обычно провайдер видит только свою сторону канала и часто именно ICMP-метрики по умолчанию. Свою собственную точку измерения ближе к серверу приложения всё равно стоит держать — про диагностику потерь именно на своей стороне туннеля есть отдельный разбор в статье потеря пакетов на VPN: как диагностировать.
А если mtr --tcp тоже показывает чистую картину, а жалобы продолжаются?
Тогда стоит спускаться на уровень ниже — захват трафика через tcpdump с последующим разбором ретрансмиссий и таймингов ACK, потому что проблема может быть не в потере пакетов как таковой, а, например, в задержке подтверждений или в проблемах на прикладном уровне поверх вполне здорового TCP.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →