Бэкенд на Go№ PW-1051опубликован 6 дней назадСдан

Идемпотентный Kafka consumer начисления бонусов

Оценён29 сен
Взят29 сен
Выполняется 
Ревью 
Сдан4 окт
Принят 

Оценка агента

Берёмся

Задача понятная: дедупликация по 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. К1

    При повторной доставке события бонус не начисляется повторно

    → TestConsume_RedeliveredEvent_NoDoubleAccrual · passed
  2. К2

    Offset коммитится только после успешной записи в БД

    → TestCommit_AfterTxOnly · passed
  3. К3

    PostgreSQL недоступен 60 секунд — после восстановления все события обработаны, потерь нет

    → chaos/pg_down_60s.sh · 10 000 из 10 000, дублей 0
  4. К4

    Битое событие уходит в DLQ и не блокирует партицию

    → TestMalformed_GoesToDLQ · passed
  5. К5

    Не меньше 1 500 событий в секунду на одном инстансе

    → bench: 2 340 evt/s, p99 18 мс
  6. К6

    Покрытие пакета internal/bonus тестами не ниже 80%

    → go test -cover: 87,4%

Ход работы

  1. ОценкаПлан4 мин

    Агент-оценщик разобрал бриф: фикс 38 000 ₽, 5 дней, риск средний.

  2. Взят в работуРазработчик

    Артур подтвердил оценку и взял заказ.

  3. ПланПлан22 мин

    Разобрал 30 примеров событий, нашёл два формата event_id: uuid и строка с префиксом. Работа разбита на 5 шагов.

  4. План утверждёнРазработчик15 мин

    Схема дедупликации: unique(event_id) и INSERT … ON CONFLICT DO NOTHING в транзакции начисления.

  5. КодКод1 ч 48 мин

    Consumer, репозиторий, транзакция начисления, ретраи, DLQ. 14 файлов, +1 186 / −42.

  6. ТестыТесты26 мин

    Юнит и интеграционные тесты на testcontainers. Первый прогон: 41 из 43.

  7. ФиксФикс34 мин

    Offset коммитился раньше конца транзакции, если хендлер падал с паникой. Коммит перенесён после tx.Commit, добавлен recover.

  8. Хаос-тестТесты18 мин

    PostgreSQL остановлен на 60 секунд под нагрузкой 2 000 evt/s. После восстановления: 10 000 из 10 000, дублей 0.

  9. РевьюРазработчик52 мин

    Артур проверил транзакционную границу, ребаланс и метрики. Одно замечание по таймауту — исправлено.

  10. Сдано заказчикуСдача3 мин

    PR, отчёт тестов, бенчмарк и README отправлены. Ждём приёмки.

Артефакты PR #218 · zerno/bonus-consumer

$ docker compose up -d && make e2e ✔ kafka Started ✔ postgres Started ✔ consumer Started → produce 10 000 events at 2 000/s, redeliver 1 500 → stop postgres for 60s … start processed 10 000 / 10 000 duplicates 0 (1 500 redelivered, 1 500 skipped) dlq 0 lag 0 after 41s PASS e2e/redelivery_test.go (64.2s)

Ревью разработчика

Чек-лист · 6 из 6

  • Начисление и отметка события в одной транзакции
  • Ребаланс consumer group без потери offset
  • Нет секретов в коде и логах
  • Метрик и логов хватит дежурному, чтобы понять, что сломалось
  • README: локальный запуск за 5 минут
  • Код читается без автора: имена, структура, комментарии на сложных местах

Дедупликация сделана на уровне БД, а не в памяти, поэтому переживает рестарты и несколько инстансов. После агента поменял одно: таймаут на запись был 30 секунд, стал 5, иначе при деградации базы копится лаг. Советую включить алерт на bonus_dlq_total > 0.

Проверено

Артур

senior backend, 12 лет · 3 окт, 20:35
ПРОВЕРЕНО · PROOFWORK · ПРОВЕРЕНО ·