Почти каждый разговор о микросервисах рано или поздно упирается в один и тот же вопрос:
что делать с транзакциями, когда данные больше не живут в одном месте?
В монолите этот вопрос даже не формулируется. Есть база данных, есть транзакция, есть чёткое правило: либо всё зафиксировалось, либо ничего не произошло. Разработчик пишет бизнес-логику, а инфраструктура аккуратно берёт на себя гарантию целостности данных.
С микросервисами эта договорённость ломается. Не потому что «кто-то сделал плохо», а потому что сама архитектура перестаёт быть синхронной и локальной.
что делать с транзакциями, когда данные больше не живут в одном месте?
В монолите этот вопрос даже не формулируется. Есть база данных, есть транзакция, есть чёткое правило: либо всё зафиксировалось, либо ничего не произошло. Разработчик пишет бизнес-логику, а инфраструктура аккуратно берёт на себя гарантию целостности данных.
С микросервисами эта договорённость ломается. Не потому что «кто-то сделал плохо», а потому что сама архитектура перестаёт быть синхронной и локальной.
Когда привычная транзакция перестаёт работать
Представим максимально приземлённый сценарий. Пользователь оформляет заказ:
• Заказ создаётся в одном сервисе,
• Оплата проходит через другой,
• Склад резервирует товар в третьем.
В монолите всё это могло бы быть одной транзакцией.
В микросервисах каждый шаг — отдельный сервис, отдельная база, отдельная зона ответственности.
И вот здесь появляется ключевой момент: между этими сервисами лежит сеть. А сеть — это задержки, таймауты и отказы.
Даже если все сервисы написаны на одном языке и используют одну СУБД, они больше не могут полагаться на локальные гарантии ACID. Любой шаг может выполниться, а следующий — нет. И система должна уметь с этим жить.
• Заказ создаётся в одном сервисе,
• Оплата проходит через другой,
• Склад резервирует товар в третьем.
В монолите всё это могло бы быть одной транзакцией.
В микросервисах каждый шаг — отдельный сервис, отдельная база, отдельная зона ответственности.
И вот здесь появляется ключевой момент: между этими сервисами лежит сеть. А сеть — это задержки, таймауты и отказы.
Даже если все сервисы написаны на одном языке и используют одну СУБД, они больше не могут полагаться на локальные гарантии ACID. Любой шаг может выполниться, а следующий — нет. И система должна уметь с этим жить.
2PC: попытка сохранить старый мир
Two-Phase Commit появился как логичное продолжение идеи «давайте просто расширим транзакцию на несколько ресурсов».
По сути, это попытка сказать распределённой системе: «веди себя как одна база данных».
В Java-мире этот подход знаком многим по JTA и XA-транзакциям. Есть координатор, есть участники, и есть два этапа: сначала все обещают, что готовы зафиксироваться, потом либо фиксируются, либо откатываются.
На уровне идеи это выглядит аккуратно и строго. Но ровно до того момента, пока система не начинает жить в реальности.
По сути, это попытка сказать распределённой системе: «веди себя как одна база данных».
В Java-мире этот подход знаком многим по JTA и XA-транзакциям. Есть координатор, есть участники, и есть два этапа: сначала все обещают, что готовы зафиксироваться, потом либо фиксируются, либо откатываются.
На уровне идеи это выглядит аккуратно и строго. Но ровно до того момента, пока система не начинает жить в реальности.
Почему 2PC плохо чувствует себя в микросервисах
Проблема не в самом протоколе, а в том, какие допущения он делает.
2PC предполагает, что:
• Участники могут долго держать ресурсы заблокированными,
• Координатор всегда доступен,
• Сбои — редкое исключение, а не норма.
В микросервисной системе всё наоборот. Сервисы должны быть независимыми, быстро масштабироваться и спокойно переживать падения соседей.
Когда один сервис держит блокировку, ожидая решения координатора, он начинает мешать остальным. Когда координатор недоступен, участники оказываются в подвешенном состоянии. А когда таких транзакций становится много, система начинает деградировать под нагрузкой.
Поэтому на практике 2PC в микросервисах либо избегают сознательно, либо используют точечно и с полным пониманием рисков.
2PC предполагает, что:
• Участники могут долго держать ресурсы заблокированными,
• Координатор всегда доступен,
• Сбои — редкое исключение, а не норма.
В микросервисной системе всё наоборот. Сервисы должны быть независимыми, быстро масштабироваться и спокойно переживать падения соседей.
Когда один сервис держит блокировку, ожидая решения координатора, он начинает мешать остальным. Когда координатор недоступен, участники оказываются в подвешенном состоянии. А когда таких транзакций становится много, система начинает деградировать под нагрузкой.
Поэтому на практике 2PC в микросервисах либо избегают сознательно, либо используют точечно и с полным пониманием рисков.
Saga: отказ от иллюзии атомарности
Saga начинается с неприятного, но честного признания:
строгая атомарность в распределённой системе — роскошь.
Вместо того чтобы пытаться откатить всё «как будто ничего не было», Saga предлагает другой подход: каждое действие фиксируется сразу, а если что-то пошло не так — система выполняет компенсирующие шаги.
Важно: компенсация — это не rollback в привычном смысле. Это отдельное бизнес-действие.
строгая атомарность в распределённой системе — роскошь.
Вместо того чтобы пытаться откатить всё «как будто ничего не было», Saga предлагает другой подход: каждое действие фиксируется сразу, а если что-то пошло не так — система выполняет компенсирующие шаги.
Важно: компенсация — это не rollback в привычном смысле. Это отдельное бизнес-действие.
Как это выглядит в реальной системе
Вернёмся к заказу.
Сервис заказов создал заказ и зафиксировал его в базе. Это факт, который уже произошёл. Далее сервис оплаты попытался списать деньги. Если списание прошло успешно — отлично, процесс продолжается. Если нет — система не «отматывает время назад», а, например, переводит заказ в статус CANCELLED.
Для пользователя результат выглядит логично, даже если внутри система прошла через несколько шагов и ошибок.
Именно здесь Saga начинает работать не как технический трюк, а как часть бизнес-модели.
Сервис заказов создал заказ и зафиксировал его в базе. Это факт, который уже произошёл. Далее сервис оплаты попытался списать деньги. Если списание прошло успешно — отлично, процесс продолжается. Если нет — система не «отматывает время назад», а, например, переводит заказ в статус CANCELLED.
Для пользователя результат выглядит логично, даже если внутри система прошла через несколько шагов и ошибок.
Именно здесь Saga начинает работать не как технический трюк, а как часть бизнес-модели.
Хореография и оркестрация — не про стиль, а про контроль
В одних системах Saga строится вокруг событий. Сервисы реагируют друг на друга, публикуют события, и процесс постепенно продвигается вперёд. Это даёт свободу и слабую связность, но усложняет понимание общего сценария.
В других — появляется отдельный компонент, который знает весь процесс целиком и управляет шагами. Такой подход проще читать и сопровождать, но он требует аккуратного отношения к отказам и масштабированию самого оркестратора.
Выбор между ними — это не вопрос вкуса, а вопрос того, что для системы важнее: автономность или управляемость.
В других — появляется отдельный компонент, который знает весь процесс целиком и управляет шагами. Такой подход проще читать и сопровождать, но он требует аккуратного отношения к отказам и масштабированию самого оркестратора.
Выбор между ними — это не вопрос вкуса, а вопрос того, что для системы важнее: автономность или управляемость.
Цена, которую платят за Saga
Saga редко выглядит сложной на диаграмме, но почти всегда становится сложной в коде.
Приходится заранее проектировать идемпотентные операции, учитывать повторы сообщений, жить с eventual consistency и инвестировать в логирование и трассировку — без этого отладка распределённого процесса быстро превращается в угадывание.
Отдельная сложность появляется, когда Saga выходит за пределы собственной системы. Взаимодействие с внешними сервисами, у которых нет компенсационных действий, ломает идеальную модель. В таких случаях откат становится невозможен в прямом смысле слова, и приходится использовать дополнительные, иногда комбинированные подходы: от отложенных корректировок состояния до ручных или полуавтоматических процессов восстановления.
Именно здесь становится понятно, что Saga — это не просто технический паттерн, а архитектурное решение, которое требует учитывать границы контроля системы и реальное поведение внешних зависимостей.
Приходится заранее проектировать идемпотентные операции, учитывать повторы сообщений, жить с eventual consistency и инвестировать в логирование и трассировку — без этого отладка распределённого процесса быстро превращается в угадывание.
Отдельная сложность появляется, когда Saga выходит за пределы собственной системы. Взаимодействие с внешними сервисами, у которых нет компенсационных действий, ломает идеальную модель. В таких случаях откат становится невозможен в прямом смысле слова, и приходится использовать дополнительные, иногда комбинированные подходы: от отложенных корректировок состояния до ручных или полуавтоматических процессов восстановления.
Именно здесь становится понятно, что Saga — это не просто технический паттерн, а архитектурное решение, которое требует учитывать границы контроля системы и реальное поведение внешних зависимостей.
Что в итоге выбирают на практике
Если смотреть на реальные микросервисные системы, особенно высоконагруженные, становится очевидно: Saga — это не компромисс, а осознанный выбор для сложных и длительных бизнес-процессов. Однако на практике это далеко не единственный вариант работы с распределёнными изменениями данных.
Во многих системах используются более простые подходы, например, Transactional Outbox. Он не решает проблему распределённых транзакций напрямую, но позволяет безопасно публиковать события из локальной транзакции, сохраняя eventual consistency между сервисами. Для большого числа сценариев этого оказывается достаточно.
Более того, на ранних этапах жизни проекта, когда архитектура ещё не сформировалась, требования нестабильны, а сам продукт находится в стадии proof-of-concept, именно такие простые решения часто оказываются оптимальными. Они снижают сложность системы, позволяют быстрее проверять гипотезы и откладывают внедрение полноценной Saga до момента, когда система действительно в ней нуждается.
В этом смысле зрелость архитектуры — не про количество паттернов, а про умение выбирать минимально достаточный инструмент под текущую стадию развития системы.
Во многих системах используются более простые подходы, например, Transactional Outbox. Он не решает проблему распределённых транзакций напрямую, но позволяет безопасно публиковать события из локальной транзакции, сохраняя eventual consistency между сервисами. Для большого числа сценариев этого оказывается достаточно.
Более того, на ранних этапах жизни проекта, когда архитектура ещё не сформировалась, требования нестабильны, а сам продукт находится в стадии proof-of-concept, именно такие простые решения часто оказываются оптимальными. Они снижают сложность системы, позволяют быстрее проверять гипотезы и откладывают внедрение полноценной Saga до момента, когда система действительно в ней нуждается.
В этом смысле зрелость архитектуры — не про количество паттернов, а про умение выбирать минимально достаточный инструмент под текущую стадию развития системы.
Распределённые транзакции — это всегда история про границы ответственности.
Микросервисы заставляют разработчика думать не только о коде, но и о том, что происходит, когда что-то обязательно пойдёт не так.
И, пожалуй, именно это отличает зрелую архитектуру от просто набора сервисов.
Хотите узнать больше? Изучите другие статьи из раздела:
Микросервисы заставляют разработчика думать не только о коде, но и о том, что происходит, когда что-то обязательно пойдёт не так.
И, пожалуй, именно это отличает зрелую архитектуру от просто набора сервисов.
Хотите узнать больше? Изучите другие статьи из раздела: