MAATRIX / Блог / Вирусный рост: что ломается первым — база, диск или канал

Вирусный рост: что ломается первым — база, диск или канал

MAATRIX

Неважно, откуда взялся всплеск — ролик набрал миллион просмотров, ссылку подхватило крупное СМИ или пост разлетелся по телеграм-каналам: физика отказа инфраструктуры одна и та же. Сервер не падает целиком и сразу — сначала отказывает один конкретный компонент, и от того, какой именно, зависит, что чинить в первую очередь. Разберём три классических кандидата — сетевой канал, базу данных и диск — как отличить их друг от друга по метрикам за пару минут, не гадая, и почему интуитивное «сервер не тянет» почти всегда указывает не туда.

Три кандидата на отказ и почему они рвутся не одновременно

У резкого роста трафика есть общая черта: нагрузка растёт быстрее, чем успевает сработать любая система защиты или предупреждения. Но три ключевых ресурса — полоса канала, пул соединений к базе и свободное место на диске — исчерпываются по разным законам, и это не совпадение, а следствие архитектуры типового веб-проекта.

Канал — это жёсткий физический потолок: конкретное число мегабит в секунду, которое либо есть, либо нет. Он либо упирается в лимит тарифа, либо нет — промежуточных состояний почти не бывает, и если контент лёгкий (HTML, JSON, мелкие картинки за CDN), канал редко становится первым виновником даже при десятикратном росте посетителей.

База данных обычно рвётся раньше всех остальных, и вот почему: типичный max_connections в MySQL или PostgreSQL по умолчанию — несколько сотен, а каждый новый пользователь на пике может открывать отдельное соединение, если в приложении не настроен пул. Небольшое приложение, которое годами держало 20-30 одновременных соединений, при росте трафика в двадцать раз упирается в лимит соединений не через часы, а через десятки секунд.

Диск ломается тише всех и часто позже — не потому что он крепче, а потому что механизм другой: не пропускная способность, а накопление. Логи nginx, PHP-FPM, приложения растут пропорционально числу запросов, и если ротация логов настроена на «раз в сутки», а не на объём, тысячекратный рост RPS может заполнить раздел /var/log за пару часов, а не за месяц, как рассчитывалось.

Дальше — по каждому из трёх кандидатов: как узнать именно его, какие команды дают однозначный ответ и какие ложные сигналы сбивают с толку.

Канал: когда трафик физически не помещается в полосу

Насыщение канала — самый простой для диагностики случай, потому что метрика однозначная: либо исходящий трафик уткнулся в лимит порта, либо нет.

Быстрая проверка в реальном времени:

nload -m                    # график входящей/исходящей полосы по интерфейсам
iftop -i eth0 -n            # кто именно генерирует трафик прямо сейчас
vnstat -l -i eth0           # live-счётчик без установки дополнительных агентов
ethtool eth0 | grep Speed   # заявленная скорость линка — с чем сравнивать

Логика простая: если nload или vnstat показывают исходящий трафик, вплотную прижатый к скорости линка (например, 950+ Мбит/с на гигабитном порту) — это канал, и других объяснений тормозам искать не нужно. Если полоса занята на 20-30% от заявленной, а сайт всё равно тормозит — дело не в канале, и дальше идти в эту сторону бессмысленно. Отдельная ловушка: заявленный «гигабитный канал» — это пропускная способность порта, а не гарантия того, что она достанется вам целиком, особенно на тарифах с общим (не выделенным) каналом на нескольких клиентов одного физического сервера.

Второй симптом упора в канал — не абсолютные цифры полосы, а рост очередей и retransmit'ов на сетевом уровне:

ss -s                                    # общая сводка TCP-сокетов
netstat -su | grep -i retrans            # ретрансмиты на уровне ОС

