Перейти к содержимому

Распределённые транзакции и Saga Pattern

Зачем знать: В мире микросервисов каждая команда имеет свою БД. Один бизнес-процесс (заказ, перевод денег, бронирование) часто затрагивает несколько сервисов. Локальной ACID-транзакции уже мало — нужны распределённые механизмы координации. На уровне Middle 2 ты обязан понимать ограничения 2PC, идиомы Saga, eventual consistency, и уметь спроектировать процесс заказа, где failures возможны на любом шаге.

  1. Концепция: зачем distributed transactions
  2. Под капотом: 2PC, TCC, Saga
  3. Gotchas
  4. Production-практики
  5. Вопросы для собеседования
  6. Practice
  7. Источники

Монолит → одна БД → одна транзакция → ACID — всё прекрасно.

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = A;
UPDATE accounts SET balance = balance + 100 WHERE id = B;
COMMIT;

Микросервисы → разные БД (или одна, но логически разделённые домены).

[Order Service] → [Payment Service] → [Inventory Service] → [Notification Service]
orders_db payments_db inventory_db notifications_db

Если на 3-м шаге упало — что делать с уже созданным заказом и списанным платежом?

ACID (классические транзакции):

  • Atomicity — всё или ничего.
  • Consistency — данные не нарушают invariants.
  • Isolation — параллельные транзакции не мешают друг другу.
  • Durability — после COMMIT данные сохранены.

BASE (распределённые системы):

  • Basically Available — система остаётся доступной (предпочтение availability над consistency).
  • Soft state — состояние может меняться даже без новых вводов (репликация).
  • Eventual consistency — рано или поздно все узлы сойдутся к одному состоянию.

CAP теорема (Brewer):

  • Consistency
  • Availability
  • Partition tolerance

Можно выбрать только 2 из 3 в случае network partition. В реальности P неизбежен → выбор между CP и AP.

  • CP систем: MongoDB (с majority writes), HBase, Zookeeper, etcd, CockroachDB.
  • AP систем: Cassandra, DynamoDB (в default mode), CouchDB.
  • Money transfers (банки): нельзя списать и не зачислить.
  • Order processing (e-commerce): зарезервировать → оплатить → отправить.
  • Inventory + Reservation.

Когда НЕ нужны:

  • Если можно сложить эти данные в одну БД — лучше так.
  • Если операция идемпотентна и можно retry — eventual consistency через очереди.
  • Если можно сделать одну “владеющую” сервис, а остальные — read-only consumers.
Strong consistency ←————————————————————————————→ Eventual consistency
2PC / XA → 3PC → TCC → Saga (orch.) → Saga (chor.) → Pure events
[blocking] [less] [comp.] [explicit] [implicit] [no compensation]

Идея: координатор (transaction manager) проводит транзакцию через 2 фазы.

Coordinator Participant A Participant B
│ │ │
│── Prepare ────────────────────────►│ │
│ │ (lock, write WAL,
│ │ return YES) │
│◄────────── YES ───────────────────── │
│── Prepare ─────────────────────────────────────────►│
│ │ (YES)
│◄────────── YES ─────────────────────────────────────│
│ │ │
│── Commit ─────────────────────────►│ │
│ │ (apply, release locks)
│── Commit ─────────────────────────────────────────►│
│ │

Фаза 1 (Prepare):

  • Каждый участник проверяет, может ли он commit.
  • Если YES — записывает в durable log “готов к commit” и блокирует ресурсы.
  • Если NO — голосует против.

Фаза 2 (Commit/Abort):

  • Если все YES → координатор шлёт COMMIT.
  • Если хоть один NO → координатор шлёт ABORT.
  • Участники применяют решение и освобождают locks.

XA standard (X/Open) — формализация 2PC. Поддерживается Oracle, PostgreSQL (prepared transactions), MySQL (XA transactions).

-- PostgreSQL XA пример
BEGIN;
-- ... операции
PREPARE TRANSACTION 'tx_123';
-- координатор где-то решает
COMMIT PREPARED 'tx_123';
-- или
ROLLBACK PREPARED 'tx_123';

Проблема 1: Coordinator failure

  • Если координатор упал после Prepare, но до Commit — участники зависли с locks.
  • Это blocking protocol — участники ждут координатора.
  • Для recovery нужен durable coordinator log.

Проблема 2: Performance

  • 2 round-trip (Prepare + Commit) к каждому участнику.
  • Locks держатся долго.
  • Не масштабируется (10+ участников — медленно и хрупко).

Где используется в 2026:

  • Внутри транзакционных систем (Oracle RAC, single-DB clustering).
  • В legacy enterprise (JTA в Java EE).
  • В микросервисах — почти не используется из-за blocking и tight coupling.

Идея: добавить дополнительную фазу PreCommit, чтобы избежать blocking.

Phase 1: CanCommit? (как Prepare в 2PC, но без блокировки)
Phase 2: PreCommit (все согласились — все знают, что будет commit)
Phase 3: DoCommit (применить)

Преимущество: если координатор упал в Phase 3, участники видят PreCommit и могут сами завершить.

Недостатки:

  • Дополнительный round-trip.
  • Не работает корректно при network partitions (Skeen’s theorem).
  • Реально почти не используется.

Идея: каждый сервис предоставляет 3 операции:

  • Try — резервирует ресурс, не финализирует.
  • Confirm — финализирует.
  • Cancel — отменяет резерв.
[Coordinator]
├─► Inventory.Try(reserve 5 items) ── success
├─► Payment.Try(hold $100) ── success
├─► Inventory.Confirm() (или Cancel при failure)
├─► Payment.Confirm()

Сравнение с 2PC:

  • Не требует XA — обычные HTTP/gRPC вызовы.
  • Бизнес-логика владеет Try/Confirm/Cancel — больше контроля.
  • Каждый сервис реализует idempotent Confirm/Cancel.

Примеры:

  • Inventory.Try: создать “reservation” record (TTL 10 min).
  • Inventory.Confirm: снять с stock.
  • Inventory.Cancel: удалить reservation.

Используется в финтехе (Alipay, Bank of China).

Идея (Garcia-Molina, Salem, 1987): длинная транзакция как последовательность локальных транзакций + компенсирующие транзакции для отмены.

Saga = [T1, T2, T3, T4]
Compensations = [C1, C2, C3, C4]
Если T3 упал:
C2; C1; ← компенсации в обратном порядке
Если T4 упал:
C3; C2; C1;

Каждое Ti — локальная ACID-транзакция в своём сервисе. Каждое Ci — обратное действие (refund вместо charge).

Пример: Order processing

T1: Order Service — создать заказ (status=pending)
T2: Inventory Service — зарезервировать товары
T3: Payment Service — списать деньги
T4: Shipping Service — создать shipment
T5: Notification Service — отправить email
C5: Notification — (nothing, email не отменить)
C4: Shipping — отменить shipment
C3: Payment — refund
C2: Inventory — освободить резерв
C1: Order — установить status=cancelled

Центральный сервис (orchestrator) дирижирует всем процессом.

┌─────────────────────────┐
│ Saga Orchestrator │
└────────────┬────────────┘
┌─────────────────┼──────────────────┐
▼ ▼ ▼
[Inventory] [Payment] [Shipping]

Плюсы:

  • Явная логика, легко понять.
  • Централизованное логирование и monitoring.
  • Легче тестировать.

Минусы:

  • Точка отказа (нужен HA orchestrator).
  • Tighter coupling — orchestrator знает все сервисы.

Tools (2026):

  • Temporal (бывший Cadence от Uber) — самый популярный workflow engine.
  • Camunda Zeebe / Camunda 8 — BPMN-based.
  • Conductor (Netflix).
  • Airflow — для batch workflows (не realtime).

Сервисы шлют events, каждый реагирует на нужные.

[Order Service] —OrderCreated→ [Kafka]
[Inventory] reads OrderCreated → reserves → InventoryReserved → [Kafka]
[Payment] reads InventoryReserved → charges → PaymentCompleted → [Kafka]
[Shipping] reads PaymentCompleted → ships → ShipmentCreated → [Kafka]

Плюсы:

  • Loose coupling.
  • Сервисы независимы.
  • Хорошо масштабируется.

Минусы:

  • Логика размазана по сервисам — трудно отследить процесс.
  • Нет single source of truth о состоянии saga.
  • Debugging сложнее (нужен distributed tracing).

Tools:

  • Kafka / NATS / RabbitMQ как event bus.
  • Schema registry для events.

Temporal — самый популярный workflow engine с Go SDK.

// Workflow definition
func OrderWorkflow(ctx workflow.Context, order Order) error {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 30 * time.Second,
RetryPolicy: &temporal.RetryPolicy{
InitialInterval: time.Second,
BackoffCoefficient: 2.0,
MaximumAttempts: 3,
},
})
// Step 1: Reserve inventory
var reservationID string
if err := workflow.ExecuteActivity(ctx, ReserveInventory, order).Get(ctx, &reservationID); err != nil {
return err // Никаких компенсаций — ничего не сделано
}
// Step 2: Charge payment
var paymentID string
if err := workflow.ExecuteActivity(ctx, ChargePayment, order).Get(ctx, &paymentID); err != nil {
// Compensate
workflow.ExecuteActivity(ctx, ReleaseInventory, reservationID).Get(ctx, nil)
return err
}
// Step 3: Create shipment
var shipmentID string
if err := workflow.ExecuteActivity(ctx, CreateShipment, order).Get(ctx, &shipmentID); err != nil {
// Compensate in reverse
workflow.ExecuteActivity(ctx, RefundPayment, paymentID).Get(ctx, nil)
workflow.ExecuteActivity(ctx, ReleaseInventory, reservationID).Get(ctx, nil)
return err
}
// Step 4: Notify (best-effort)
workflow.ExecuteActivity(ctx, SendOrderConfirmation, order).Get(ctx, nil)
return nil
}
// Activity
func ReserveInventory(ctx context.Context, order Order) (string, error) {
// Locally transactional call to inventory service
resp, err := inventoryClient.Reserve(ctx, &inventory.ReserveRequest{
OrderID: order.ID,
Items: order.Items,
})
if err != nil {
return "", err
}
return resp.ReservationID, nil
}

Что Temporal даёт:

  • Durability: workflow state хранится в Temporal DB. Если worker упал — другой подхватит и продолжит.
  • Retries: автоматические с backoff.
  • Timeouts: на каждой activity.
  • Compensations через явные branches.
  • Visibility: UI для просмотра всех workflows.
  • Versioning: возможность менять workflow code без поломки in-flight.

Определение: после прекращения новых обновлений, рано или поздно все реплики сойдутся к одному состоянию.

Window of inconsistency — время между событием и его propagation на все узлы.

Примеры:

  • DNS — обновление IP видно везде через TTL.
  • Cassandra — eventual consistency (можно настроить QUORUM для CP).
  • Saga — пока saga не завершилась, частичное состояние видно.

Принципы работы с EC:

  • Идемпотентность операций.
  • Дедупликация сообщений.
  • Read-after-write challenges (часто читай с master).
  • UI: оптимистичные updates (“loading…”, retry на ошибку).

Определение: операция, повторяющаяся с тем же input, даёт тот же результат и не имеет побочных эффектов.

Зачем критично в saga:

  • Сеть теряет ответы — клиент повторяет.
  • Worker упал в середине — другой повторяет step.
  • Без идемпотентности → дубли (двойная оплата, двойная отправка).

Реализация:

type ChargeRequest struct {
IdempotencyKey string // UUID, генерируется клиентом
Amount int64
AccountID int64
}
func (s *PaymentService) Charge(ctx context.Context, req ChargeRequest) (*Charge, error) {
// Check if this key was processed
existing, err := s.repo.FindByIdempotencyKey(ctx, req.IdempotencyKey)
if err == nil {
return existing, nil // Return cached result
}
// Process and save
charge := &Charge{ID: uuid.New(), IdempotencyKey: req.IdempotencyKey, ...}
if err := s.repo.Save(ctx, charge); err != nil {
return nil, err
}
return charge, nil
}

В БД:

CREATE UNIQUE INDEX idx_charges_idem ON charges(idempotency_key);

При повторном INSERT — ON CONFLICT DO NOTHING или возврат существующего.

