Как стать автором
Обновить

Распределённые транзакции в микросервисах: от SAGA до Two‑Phase Commit

Время на прочтение29 мин
Количество просмотров7.1K
Всего голосов 20: ↑20 и ↓0+22
Комментарии10

Комментарии 10

Я добавил:

Если бизнес процесс допускает возможность обратных действий, то сага всегда предпочтительней. Допустим, на обоих серверах действие успешно выполняется в 99% случаев и в 1% сетевой сбой.

1. Что происходит при 2PC

• Coordinator посылает prepare двум ресурсам.

• Оба лочат данные и отвечают «OK».

• Coordinator шлёт commit.

• Если между prepare и commit происходит сетевой разрыв (1 %), один из участников может остаться в «in-doubt» состоянии, пока таймаут/админ не решит, что делать.

• Данные заблокированы, масштабирование страдает.

Вероятность зависшего состояния ≈ P(prepare-OK) × P(network failure) = 0.99² × 0.01 ≃ 0.0098 (≈ 1 %). При высокой нагрузке 1 % «зависших» транзакций может быстро превратиться в лавину блокировок.

2. Что происходит при саге

• Сервис A делает action A.

• Если всё ок, публикует событие / вызывает сервис B.

• Сервис B делает action B.

• При сетевом сбое между ними сервис A через ретрай или таймаут решает «не пришёл ответ» и запускает компенсирующее действие A-.

• Система возвращается в консистентное, пусть и «бизнес-откатное», состояние; блокировок нет.

Цена вопроса: нужно иметь чётко определённую компенсацию и уметь жить с временной Inconsistent view (например, пользователь увидел списание денег, а товар не отгрузился, пока сага не откатила).

3. Математика надёжности (упрощённая)

Допустим, каждая операция (action или compensate) также успешна в 99 %.

2PC:

Failure = 1 % (сетевой) + ε (сбой БД). При сбое требуются ручные процедуры.

Saga:

• Шанс, что основной flow прошёл: 0.99 × 0.99 = 0.9801

• Шанс, что потребовался откат: 1 %, но и откат успешен в 0.99 → 0.0099

• Шанс, что и откат упал (double failure): 0.0001 (0.01 × 0.01)

То есть вместо 1 % «подвисших» транзакций мы получаем 0.01 % «неразруленных» провалов, а блокировок нет вовсе

И ещё добавлю

Консистентность в распределенных системах имеет вероятностный характер всегда. Чтобы консистентность стала 100% вероятной придется ждать бесконечное время

По возможности избегайте глобальных транзакций

Значит у вас половина агрегата в одной базе, половина в другой. Если это действительно «агрегат»

Агрегат (по DDD) — это совокупность сущностей, чьи инварианты должны выполняться в момент завершения операции. Если правило допускает eventual consistency («исправим за минуту, пользователю не критично») — это ещё не агрегат; можно остаться на саге/событиях. Если правило жёсткое «ни одна из сторон (пользователь, другие сервисы, отчёты) не должна увидеть систему в промежуточном состоянии», то нужна синхронная защита, и это признак смещения границы.

Опции лечения (от дешёвой к дорогой)

  • Сага с оркестратором + lock (такая же медленная как и 2PC, но проще а реализации)

  • Репликация + локальный агрегат

  • Слияние bounded contexts

  • Выделить новый агрегат в отдельную БД

Как мигрировать без даунтайма: Dual-write, shadow-read, feature flag.

Ключевая проблема саг - неизбежные баги в логике компенсаций. Если даже их не будет в первой версии то они появятся по мере внесения изменений в бизнес-логику. Люди просто не справляются с тем, чтобы учитывать все возможные нюансы (всё, что могло измениться во всей системе между действием и его компенсацией, и как это повлияло на то, как именно нужно корректно компенсировать) и корректно поддерживать (просчитать какие изменения в логике компенсации других шагов может потребовать изменение бизнес-логики в ином месте проекта) такую сложную логику.

Это примерно как с ручным управлением памятью: в теории ничего сложного сделать malloc а потом free нет, но на практике писать такой код без ошибок не получается почти ни у кого. И если в случае ручного управления памятью заметить такие ошибки ещё как-то возможно (по утечкам или крешам при повторном освобождении), то в случае саг даже заметить баг в логике компенсаций крайне сложно, что создаёт иллюзию "простоты" саг и способствует широкому внедрению саг там, где без них можно было бы обойтись.

Согласен. Меня всегда передергивает, когда я слышу что это надёжное решение. Мы типа пишем свою "микро СУБД" и наивно предполагаем, что сделаем это идеально.

По возможности избегайте глобальных транзакций - вот прямо в точку. Современные БД шустры. Можно разделять чтение и запись, шардировать, кэшировать данные на стороне сервисов, переносить аналитическую нагрузку, чистить старые транзакции и ещё 1001 способ. Не надо лишний раз делить то, что отлично работает вместе. Исключение - внешние сервисы. Классический пример - оплата заказа.

Я бы ещё про качество данных добавил. Как только есть распрелеленная транзакция или простой пайплайн в одну сторону сразу подумайте как будете сверять данные с учётом того, что в моменте они не консистентны.

Outbox хорош, но гарантированно хорош только на уровне тригера БД. Это гарантирует генерации события по каждому изменению нужной сущности и не потребует переписывания всех интеграцмй.

Не надо лишний раз делить то, что отлично работает вместе

В Озон не попробовал устроиться? Там микросервисный взрыв, тысячи их

Как я понял, они просто не успели найти на рынке сотню опытных программистов, которые умеют изолировать свою работу. Поэтому набрали тысячу хомячков.

Ну и ошибку/ скрытые мотивы архитектора держим в уме

А на самом деле - все это вилами по воде писано. Упасть может и откат очередного отката. И все равно дело будет в теории вероятности. При этом не все системы требуют лютой надежности и не нужно усложнять.

Так может проще подзабить на это дело? Максимально стремиться решать такие вещи в рамках одной базы, а если даже пришлось разнести - просто помечать записи/сущности после определенного тайм-аута как «несогласованно» и писать логи со сквозным ид, чтобы было понятно кто в цепочке сбойнул.

И попытка записать «несогласованно» умрёт от сбоя сети.

Точно 😅

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации