Идемпотентный Kafka consumer начисления бонусов
Оценка агента
Берёмся
Задача понятная: дедупликация по event_id в той же транзакции, что и начисление. Риск — старые события в двух форматах, на разбор заложен день.
Сервис на Go читает события покупок из Kafka и начисляет бонусы в PostgreSQL. Сейчас при повторной доставке бонус иногда начисляется дважды. Нужен consumer, который даёт одинаковый результат, сколько бы раз ни пришло событие.
Входит
- Consumer group на kafka-go с ручным коммитом offset
- Таблица processed_events и начисление в одной транзакции
- Ретраи с паузой и DLQ-топик для битых событий
- Метрики Prometheus: lag, обработано, дубли, ошибки
- docker-compose с Kafka и PostgreSQL для локального запуска
Не входит
- Миграция исторических начислений
- Алерты в вашей Grafana
- Изменения в сервисе-продюсере
Что нужно от заказчика
- 20–30 реальных событий без персональных данных
- Схема таблицы bonuses
- Доступ к тестовому топику
Критерии приёмки 6 из 6 выполнено
- К1
При повторной доставке события бонус не начисляется повторно
→ TestConsume_RedeliveredEvent_NoDoubleAccrual · passed - К2
Offset коммитится только после успешной записи в БД
→ TestCommit_AfterTxOnly · passed - К3
PostgreSQL недоступен 60 секунд — после восстановления все события обработаны, потерь нет
→ chaos/pg_down_60s.sh · 10 000 из 10 000, дублей 0 - К4
Битое событие уходит в DLQ и не блокирует партицию
→ TestMalformed_GoesToDLQ · passed - К5
Не меньше 1 500 событий в секунду на одном инстансе
→ bench: 2 340 evt/s, p99 18 мс - К6
Покрытие пакета internal/bonus тестами не ниже 80%
→ go test -cover: 87,4%
Ход работы
- ОценкаПлан4 мин
Агент-оценщик разобрал бриф: фикс 38 000 ₽, 5 дней, риск средний.
- Взят в работуРазработчик
Артур подтвердил оценку и взял заказ.
- ПланПлан22 мин
Разобрал 30 примеров событий, нашёл два формата event_id: uuid и строка с префиксом. Работа разбита на 5 шагов.
- План утверждёнРазработчик15 мин
Схема дедупликации: unique(event_id) и INSERT … ON CONFLICT DO NOTHING в транзакции начисления.
- КодКод1 ч 48 мин
Consumer, репозиторий, транзакция начисления, ретраи, DLQ. 14 файлов, +1 186 / −42.
- ТестыТесты26 мин
Юнит и интеграционные тесты на testcontainers. Первый прогон: 41 из 43.
- ФиксФикс34 мин
Offset коммитился раньше конца транзакции, если хендлер падал с паникой. Коммит перенесён после tx.Commit, добавлен recover.
- Хаос-тестТесты18 мин
PostgreSQL остановлен на 60 секунд под нагрузкой 2 000 evt/s. После восстановления: 10 000 из 10 000, дублей 0.
- РевьюРазработчик52 мин
Артур проверил транзакционную границу, ребаланс и метрики. Одно замечание по таймауту — исправлено.
- Сдано заказчикуСдача3 мин
PR, отчёт тестов, бенчмарк и README отправлены. Ждём приёмки.
Артефакты PR #218 · zerno/bonus-consumer
Ревью разработчика
Чек-лист · 6 из 6
- Начисление и отметка события в одной транзакции
- Ребаланс consumer group без потери offset
- Нет секретов в коде и логах
- Метрик и логов хватит дежурному, чтобы понять, что сломалось
- README: локальный запуск за 5 минут
- Код читается без автора: имена, структура, комментарии на сложных местах
Дедупликация сделана на уровне БД, а не в памяти, поэтому переживает рестарты и несколько инстансов. После агента поменял одно: таймаут на запись был 30 секунд, стал 5, иначе при деградации базы копится лаг. Советую включить алерт на bonus_dlq_total > 0.
Проверено
Артур
senior backend, 12 лет · 3 окт, 20:35