Как избежать потери сообщений при записи в базу при замедлении базы
Что проверяют
Понимаете ли вы, где именно теряются сообщения: в памяти процесса, при раннем подтверждении или при падении посреди записи. Следом почти всегда спрашивают, что будет с дублями, если запись повторится.
Как отвечать
Главная мысль: сообщение нельзя считать обработанным, пока оно не легло в базу. Дальше каркас:
- подтверждение после записи: консьюмер Kafka коммитит offset только после успешного
INSERT. Вkafka-goэто параFetchMessageиCommitMessages, а неReadMessage, который в группе коммитит сам. В RabbitMQ ту же роль играет ручнойack; - идемпотентная запись: раз доставка идёт «хотя бы один раз», повтор неизбежен. У каждого сообщения свой ключ, запись идёт через
INSERT ... ON CONFLICT (message_id) DO NOTHING; - ограниченный буфер и backpressure: между чтением и записью стоит канал фиксированного размера. Когда база тормозит, канал заполняется и чтение из брокера само останавливается: сообщения ждут в брокере, а не в памяти процесса;
- батчи: пачка через
pgx.BatchилиCopyFromснижает число обращений, и медленная база выдерживает тот же поток; - таймауты и повторы: каждый запрос с
context.WithTimeout, повтор с экспоненциальной задержкой и джиттером, после N попыток сообщение уходит в DLQ, а не в лог; - корректная остановка: по
SIGTERMсервис дописывает то, что уже взял, и только потом закрывается.
Если источник не брокер, а HTTP, честнее отвечать 503 или 429, чем принимать запрос и держать его в памяти. Отставание консьюмера (lag) выводите в метрики: это первый сигнал, что база не успевает.
Где спотыкаются
Предлагают копить сообщения в неограниченном буфере или запускать горутину на каждое. При падении процесса всё накопленное пропадает, а при долгом замедлении сервис съедает память и падает сам.
Коммитят offset до записи или оставляют автокоммит. Сбой между подтверждением и INSERT теряет сообщение молча.
Настраивают повторы, но забывают про идемпотентность. Потерь нет, зато появляются дубли, и это та же порча данных. Кэш в Redis как «буфер на время замедления» тоже не спасает: без специальной настройки это не надёжное хранилище.