Как сага паттерн управляет распределенными транзакциями между микросервисами
Что проверяют
Понимаете ли вы, что сага не даёт атомарности в смысле ACID, а заменяет откат компенсациями и живёт с промежуточными состояниями. Следом просят сравнить хореографию с оркестрацией и спрашивают, как гарантировать, что событие не потеряется.
Как отвечать
Сага это цепочка локальных транзакций, каждая в своём сервисе и своей базе. У каждого шага есть компенсация, которая отменяет результат по смыслу: заказ не удаляется, а получает статус «отменён», платёж не стирается, а возвращается. Если шаг упал, компенсации запускаются для уже выполненных шагов в обратном порядке.
Два способа координации:
- хореография: сервисы публикуют события в брокер (Kafka, RabbitMQ) и реагируют на чужие. Центра нет, но трудно понять, где сейчас процесс;
- оркестрация: координатор хранит состояние саги в базе как конечный автомат и шлёт команды шагам. Логика в одном месте, её проще тестировать и мониторить.
Что назвать для надёжности:
- transactional outbox: запись в базу и публикацию в брокер одной транзакцией не сделать, поэтому событие пишется в таблицу outbox той же транзакцией, а отдельный процесс его отправляет;
- идемпотентность: доставка «как минимум один раз», значит шаги и компенсации должны переносить повтор, обычно по ключу идемпотентности;
- изоляция: промежуточные состояния видны другим, отсюда статусы вроде
PENDINGи семантические блокировки.
В Python оркестрацию часто строят на Temporal (SDK temporalio): воркфлоу пишется обычным кодом, компенсации копятся в список и выполняются в except. Цепочки Celery сами по себе сагой не являются, состояние процесса и порядок компенсаций придётся вести самим.
Где спотыкаются
Говорят, что сага «откатывает транзакцию». Отката нет, есть новые транзакции, а некоторые действия не компенсируются вовсе, например отправленное письмо. Такие шаги ставят в конец.
Пишут в базу и в Kafka двумя отдельными вызовами. При падении между ними данные и события расходятся.
Компенсируют шаг, который сам упал. Если резервирование не прошло, откатывать нужно предыдущие шаги, а не его.