Первый запрос от мониторинга пришёл аккуратно: «/ — 95% full». Через полчаса доходило до критических цифр, а через два часа сервисы начали отвечать ошибкой базы данных. Я — Андрей Соколов, бэкенд с десятью годами на Go — расскажу, как одна комбинация метрик и логов поставила прод в read‑only и почему это было не только про место на диске.
Как всё началось: короткая хронология
День был обычный. Ранние пики трафика, пара деплоев в соседнем кластере, метрики шли как обычно. В 11:10 мониторинг поднял предупреждение по диску на одном из монолитных серверов: 85%. К 11:40 — 95%. В 12:05 диск был заполнен почти полностью, в 12:12 база данных перевела файловую систему в режим read‑only (поведение Postgres/innodb при ошибках записи), и служба, отвечающая за обработку писем и платежей, упала. В 14:30 инженер дежурства откатил часть логов и restore вручную — к 15:00 система снова в рабочем состоянии. Итог: около 3 часов простоя, несколько сотен фейлов в очередях и пара злых писем от бизнеса.
Причины, которые я нашёл
- Рост метрик и логов в связке. Мы включили детализированный debug‑лог для одного сервиса в проде незадолго до инцидента — и пара минутёрных точек в метриках стали генерировать сотни MB в минуту.
- Неправильная ротация логов. logrotate был настроен только по размеру, без ограничения суммарного объёма или числа файлов, плюс опция compress выполнялась медленно, и временные .gz.tmp файлы занимали место.
- Алерты с задержкой. Мониторинг срабатывал при 85%, но мы не имели правила «увеличивающаяся скорость роста» — другой алерт, который должен был поймать экспоненциальное заполнение, был выключен.
- Отсутствие таргетов по retention для метрик. Метрики с детализацией в 1 секунду хранились неделю — нормально для dev‑кластеров, но не для прод‑сервера.
Что конкретно ломалось технически
Коротко: файловая система не могла выделять новые inode/блоки, WAL Postgres не писал, и СУБД автоматически переводила базу в read‑only. Это — не магия, это защита от порчи данных, но для приложений выглядит как «сервис упал». Мы увидели ошибки «cannot write to file», «read only filesystem» и кучку таймаутов в upstream.
Небольшой пример команд, которые мы использовали в разборе
# посмотреть крупные файлы
sudo du -ah /var | sort -rh | head -n 30
# проверить свободное место и inode
df -h /
df -i /
# найти логи старше N дней
sudo find /var/log -type f -mtime +7 -exec ls -lh {} \;Разбор инцидента по цифрам
Пара цифр из наших логов и мониторинга, чтобы представить масштаб:
- Сервер имел диск 500 GB, из них 460 GB использовалось. Мы потеряли свободных ~40 GB — это меньше 10% диска, но достаточно для WAL и временных файлов Postgres при пиковых операциях.
- Накопление логов шло со скоростью ~300–400 MB/мин в самые активные 15 минут (это измерено по приращениям файлов логов). За 15 минут — до 6 GB.
- Индексация метрик: 1s granularity для 20 ключевых метрик дала нам около 100M записей в день на хост. Хранилище метрик было локальным, репликации на S3 не было.
- После падения очередей у нас накопилось примерно 1.2k задач в очередях на обработку писем и около 300 оплатных транзакций в состоянии retry.
Числа приблизительные — в разных системах метрики считаются по‑разному, но порядок величин верный и помог понять узкие места.
Что мы поменяли в алертах и ротации
После инцидента мы сделали набор конкретных правок — не «добавили больше алертов», а прописали их бизнес‑логикой.
- Алерт на скорость заполнения диска. Новый алерт смотрит рост заполнения за 15 минут и срабатывает если скорость > 1 GB / 15min. Это ловит быстрые утечки, когда обычный порог срабатывает слишком поздно.
- Каскад алертов. 85% теперь — warning с тикетом в инцидент‑чат, 92% — critical и page инженера, 98% — автоматический переключатель логики: включается скрипт чистки tmp/старых логов и уменьшается retention метрик.
- Ограничение retention для детальных метрик. Метрики с granularity 1s хранятся максимум 48 часов. Если нужен аудит дальше — делаем downsampling до 1m и сохраняем ещё 30 дней.
- Ротация логов по суммарному объёму. Добавили систему, которая гарантирует, что логи не займут более 50% диска: старые архивы удаляются, а compress выполняется асинхронно на другом сервере.
Скрипты и фрагменты, которые мы добавили
Ниже — реальные куски, которые мы положили в репозиторий ops, чтобы ускорить реакцию и автоматизацию. Прошу без прямого копирования в production — адаптируйте под свои пути и политики.
# простой скрипт очистки старых логов и tmp (пример)
#!/bin/bash
LOGDIR=/var/log/myapp
MAX_TOTAL=250000000000 # ~250GB в байтах, пример
CURRENT=$(du -sb "$LOGDIR" | awk '{print $1}')
if [ "$CURRENT" -gt "$MAX_TOTAL" ]; then
echo "Total logs $CURRENT > $MAX_TOTAL, removing oldest..."
find "$LOGDIR" -type f -name '*.log.*' -printf '%T+ %p\n' | sort | head -n 50 | awk '{print $2}' | xargs -r rm -f
fiЕщё добавили один‑два systemd timer, чтобы запускать compress/transfer на другом узле каждые 5 минут и не блокировать основной сервер.
Операционные изменения и практические уроки
- Регулярно тестируйте поведение базы при заполнении диска в staging: как она реагирует, какие логи появляются, сколько времени занимает восстановление.
- Не делайте debug‑логи в prod без автоматического таймаута. Мы теперь включаем verbose только через feature flag с TTL.
- Договоритесь о SLA для восстановления: у нас были неочищенные ожидания между dev и oncall — теперь роллинг восстановления прописан в runbook.
"Если что‑то можно автоматизировать — автоматизируй; если не можешь автоматизировать — документируй, как вручную вернуть всё в рабочее состояние." — слова моего коллеги Сергея на одном из поздних oncall'ов
Что мы делали во время инцидента: по шагам
Чтобы не оставлять общих фраз — коротко по шагам, что конкретно делали в первые 2 часа:
- Определили узел с 95% заполнением и снэпшоти диска (чтобы сохранить состояние перед чистками).
- Остановили не критичные сервисы (фоновую обработку, batch‑джобы), чтобы снизить скорость записи.
- Запустили быстрый find по /var и нашли 3 лог-файла по 1.2 GB и пару временных .tmp архивов по 4–6 GB — удалили 2 самых старых после проверки, освободив ~12 GB.
- Перевели Postgres в recovery mode, восстановили WAL из архива, откатили последние некритичные логи и перезапустили сервисы с пониженным retention.
Эти шаги заняли примерно 2 часа и ещё час ушёл на проверку целостности и мониторинг очередей, пока система полностью не пришла в рабочее состояние.
Что бы я сделал по‑другому сразу
Признаюсь: я слишком доверял, что «logrotate да всё решит». Надо было смотреть не только пороговые алерты, но и темпы изменений, а также распределение по файлам. В следующий раз — правило «разбивай по дискам»: метрики и WAL на разных дисках, логи на сетевом хранилище или в S3, чтобы локальный корневой раздел не страдал от шумовой активности.
Конкретный совет: поставьте алерт на прирост (%) или абсолютный рост (GB) в коротком окне и ограничьте retention для высокоразрешённых метрик. И ещё — перед включением чего‑то детального в проде добавьте TTL на включение: если никто не выключил debug через 24 часа, система автоматически его гасит.
А я пойду добавлю ещё один тест в CI, который симулирует быстрое заполнение диска — это займёт 20 минут, но следующий подобный инцидент не займёт у нас три часа.






Комментарии(4)
Чтобы участвовать в обсуждении, или .