Растущий счётчик retransmit одновременно с полосой на пределе — верный признак: пакеты теряются не из-за проблем на маршруте, а из-за переполнения буферов на исходящем интерфейсе. Если канал не насыщен, а retransmit всё равно растёт — это, вероятнее всего, отдельная сетевая проблема (плохой аплинк, перегруженный сосед по гипервизору), а не следствие вашего трафика.

Важная оговорка: если проект отдаёт тяжёлый контент напрямую с сервера — видео, большие изображения, архивы — канал становится реальным кандидатом на первое место в очереди отказов. Если контент лёгкий и статика уже уходит через CDN, канал почти всегда держится дольше базы и диска, и тратить первые минуты диагностики на него — не самая эффективная стратегия.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Подобрать VPS под нагрузку

База данных: почему пул соединений рвётся раньше, чем кажется

Если канал свободен, а сайт всё равно не отвечает или отвечает с задержкой в секунды — следующий подозреваемый почти всегда база данных, и на практике именно она чаще всего оказывается первым узким местом при вирусном росте.

Причина в арифметике, а не в мощности железа: max_connections в MySQL по умолчанию — 151, в PostgreSQL — 100. Если у приложения нет пула соединений (PgBouncer, ProxySQL, встроенный пул фреймворка) и каждый веб-запрос открывает новое соединение к базе, то при кратном росте одновременных пользователей лимит соединений исчерпывается почти мгновенно — гораздо раньше, чем CPU или память сервера БД успевают вырасти до тревожных значений.

Диагностика за минуту:

-- MySQL
SHOW STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';
SHOW FULL PROCESSLIST;
-- PostgreSQL
SELECT count(*) FROM pg_stat_activity;
SHOW max_connections;
SELECT pid, state, wait_event, query FROM pg_stat_activity WHERE state != 'idle';

Если Threads_connected (или количество строк в pg_stat_activity) вплотную приближается к max_connections — это база, и дальнейшие действия однозначны: искать, что не отпускает соединения, временно поднимать лимит (с оглядкой на RAM — каждое соединение PostgreSQL съедает несколько мегабайт) и, если пула ещё нет, разворачивать PgBouncer или его аналог в аварийном режиме.

Второй признак упора в базу — не исчерпанный лимит соединений, а очередь блокировок: несколько десятков активных соединений висят в состоянии Waiting for table lock или Lock в wait_event, при этом CPU базы не загружен. Это значит, что не хватает не соединений, а пропускной способности самих запросов — один медленный запрос без индекса или конкурентная запись в одну и ту же строку держит остальные в очереди. Здесь помогает не апгрейд, а EXPLAIN на подозрительный запрос и, часто, один недостающий индекс.

Отдельно стоит проверять not CPU-загрузку базы саму по себе, а именно число активных соединений и характер запросов — сервер БД с загрузкой CPU в 20% может при этом полностью отказывать в новых соединениях, если упёрся именно в лимит max_connections, а не в вычислительную мощность. Похожая логика разбирается в статье про DDoS на API, где первой падает база, а не канал — механизм совпадает даже при легитимном вирусном трафике: десятков параллельных дорогих запросов достаточно, чтобы уложить бэкенд при полностью свободном канале.

Диск: когда логи и временные файлы съедают место в реальном времени

Диск ломается не так, как канал или база — не через отказ в обслуживании новых запросов, а через физическое заполнение раздела, после чего запись куда угодно (логи, сессии, временные файлы загрузки, кеш) начинает возвращать ошибку, и приложение валится с 500-ми там, где раньше просто писало лог.

Live-мониторинг заполнения:

