Многим, наверняка приходилось сталкиваться с ситуациями, когда система медленно умирает, но не падает. То есть все мониторинги горят зелёным, алерты молчат, ни одной падающей ошибки в логах. Но пользователи уходят к конкурентам, потому что «сайт тормозит». Техподдержка завалена жалобами на медленную работу, а разработчики разводят руками — «у нас всё работает, не единого разрыва проверьте интернет».
Это классический сценарий деградации производительности без явных ошибок. Самая коварная категория проблем в инфраструктуре. Система не падает с грохотом, она медленно задыхается день ото дня, неделя за неделей. И если вовремя не заметить — бизнес потеряет сначала пользователей, а потом и деньги.
В этой статье мы попробуем провести небольшое расследование, посвященное тому, как найти и обезвредить «тихого убийцу». В частности, мы на конкретных примерах разберём утечки памяти, фрагментацию дисков, проблему 95-го перцентиля и «зловещую тишину» в логах. А также, рассмотрим инструменты, которые не дадут производительности умереть незаметно.
Медленная смерть по капле
Давайте посмотрим, как может выглядеть постепенная смерть системы. Допустим, она работает месяц, потом два, а на третьем месяце начинает «подтормаживать». Разработчики перезапускают сервис — всё снова летает. Ещё через месяц история повторяется. Думаю, подобное знакомо многим инженерам и администраторам.
Это пример классической утечки памяти. По тем или иным причинам, приложение постепенно занимает всё больше оперативной памяти, сборщик мусора (если он есть) работает всё чаще и дольше, система начинает активно свопиться, и в итоге производительность катастрофически проседает.
Самое страшное в утечках памяти это то, что они накапливаются незаметно. Каждая отдельная утечка может составлять килобайты, но в сумме за месяц они съедают гигабайты.
Здесь типичным является следующий сценарий: процесс убивается OOM‑killer'ом, все чешут затылки и идут смотреть логи. Но хороший инженер ловит утечку задолго до этого момента.
Самый простой, но эффективный способ — смотреть не на абсолютные значения, а на тренды. Например, давайте попробуем собрать статистику по процессу раз в час.
Сделать это можно с помощью следующего простого скрипта:
while true; do echo "$(date): $(ps -C myapp -o rss=)" >> memory.log sleep 3600 done
Через неделю открываем memory.log и смотрим график. Если линия неуклонно ползёт вверх значит у вас утечка. Это конечно довольно примитивный способ обнаружения и для более глубокого анализа нужны специализированные инструменты.
Например, Valgrind (memcheck) — является классикой для приложений, написанных на C/C++. Позволяет найти даже самые мелкие утечки, но сильно замедляет работу, поэтому используется только в тестовых средах. Инструмент heaptrack является более современной заменой Valgrind для Linux, с меньшими накладными расходами. Pyroscope — непрерывный профайлер, который работает в продакшене с минимальным оверхедом (~1-3% CPU) и показывает, какие функции потребляют память в реальном времени.
Ниже приведен пример настройки Pyroscope для Python:
import pyroscope pyroscope.configure( application_name="my-python-service", server_address="http://pyroscope:4040", enable_memory_profiling=True, # Включаем профилирование памяти sample_rate=100, )
Теперь в веб‑интерфейсе Pyroscope можно увидеть, какие функции потребляют больше всего памяти. В качестве примера из жизни давайте разберем случай с драйвером, который не отдавал память. Вот код, который приводит к утечке памяти при повторных инициализациях:
static int cam_probe(struct platform_device *pdev) { struct cam_dev dev = kzalloc(sizeof(dev), GFP_KERNEL); if (!dev) return -ENOMEM; dev->dma_buf = dma_alloc_coherent(...); if (!dev->dma_buf) goto err_free_dev; // Забыли освободить dev->dma_buf в некоторых ветках // ... ещё код ... return 0; err_free_dev: kfree(dev); // А dma_buf не освободили! return -ENOMEM; }
Каждый раз, когда инициализация драйвера срывалась на полпути, часть памяти оставалась занятой навсегда. Через месяц таких перезапусков — система задыхалась. Здесь правильным решением является всегда использовать единую точку выхода с освобождением всех ресурсов.
Фрагментация
Когда база данных или файловая система только созданы, данные пишутся последовательно. Чтение происходит быстро, потому что головки диска (или контроллер SSD) могут читать большие куски подряд.
Но проходит несколько месяцев. Данные удаляются, новые записываются, обновляются. В итоге один файл может быть размазан по диску тысячами фрагментов. Чтение такого файла превращается в квест: головка диска скачет туда‑сюда, теряя драгоценные миллисекунды. А при использовании SSD — увеличивается количество операций ввода‑вывода, что тоже не бесплатно.
Давайте разберемся, как можно выявить фрагментацию для файловых систем и СУБД.
Для проверки файловой системы выполним проверку фрагментации файловой системы
sudo fsck ‑fn /dev/sda1 # Для ext4 sudo xfs_db ‑c frag /dev/sda1 # Для XFS |
Далее смотрим уровень фрагментации конкретных файлов
filefrag /var/lib/mysql/ibdata1
Для СУБД PostgreSQL запрос будет иметь следующий вид:
— Проверка фрагментации таблиц SELECT schemaname, tablename, n_dead_tup, n_live_tup, round(100 * n_dead_tup / (n_live_tup + n_dead_tup + 1), 2) AS dead_pct FROM pg_stat_user_tables WHERE n_live_tup > 0 ORDER BY dead_pct DESC; |
Высокий процент мёртвых кортежей (dead tuples) — признак того, что таблица нуждается в VACUUM и, возможно, VACUUM FULL для дефрагментации.
Для MySQL (InnoDB) аналогичный запрос будет иметь следующий вид:
— Размер и фрагментация таблиц SELECT table_schema, table_name, data_length, index_length, data_free, round(100 * data_free / (data_length + index_length + data_free), 2) AS frag_pct FROM information_schema.tables WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema') ORDER BY frag_pct DESC; |
Поле data_free показывает, сколько места внутри таблицы занято фрагментацией.
Для файловых систем: к сожалению, лучший способ — сделать дамп, переформатировать и восстановить. Для XFS есть xfs_fsr, но работает он не всегда идеально. Что касается СУБД, то здесь лучше всего использовать для дефрагментации те рекомендации, которые рекомендует разработчик.
Проблема среднего арифметического
Допустим, у нас есть график средней скорости ответов — красивая зелёная линия, плавная, укладывающаяся в 200 мс. А пользователи при этом жалуются, что сайт тормозит. Причиной такой ситуации могут быть дашборды, построенные на средних значениях.
Представьте следующую ситцацию: 1000 запросов за минуту. 950 из них отработали за 100 мс, 40 — за 900 мс, и 10 — за 5 секунд. Среднее время будет около 270 мс — вполне прилично. Но 5% пользователей (50 человек из тысячи) получили ответ за секунду и дольше, а 1% — вообще за 5 секунд (те самые скачки на графике).
Для решения этой проблемы нужно вместо среднего смотреть на перцентили:
p50 (медиана) — время, быстрее которого выполняется половина запросов
p95 — время, быстрее которого выполняется 95% запросов
p99 — время, быстрее которого выполняется 99% запросов
Для нашего примера:
p50 = 100 мс (половина пользователей довольны)
p95 = 900 мс (5% пользователей ждут почти секунду)
p99 = 5000 мс (1% ждут 5 секунд!)

Рассмотрим еще один пример. Однажды мы искали причину, почему страница заказов грузится 10 секунд вместо ожидаемой 1 секунды. Среднее время запросов было в норме, p95 тоже, но p99 зашкаливал.
Оказалось, ORM (Object‑Relational Mapping) генерировала один запрос на получение списка заказов, а потом для каждого заказа — отдельный запрос на получение данных пользователя. При 100 заказах на странице получался 101 запрос к базе.
Решением для этой проблемы оказалось включить «жадную загрузку» (eager loading) и собирать все данные одним запросом.
Изначально код выглядел следующим образом:
# Плохо orders = Order.objects.filter(user_id=123) for order in orders: print(order.user.email) # N дополнительных запросов!
Исправленный вариант:
# Хорошо orders = Order.objects.filter(user_id=123).select_related('user') for order in orders: print(order.user.email) # Всё уже загружено
Если хочется понять, насколько уверенно вы ориентируетесь в мониторинге, логировании и трассировке на практике, начните с короткого тестирования. ➥ |
…и тишина
Как мы уже говорили, самое страшное в производительности — отсутствие явных ошибок. Логи чисты, мониторинг показывает зелёные метрики, но система еле шевелится. Это «зловещая тишина» — когда проблема есть, но она не кричит.
Например, таблица заблокирована одной транзакцией, остальные висят в ожидании. В логах — тишина, потому что запросы не падают, они просто ждут.
Найти кто кого заблокировал в PstgeSQL можно с помощью следующего запроса:
SELECT blocked_locks.pid AS blocked_pid, blocking_locks.pid AS blocking_pid FROM pg_locks blocked_locks JOIN pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype WHERE NOT blocked_locks.granted;
Еще один пример молчания это когда все соединения к базе заняты долгими запросами, новые запросы встают в очередь. База жива, пингуется, но новые соединения не принимает.
Также бывают микропотери пакетов, которые не видны на пингах, но приводят к пересылкам TCP и росту задержек.
Что можно сделать для выявления подобных проблем с сетью? Прежде всего можно посмотреть реальные потери на уровне TCP с помощью команды:
netstat -s | grep -i "retransmit"
Также, если собрать дамп трафика и открыть его в Wireshark, то увидеть retransmit пакеты можно с помощью фильтра:
tcp.analysis.retransmission

А для того, чтобы выполнить трассировку с анализом задержек, необходимо запустить:
mtr ‑report your‑server.com
В целом, для сбора информации по системе можно воспользоваться этими командами:
Кто больше всего потребляет CPU прямо сейчас
top -b -n 1 | head -20
Дисковый ввод‑вывод:
iostat -x 1 5
Сеть:
iftop
А если нужно срочно понять, почему Python‑приложение потребляет большой объем CPU, а менять код и разворачивать инфраструктуру некогда, то можно просто установить и запустить инструмент py‑spy.
pip install py-spy
Запуск профайлера для работающего процесса:
sudo py-spy record -o profile.svg --pid 12345
Или сразу смотрим «горячие» функции в реальном времени:
sudo py-spy top --pid 12345
Через минуту получаем flamegraph (огненную диаграмму), где видно, какие функции занимают больше всего времени.
Заключение
Давайте подведем итог сегодняшней статье.
Прежде всего, не верьте средним значениям, смотрите на p95 и p99 — именно там прячутся проблемы.
Также, мониторьте тренды, а не абсолютные значения. Потребление памяти должно быть стабильным, а не расти день ото дня.
Не забывайте о блокировках. Иногда система не падает, а просто ждёт — и это не видно в обычных метриках.
Используйте непрерывный профайлинг. Специализированные инструменты покажут, куда реально уходит CPU и память, без необходимости гадать.
Не забывайте про деградацию на уровне ОС и железа. Фрагментация, утечки в драйверах, микро‑сетевые потери — всё это накапливается и убивает производительность.
Помните: система, которая не падает, но еле дышит — убивает бизнес так же эффективно, как и полностью упавшая. Просто медленнее и незаметнее. Ваша задача — заметить это до того, как заметят пользователи.

Когда система начинает тормозить без явных ошибок, обычного мониторинга уже недостаточно. Нужен навык, который помогает быстро понять, где именно проблема: в запросах, логах, метриках или взаимодействии сервисов.
Курс «Observability: мониторинг, логирование, трассировка» нужен как раз для этого: чтобы не гадать, почему всё «вроде работает», а точно видеть причины деградации и быстрее находить узкие места.
Если стоит задача быстрее находить причины тормозов, а не тратить часы на догадки, начните с открытых уроков ниже:
8 апреля, 20:00 «Надёжность и сети, или почему скорость перестроения STP уже очень давно никому не нравится?» ➟
Записаться14 апреля, 20:00 «Grafana Stack — закрываем все современные потребности Observability» ➟
Записаться
Практические курсы по ИТ-инфраструктуре от экспертов — в каталоге. Подберите маршрут обучения под свою роль: от базовой подготовки до задач в рабочей среде.