Order: pending → paid → shipped → delivered

Если в pending уже — Charge сделает paid. Если уже paidCharge no-op (idempotent).

Менее предпочтительный, но иногда нужен (внешние API без idempotency).

1. Order Service ──CreateOrder──► Order(status=pending) created
2. Inventory Service ──Reserve────► Items reserved (TTL 30 min)
3. Payment Service ──Charge─────► Money charged
4. Order Service ──Confirm────► Order(status=paid)
5. Shipping Service ──Ship───────► Shipment created
6. Order Service ──Update─────► Order(status=shipped)
7. Notification ──Email──────► Confirmation sent
Failure at step 5 (Shipping fails):
4'. Payment Service ──Refund─────► Money refunded
3'. Inventory Service──Release────► Items released
2'. Order Service ──Cancel─────► Order(status=cancelled)

Что компенсировать нельзя:

  • Email уже отправлен → нельзя отозвать, только отправить compensating email “извините, отменено”.
  • Внешний платёж через bank wire → требуется ручная компенсация.
  • Physical действия (отправили посылку, нельзя её “не отправить”) — нужна страховка.
Аспект2PCSaga
ConsistencyStrongEventual
CouplingTight (XA across DBs)Loose (через events)
PerformanceНизкая (locks)Высокая (no locks)
Failure handlingBlockingCompensation
ComplexityМеньше кода (DB делает)Больше кода (компенсации)
МикросервисыНе подходитПодходит
ИдемпотентностьНе требуетТребует
Видимость промежуточных состоянийНетДа (другие транзакции видят)
ПрименениеSingle org, controlled envDistributed, microservices

Перед distributed tx подумайте: можно ли:

  • Объединить сервисы (если связь сильная)?
  • Использовать eventual consistency через events?
  • Сделать один сервис “source of truth”?

Микросервисы с distributed tx часто = distributed monolith (худшее обоих миров).

В saga нет isolation. Другие операции видят промежуточные состояния:

  • Между Inventory.Reserve и Payment.Charge — товар “заблокирован”, но не оплачен.
  • Если другой пользователь хочет купить — видит “out of stock”.

Это бизнес-проблема, а не bug.

Компенсация — это новое действие, отменяющее эффект.

  • Refund — не “отмена charge”, а новая transaction.
  • Cancel email — отправка следующего email.

Аудит-логи будут содержать обе записи (charge + refund).

T1: Create user
T2: Send welcome email

Если T2 упал → C1 (delete user). Но email уже отправлен → пользователь получил “welcome”, аккаунт удалён.

Решение: переставить — сначала email, потом user. Или: email — best-effort (без компенсации, просто log).

T3 failed → run C2 → C2 also fails → ???

Что делать?

  • Retry C2 с backoff (обычно).
  • Если retry не помогает — alert человеку.
  • Dead Letter Queue + manual intervention.
  • В Temporal — это решается через retry policies + alerts.

3.6 ⚠️ Идемпотентность сложнее, чем кажется

Заголовок раздела «3.6 ⚠️ Идемпотентность сложнее, чем кажется»

Не только “не дублировать”. Нужно учитывать:

  • Race conditions (одновременные retries).
  • Side effects (отправка emails — не идемпотентна по умолчанию).
  • External API без idempotency key.

Плюсы:

  • Durability, retries.
  • Visibility.

Минусы:

  • Сложный для понимания (workflows ≠ обычный код, есть restrictions).
  • Дополнительная инфра (Temporal cluster, БД).
  • Lock-in (workflow code привязан к Temporal SDK).
  • Versioning workflows — отдельная сложная тема.

Если 10+ сервисов слушают events друг друга — никто не знает полную картину.

Service A → event X → Service B reads X, emits Y
→ Service C reads X, emits Z
Service D reads Y AND Z, emits W
Service E reads W, emits...

Решение:

  • Event catalog (документация events).
  • Distributed tracing (Jaeger/Tempo).
  • Limited number of “core” events.

AWS RDS, Cloud SQL — частично поддерживают XA, но это медленно. Часто XA отключают для performance.

3.10 ⚠️ Sagas не для финансовых транзакций (в чистом виде)