watch -n 2 'df -h'                          # обновление каждые 2 секунды
du -sh --exclude=/proc --exclude=/sys /* 2>/dev/null | sort -rh | head -15
journalctl --disk-usage                     # если систему логов ведёт journald

При вирусном росте трафика на заполнение диска чаще всего работают три источника, и все три растут пропорционально RPS, а не линейно во времени:

  • Логи веб-сервера и приложения. access.log nginx при обычном трафике в сотни визитов в день растёт на мегабайты, а при росте в десятки раз — на гигабайты за часы. Если logrotate настроен по расписанию («раз в сутки»), а не по размеру, между ротациями лог может съесть весь свободный раздел.
  • Временные файлы и сессии. PHP-сессии в /var/lib/php/sessions, временные файлы загрузки в /tmp, кеш рендеринга страниц — при кратном росте одновременных пользователей число одновременно живущих временных файлов растёт в той же пропорции.
  • tmpfs-разделы в памяти. Директории вроде /run или /dev/shm, смонтированные как tmpfs, физически занимают оперативную память, а не диск — при их разрастании сервер убивает процессы через OOM-killer раньше, чем df покажет проблему на обычном разделе. Разбор именно такого инцидента — в статье про tmpfs на /run, который вырос до 8 ГБ и положил сервер.

Важно не путать два разных «диск упёрся»: заполнение места (df -h показывает 100% использования раздела) и упор в пропускную способность ввода-вывода (iostat показывает высокий %util при обычном свободном месте). При вирусном росте трафика на статическом или преимущественно читающем сайте первым обычно кончается именно место — из-за логов, а не скорость записи. А сама метрика %util в iostat к тому же не значит то, что кажется на первый взгляд, особенно на NVMe — нюансы разобраны в статье про то, почему 100% в iostat не всегда означает предел диска.

Практическое правило для аварийной ситуации: временно вынести самые быстрорастущие логи на отдельный раздел или в syslog-приёмник за пределами основного сервера, сжать и отправить в архив старые логи, и только потом разбираться с причиной — почему logrotate не успевает.

Экспресс-диагностика за две-три минуты: единый чек-лист

Когда график нагрузки уже летит вверх, тратить время на последовательный перебор трёх разделов статьи некогда — нужен один проход, который сразу даёт ответ.

echo "=== КАНАЛ ==="
vnstat -l -i eth0 &   VNPID=$!
sleep 3; kill $VNPID 2>/dev/null

echo "=== БАЗА (MySQL) ==="
mysql -e "SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"

echo "=== ДИСК ==="
df -h | grep -vE '^tmpfs|^udev'

echo "=== ПАМЯТЬ / TMPFS ==="
df -h | grep tmpfs
free -h

Таблица-шпаргалка для интерпретации результата:

СимптомВероятная причинаЧто проверить дальше
Исходящий трафик у лимита линка, растут retransmitКаналethtool, тариф провайдера, доля статики без CDN
Threads_connected близко к max_connections, CPU базы низкийПул соединений к базеНаличие PgBouncer/ProxySQL, пул в приложении
Много соединений в состоянии Waiting for lockМедленный запрос или блокировкаEXPLAIN, недостающий индекс
df -h растёт на глазах, растёт конкретно /var/logЛоги не успевают ротироватьсяlogrotate по размеру, временный вынос логов
Растёт tmpfs-раздел (/run, /dev/shm), падает свободная RAMВременные файлы в памятиЧто пишет в tmpfs, лимит размера tmpfs
Канал свободен, база в норме, диск в норме, но сайт тормозитCPU веб-сервера или воркеров приложенияuptime, top, число воркеров PHP-FPM/gunicorn

Правило чтения таблицы: искать не «что вообще не в порядке», а первое совпадение сверху вниз — как только пункт подтвердился метрикой, действовать по нему, не распыляясь на остальные. После снятия первого узкого места часто вскрывается следующее — это нормально, цикл стоит повторить.

Какой компонент чаще всего рвётся первым на практике

Универсального ответа нет — порядок отказа зависит от архитектуры конкретного проекта, но общая закономерность на типовых VPS-проектах (сайт на LAMP/LEMP-стеке или Node.js с одной базой) прослеживается достаточно чётко.

База данных чаще всего первая, потому что лимит соединений — самый низкий из трёх потолков в абсолютных цифрах, и на него редко закладываются заранее: сотня-другая соединений выглядит достаточной, пока трафик не вырастает на порядок. Особенно уязвимы проекты без пула соединений и с ORM, который по умолчанию открывает новое соединение на каждый запрос.

Диск — второй по частоте, и почти всегда через логи, а не через нехватку места под сам контент. Проекты, где /var/log живёт на общем с системой разделе без отдельного лимита, рискуют заполнить его на пике за считаные часы, особенно если включено подробное логирование (debug-уровень, логирование каждого SQL-запроса).

Канал ломается первым реже всего на типичном контентном или e-commerce проекте — современные тарифы VPS обычно дают запас полосы, которого хватает на многократный рост числа посетителей, если контент не видео- и не файлоориентированный. Исключение — проекты с большим весом медиаконтента без CDN: тогда канал вполне может обогнать и базу, и диск.

Эта закономерность — не гарантия, а ориентир: у конкретного проекта соотношение лимитов может быть любым, и единственный надёжный способ узнать реальный порядок отказа — сравнить свои цифры (max_connections, размер логового раздела, тариф по полосе) заранее, до пика. Отдельно стоит подчеркнуть: сам факт и природа источника трафика — попал ли проект в подборку СМИ, разошёлся ролик блогера или сработала реклама — на порядок отказа компонентов не влияет; влияет только архитектура и настройки сервера. Разбор именно первой помощи в момент такого всплеска, независимо от причины, — в статье про экстренные действия, когда трафик вырос за ночь после попадания в СМИ: там пошаговый план на первый час, здесь — как быстро понять, какой из трёх компонентов пробило первым.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Подобрать VPS под нагрузку

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли заранее узнать, что откажет первым, не дожидаясь реального пика?

Да, приблизительно — сравните три потолка на своей системе: max_connections базы против текущего числа одновременных пользователей на пике, размер и скорость роста /var/log за последнюю неделю, заявленную полосу тарифа против пикового трафика в мирное время. Компонент с наименьшим запасом по отношению к текущей нагрузке — первый кандидат на отказ.

Что делать, если все три метрики выглядят нормально, а сайт всё равно не отвечает?

Проверьте CPU и число воркеров веб-сервера — на PHP-FPM это pm.max_children, на gunicorn или node.js — число процессов/потоков. Часто узкое место не в инфраструктурных ресурсах, а в лимите параллельных запросов, которые может обработать сам процесс приложения, даже если CPU, память, диск и канал свободны.

Стоит ли поднимать max_connections в базе заранее, про запас?

Разумный запас — да, но не бесконтрольно: каждое соединение MySQL или PostgreSQL резервирует память, и слишком высокий лимит на сервере с ограниченной RAM может привести к OOM при реальном заполнении всех соединений. Правильнее сочетать умеренный запас лимита с пулом соединений (PgBouncer, ProxySQL), который переиспользует уже открытые соединения вместо создания новых под каждый запрос.

Ротация логов по расписанию (раз в сутки) — это вообще плохая практика?

Для стабильного трафика — нормальная. Проблема именно в пиках: logrotate, настроенный по времени, не реагирует на аномальный рост объёма между плановыми запусками. Более устойчивый вариант — ротация по размеру (директива size в logrotate) или потоковая отправка логов во внешнюю систему (syslog, Loki, ELK), где место на локальном диске вообще не задействовано.

Одинаковая ли методика диагностики для вирусного трафика от людей и от нагрузочного бота или парсера?

Инструменты те же самые (ss, iostat, SHOW PROCESSLIST), но интерпретация может отличаться: легитимные посетители обычно распределены по разным IP и создают разнообразный паттерн запросов, а агрессивный бот часто бьёт в один и тот же дорогой эндпоинт с одного диапазона адресов. Если диагностика показывает, что упор в базу идёт от повторяющихся одинаковых запросов с небольшого числа IP — это повод проверить логи на паттерн скрапинга или атаки, а не только списывать всё на органический рост.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →