Проблема

Утро, все как обычно. Разработчики пишут: «Слушай, что-то с Sentry. Вроде работает, но issues не приходят». Проверяю статус, инстанс жив, но все верно: логов нет, issues не создаются.

Первый рефлекс, перезапустить. Делаю docker compose down && docker compose up. Через минуту инстанс поднимается, issues идут, разработчики довольны. День-два все нормально.

Потом повторяется. Issues нет. Нужен еще один рестарт.

Через несколько дней такого становится понятно: это не случайное падение. Происходит что-то системное. Перезапускать каждый день не решение.

Первая попытка: базовые метрики

Смотрю на дашборд мониторинга. На сервере только базовые метрики: диск, память, CPU, сеть. Все выглядит нормально, ничего не выделяется. Пользы никакой.

Добавляю детализацию: метрики диска и Redis

Понимаю, что нужна более гранулярная картина. Добавляю:

  • Disk I/O в разрезе устройств (dm-0, dm-1, dm-2, sda, sr0)

  • Load Average

  • Redis: RAM, evicted keys, expired keys, connected clients

Включаю и жду следующего утра.

Первая реальная зацепка

Следующим утром примерно в то же время вижу это на графиках.

Image description
Image description

Память Redis выстреливает до лимита. Потом начинают вытесняться ключи. Это уже настоящая зацепка: если события обрабатываются нормально, память должна освобождаться. Если она забивается и ключи вытесняются, значит, события где-то застревают.

Они копятся в Redis, потому что их никто не обрабатывает.

Добавляю метрики Kafka

Копаюсь в логах и метриках и нахожу ответ. Snuba-консьюмеры лежат. Они не разбирают события из Kafka. Это все объясняет: события не обрабатываются, не удаляются из Redis, Redis забивается, ключи вытесняются.

Перезапускаю Snuba-консьюмеров, они поднимаются, события начинают разгребаться, все приходит в норму. Разработчики снова видят issues.

Но я понимаю, что завтра это повторится. Что-то стабильно убивает эти консьюмеры. Нужно понять что.

Теперь мне нужен контроль над консьюмерами. Добавляю более детальные метрики по Kafka:

  • Stuck consumers

  • Consumer group members

  • Consumer lag

Заодно вешаю алерты на consumer lag, чтобы ловить проблему сразу.

Метрики Kafka consumer groups
Метрики Kafka consumer groups

Иду по следу: Disk I/O

Жду, когда проблема проявится снова. В этот раз смотрю внимательно.

При следующем срабатывании присматриваюсь к графику disk I/O, который добавил раньше. И вот оно, странно что не заметил раньше

Каждое утро примерно в одно и то же время disk I/O упирается в потолок почти на 30 минут подряд. Disk I/O почти 100%, диск полностью загружен.

Это не случайность. Происходит ровно в одно и то же время каждый день.

Тут все складывается. Kafka работает с диском. Когда диск полностью загружен, Kafka не может нормально читать и писать. Консьюмеры начинают ловить таймауты, соединения рвутся, они отваливаются.

Но почему диск так загружен? Активных процессов, которые пишут большие объемы, нет. Ни джоб, ни бэкап-сервисов. Проверяю и ничего подозрительного.

Спрашиваю у админов, что происходит с диском по утрам.

«А, ну да, по утрам делается полный бэкап или снепшот сервера. Занимает диск где-то на полчаса».

Полная картина

Теперь видна вся цепочка событий:

  1. Утро: стартует бэкап/снепшот - диск полностью забивается

  2. Kafka не может нормально работать - диск занят, I/O заблокирован

  3. Snuba-консьюмеры ловят таймауты - соединения рвутся, они отваливаются

  4. События остаются в Redis - они удаляются только после успешной обработки

  5. Redis начинает забиваться необработанными событиями

  6. Redis вытесняет старые ключи, чтобы освободить место

  7. Через 30 минут диск освобождается - консьюмеры снова работают, события разгребаются

  8. Но часть консьюмеров так и осталась отвалившейся после таймаута, поэтому обработка идёт медленнее

  9. Redis продолжает расти - медленнее, чем раньше, но растет. Пока я не перезапущу Snuba-консьюмеров

Память Sentry
Память Sentry

Бэкап - неизбежная часть продакшен-инфраструктуры. Просто выключить его нельзя, данные критичны. Нужно адаптировать систему так, чтобы она переживала окно бэкапа.

Решение

Было сделано несколько слоев.

Первое: relay на всех сервисах, где еще не успели Relay - это буфер. Если Redis недоступен или перегружен, события копятся локально на relay, а потом отправляются, когда Redis оживет. Защита от потери данных:

https://github.com/getsentry/relay

Второе: увеличил лимит памяти Redis. Вместо того чтобы вытеснять ключи при заполнении памяти, просто дать памяти больше пространства:

...
redis:
  ...
  command: redis-server --maxmemory 10gb --maxmemory-policy allkeys-lru
  ...

Политику вытеснения ключей я менять не стал, чтобы в худшем случае Redis не встал совсем. Лучше потерять несколько ключей, чем заблокировать весь сервис.

Третье: healthcheck и авторестарт для Snuba-консьюмеров. Если консьюмер падает, пусть перезапускается сам. Быстро:

snuba-consumer:
  image: getsentry/snuba:latest
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:1218/health"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s
  restart: on-failure:5
  environment:
    SNUBA_SETTINGS: docker
    KAFKA_BROKERS: kafka:9092
    REDIS_HOST: redis
  ...

Результат

После этих изменений всё стабилизировалось. Redis не переполняется благодаря relay и увеличенному лимиту памяти. Snuba-консьюмеры, если и падают, поднимаются сами. Разработчики видят issues так, как и должны.

Комментарии (0)