Заголовок раздела «3.10 ⚠️ Sagas не для финансовых транзакций (в чистом виде)»

Финансы требуют strong consistency. Saga работает, если:

  • Каждый шаг — local ACID.
  • Есть escrow (промежуточный счёт) для money.
  • Регуляторное требование — audit trail (Saga его естественно даёт).

В классических банках — двойная запись (debit + credit) одной транзакцией.

Каждый шаг должен иметь timeout. Иначе:

  • Inventory.Reserve висит 5 минут.
  • Клиент думает, что заказ не прошёл.
  • Дублирующий заказ.

Решение: explicit timeouts + cancellation propagation.

Не все шаги — sequential. Можно делать parallel:

T1: Create order
T2, T3 parallel: Reserve inventory, Verify customer
T4: Charge
T5: Ship

Компенсация parallel branches — сложнее. Temporal поддерживает через workflow.Go() и channels.

Локальные транзакции в saga могут нарушать глобальные invariants.

Пример: invariant “total inventory ≥ 0”.

  • T1 ─ Reserve 10 items (inventory: 5 → -5)
  • Если бизнес запрещает minus — Reserve вернёт ошибку (compensation chain).

Но если physical stock = 5 (другие резервы есть), решение принимается на уровне Inventory service логики.


Decision tree:

  1. Все сервисы на одной БД (или поддерживают XA)? И acceptable latency 100ms+? → 2PC (просто).
  2. Микросервисы с разными БД? Compensation возможна? → Saga.
  3. Compensation невозможна или сложна? Можно жить с eventual consistency? → Events + Idempotent consumers.
  4. Multi-step + complex orchestration? → Temporal/Cadence.

Standard pattern для REST API (а-ля Stripe):

POST /v1/charges
Idempotency-Key: 7f8d9e0a-1234-...
{
"amount": 1000,
"currency": "USD"
}

В Go:

type Handler struct {
cache redis.Client
repo Repo
}
func (h *Handler) Charge(c *gin.Context) {
key := c.GetHeader("Idempotency-Key")
if key == "" {
c.JSON(400, gin.H{"error": "missing Idempotency-Key"})
return
}
// Check cache
if cached := h.cache.Get(ctx, "idem:"+key); cached != nil {
c.JSON(200, cached)
return
}
// Process
result, err := h.process(...)
// Cache result (with TTL 24h)
h.cache.Set(ctx, "idem:"+key, result, 24*time.Hour)
c.JSON(200, result)
}

(Подробнее в файле 18.)

Чтобы избежать dual-write (DB + queue):

BEGIN;
UPDATE orders SET status = 'reserved' WHERE id = $1;
INSERT INTO outbox (event_type, payload) VALUES ('OrderReserved', $2);
COMMIT;

Worker читает outbox → публикует event → mark sent.

func main() {
c, err := client.Dial(client.Options{
HostPort: "temporal.local:7233",
Namespace: "default",
})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()
w := worker.New(c, "order-task-queue", worker.Options{})
w.RegisterWorkflow(OrderWorkflow)
w.RegisterActivity(ReserveInventory)
w.RegisterActivity(ChargePayment)
w.RegisterActivity(CreateShipment)
w.RegisterActivity(SendOrderConfirmation)
if err := w.Run(worker.InterruptCh()); err != nil {
log.Fatalln("Unable to start worker", err)
}
}

Метрики:

  • Saga started / completed / failed (rate, по типам).
  • Saga duration (histogram).
  • Step duration (per step).
  • Compensation rate (по step).
  • DLQ size.

Алерты:

  • Saga failure rate > 1%.
  • Saga stuck > 10 минут (timeout).
  • Compensation failed.
  • DLQ растёт.

Unit tests для activities: обычные тесты.

Workflow tests (Temporal):

func TestOrderWorkflow(t *testing.T) {
s := &testsuite.WorkflowTestSuite{}
env := s.NewTestWorkflowEnvironment()
env.OnActivity(ReserveInventory, mock.Anything, mock.Anything).Return("res-123", nil)
env.OnActivity(ChargePayment, mock.Anything, mock.Anything).Return("pay-456", errors.New("payment failed"))
env.OnActivity(ReleaseInventory, mock.Anything, "res-123").Return(nil)
env.ExecuteWorkflow(OrderWorkflow, Order{ID: "1"})
require.True(t, env.IsWorkflowCompleted())
require.Error(t, env.GetWorkflowError())
}

