Кейс: два инстанса сервиса отправляли разные email одному пользователю
Сервис уведомлений слушал очередь событий и по каждому событию отправлял письмо. При скейлинге до трёх подов пользователи начали получать письма с рассинхроном — то устаревший статус заказа, то дублирующее письмо через минуту после первого.
Причина была не в очереди и не в дублировании сообщений — consumer настроен идемпотентно, дублей на уровне доставки не было. Проблема была в том, что каждый под держал собственный локальный кэш шаблонов писем с TTL в пять минут, и инвалидация кэша шла по локальному таймеру, а не по событию изменения шаблона в админке. Когда маркетинг обновлял шаблон письма, один под подхватывал новую версию сразу при следующем чтении из БД мимо кэша, а два других продолжали слать старую версию до истечения TTL.
Вторая часть проблемы: сам факт отправки письма фиксировался в локальной in-memory структуре для защиты от повторной отправки в рамках процесса, а не в общем хранилище. Под перезапускался — защита от повтора терялась, и следующий под мог отправить то же письмо повторно, если событие ещё оставалось в очереди дольше, чем жил под.
Что спросят следом на собесе: как избежать инвалидации по TTL для конфигурационных данных вроде шаблонов. Ответ — Pub/Sub на изменение конфигурации: при обновлении шаблона в БД публикуется событие invalidate в общий канал, все поды сбрасывают локальный кэш синхронно, а не ждут истечения TTL. Отдельно спросят про идемпотентность самой отправки — держать отметку об отправленном письме нужно в общем хранилище с TTL чуть больше времени жизни события в очереди, не в памяти процесса.
Локальный кэш на каждом поде без общего механизма инвалидации — это не только рассинхрон данных, это ещё и рассинхрон между самими подами, который выглядит как случайный баг.
Тренажёр: 600 вопросов, мок с таймером, план повторов
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки