transactional outboxУ нас сейчас так 🕵️♂️:
async with db.transaction(): # BEGIN
await save_order()
await kafka.send(event) # потенциально долгий запрос
# COMMIT
Пока
Kafka думает, ручка держит соединение с БД. Трафик растёт — таких соединений становится больше, latency ручки летит вверх 😨Надо разделить транзакцию в БД и отправку евента. Предлагаю просто делать отправку во внешнем процессе, тогда наша ручка будет работать стабильно быстро. С
outbox ручка делает только это:async with db.transaction(): # BEGIN
await save_order()
event = Event(...)
await outbox.add(event) # новая сущность в бд с флагом status=NEW например
# COMMIT
А отдельный
worker забирает событие и отправляет его в Kafka:while True:
async with db.transaction(): # BEGIN
event = await outbox.next_for_update()
await kafka.send(event)
await event.mark_sent()
# COMMIT
Да, одна транзакция всё ещё может долго держать соединение.
Но теперь это управляемо: у
worker свой пул и фиксированное число воркеров. Он не плодит коннекты вместе с запросами в API и не раздувает latency ручек 😱А премию дают за низкий latency 💰
Цена надёжности:
🚬 Требуется отдельный
worker + появляется лаг на отправку евента (это надо согласовать) 😕 если Kafka приняла событие, а
COMMIT упал, оно уйдёт повторно. Значит, consumer должен быть идемпотентным, а это уже другая историяКароч, это топ паттерн для решения таких проблем. Он простой, понятный, почти всегда применим. На собесе достаем