Integration tests: запустить Temporal docker + sample services + проверить full flow.

Chaos testing: симулировать failures на каждом шаге, убедиться, что компенсация корректна.

Saga может выполняться часами/днями. Что если деплоится новая версия?

Temporal:

  • workflow.GetVersion(ctx, "v2-shipping-logic", workflow.DefaultVersion, 1) — определяет, какая версия исполняется.
  • Старые workflows доживают на старой версии.

Choreography:

  • Backward-compatible event schemas (Avro, Protobuf).
  • Schema registry.

OpenTelemetry — стандарт 2026.

ctx, span := otel.Tracer("payment").Start(ctx, "ChargePayment")
defer span.End()
// Trace ID propagates через gRPC/HTTP headers → видно в Jaeger/Tempo

В Temporal — встроенная интеграция с OTel.

Saga естественно даёт audit log. Сохранять:

  • Все steps (success/failure).
  • Все compensations.
  • Decision points.

Для compliance (finance, healthcare).

Client → API Gateway → Order Service
├─► Outbox table → Debezium → Kafka
Temporal Workflow
┌─────────────┼─────────────┐
▼ ▼ ▼
Inventory Payment Shipping
Service Service Service
(own DB) (own DB) (own DB)
│ │ │
└─────────────┼─────────────┘
Kafka events
┌───────┴────────┐
▼ ▼
Notification Analytics
Service pipeline

  1. Чем отличается ACID от BASE? ACID — strong consistency, локальные транзакции. BASE — basically available, soft state, eventual consistency. ACID для одной БД, BASE для распределённых систем.

  2. Что такое CAP теорема? В распределённой системе при network partition можно иметь только 2 из 3: Consistency, Availability, Partition tolerance. Реально выбор между CP и AP.

  3. Что такое 2PC? Two-Phase Commit. Координатор + участники. Фаза 1: Prepare (lock + vote). Фаза 2: Commit/Abort. Гарантирует atomicity, но blocking при coordinator failure.

  4. Почему 2PC плохо для микросервисов? Blocking при failures, медленный (round-trips), tight coupling (XA across DBs), не масштабируется.

  5. Что такое XA standard? Спецификация 2PC от X/Open. Поддерживается СУБД и Java EE (JTA).

  6. Что такое 3PC? 3-фазный протокол. Добавлена фаза PreCommit для устранения blocking. Не работает корректно при network partitions. Редко используется.

  7. Что такое TCC? Try-Confirm-Cancel. Сервис предоставляет 3 операции: Try (reserve), Confirm (apply), Cancel (rollback). Координатор оркестрирует.

  8. Что такое Saga pattern? Длинная транзакция как sequence локальных транзакций + компенсирующие транзакции для отката. Используется в микросервисах.

  9. Чем orchestration отличается от choreography в saga? Orchestration — центральный orchestrator дирижирует. Choreography — сервисы реагируют на events независимо.

  10. Когда orchestration лучше, когда choreography? Orchestration — для сложной логики, visibility, audit. Choreography — для простой логики и максимального decoupling.

  11. Что такое eventual consistency? После остановки новых обновлений, рано или поздно все узлы сойдутся к одному состоянию.

  12. Что такое идемпотентность и зачем в saga? Операция, повторяющаяся с тем же input, даёт тот же результат без побочных эффектов. Необходима в saga, т.к. retries неизбежны.

  13. Как реализовать idempotency? Idempotency-Key в API + проверка уникальности в БД. State machine — переход только из конкретного состояния.

  14. Что такое compensating transaction? Действие, отменяющее эффект предыдущей транзакции (например, refund для charge). Не undo, а новая транзакция.

  15. Что делать, если compensation тоже падает? Retry с backoff. Если не помогает — alert человеку, DLQ, manual intervention.

  16. Какие есть популярные workflow engines в 2026? Temporal (стандарт), Cadence (предшественник Temporal от Uber), Camunda Zeebe (BPMN), Conductor (Netflix).

  17. Зачем нужен Temporal? Durability workflows, автоматические retries, timeouts, visibility, compensations. Решает проблему “длинных распределённых процессов”.

  18. В чём workflow Temporal отличается от обычного кода? Workflow должен быть deterministic. Нельзя использовать time.Now, rand.Int напрямую — только через workflow API. State хранится в Temporal, при failure worker’a workflow продолжается.

  19. Что такое activity в Temporal? Конкретное действие (HTTP-вызов, БД-операция). Не deterministic. Идемпотентно. Имеет retry policy.

  20. Какие шаги нельзя компенсировать? Отправленные emails, SMS, physical actions (shipped items). Решения: avoid (best-effort steps в конце), compensating notification (“извините, отменено”), insurance.

  21. Что делать, если 5 сервисов слушают одно событие, и порядок важен? Использовать orchestrator (Temporal). Или ввести “core” сервис, который коордирует. Choreography без порядка не подходит.

  22. Как тестировать saga? Unit tests для activities. Workflow tests (Temporal test framework — мокать activities). Integration tests на staging. Chaos testing (симулировать failures).

  23. Что такое distributed tracing и почему важно для saga? Tracing запроса через все сервисы (OpenTelemetry, Jaeger, Tempo). Критично для debugging — иначе невозможно понять, где упало.

  24. Как добавить новый шаг в running saga? Workflow versioning. В Temporal — workflow.GetVersion(). Старые workflows доживают на старой версии, новые — на новой.

  25. Saga vs Event-Driven Architecture — в чём разница? Saga — конкретный pattern для consistency. EDA — общая архитектура. Saga может реализовываться поверх EDA (choreography saga).


