Kafka как база данных: удобно до первой аварии

public

Автор: Дмитрий Емельянов (Altenar) · Видео с мероприятий {speach!}


В 39-минутном докладе Дмитрий Емельянов, тимлид группы разработки в Altenar, рассказывает об аварии, которая началась почти как анекдот: команда настраивала новый бэкап Kafka — и именно в этот момент потеряла Kafka. Но самое интересное случилось не во время аварии, а после, когда кластер уже удалось восстановить.

В системе Altenar некоторые топики были не просто транспортом между микросервисами, а единственным источником истины. При запуске сервисы перечитывали их, собирали состояние в памяти и затем продолжали работать с ним. Kafka реплицировалась между брокерами, а раз в сутки её файлы дополнительно копировали с одного диска на другой. Восстановление этих копий не проверяли: для этого пришлось бы останавливать stage, мешать работе QA и откладывать бизнес-фичи.

Когда Kafka исчезла, после восстановления из свежего бэкапа в ней не хватало части партиций. Копия двухдневной давности поднялась успешно: топики на месте, сервисы запускаются, объём данных выглядит правдоподобно. Казалось бы, инцидент закончен. На самом деле система только что раскололась во времени: база осталась в настоящем, а Kafka вернулась на два дня назад.

Кому смотреть: разработчикам и архитекторам, у которых Kafka хранит состояние системы, а рядом существуют базы, кэши или другие независимо восстанавливаемые хранилища.

Из этого можно взять в работу: проверять не только то, поднимается ли Kafka из копии, но и то, сможет ли после отката продолжить работу вся система. Особенно важно заранее понимать, как устранять рассинхронизацию между хранилищами, восстановленными на разные моменты времени.


Рассинхронизация обнаружилась на опубликованных матчах. Управляющий сервис держал состояние в памяти и назначал новому матчу ID, увеличивая последнее значение из Kafka. После отката последним снова оказался матч №1000. Следующий получил №1001 — но в сохранившейся базе такой ID уже существовал и относился к другому матчу, со своим временем начала, настройками и связанными данными.

Это неприятнее обычной потери сообщений. Каждый компонент по отдельности выглядел исправным: Kafka восстановлена, база не падала, сервисы запускаются. Проблема проявлялась только тогда, когда они снова начинали работать вместе. Команде пришлось найти последний матч, сохранившийся в восстановленном топике, и каскадно удалить из базы более новые записи. Обсуждение восстановления заняло около 400 сообщений в одном Slack-треде, не считая встреч.

После инцидента команда начала готовить запасной кластер через MirrorMaker, который умеет реплицировать сообщения и сохранять offsets. Здесь важна оговорка из вопросов после доклада: на тот момент это решение ещё не внедрили и не протестировали. Поэтому временной страховкой стал сервис, сохраняющий содержимое важных compacted-топиков в БД.

Но главное исправление оказалось архитектурным. Состояние матчей перенесли в базу, идентификаторы стали выдавать через sequence в БД, а Kafka оставили для распространения изменений между сервисами. Данные при этом продолжают храниться в compacted-топике: базу можно восстановить из Kafka, а Kafka — из базы, при условии что в обеих копиях есть все необходимые поля.

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