Задача 1: Реализовать TCC для бронирования отеля

Заголовок раздела «Задача 1: Реализовать TCC для бронирования отеля»

3 сервиса: Hotel, Payment, Customer (loyalty points).

  • Try: reserve room, hold payment, deduct points temp.
  • Confirm: confirm reservation, charge, deduct points permanent.
  • Cancel: release room, refund hold, restore points.

Задача 2: Saga с компенсациями на Go (без Temporal)

Заголовок раздела «Задача 2: Saga с компенсациями на Go (без Temporal)»

Простой in-memory orchestrator. Steps + compensations как функции. Тест: симулировать failure на step 3, проверить, что C2, C1 выполнились.

Order processing workflow (5 шагов). Запустить Temporal в Docker. Implement activities. Тест на failure scenarios.

POST /charges с Idempotency-Key header. Дубли возвращают cached result. Использовать Redis для хранения.

3 сервиса + Kafka. Order Service → OrderCreated. Inventory → InventoryReserved. Payment → PaymentCompleted. Реализовать reactive consumers.

Задача 6: Сравнить 2PC и Saga (написать essay 1 страница)

Заголовок раздела «Задача 6: Сравнить 2PC и Saga (написать essay 1 страница)»

Когда какой выбрать. Trade-offs. Real-world примеры.

Если C2 трижды упал — отправить в DLQ + slack alert. Manual replay endpoint.


  1. Microservices Patterns by Chris Richardson — глава про Saga и обработку distributed transactions.
  2. Designing Data-Intensive Applications by Martin Kleppmann — главы 7-9: транзакции, distributed systems, consistency.
  3. Temporal Documentationhttps://docs.temporal.io/ (особенно Go SDK + workflow patterns).
  4. The Saga Pattern by Hector Garcia-Molina, Kenneth Salem (1987) — оригинальная статья.
  5. Building Event-Driven Microservices by Adam Bellemare — отличная книга про event-driven, EDA, Kafka.
  6. Microservices.io patternshttps://microservices.io/patterns/data/saga.html
  7. Pat Helland — “Life Beyond Distributed Transactions” — classic paper.
  8. Stripe API Docs (Idempotency)https://stripe.com/docs/api/idempotent_requests — реальный пример индустриальной практики.
  9. Confluent Bloghttps://www.confluent.io/blog/ — паттерны event-driven.
  10. AWS Step Functions docs — альтернативная workflow платформа.