--- id: backend-saga-patterns title: Saga — 분산 트랜잭션 / 보상 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [backend, saga, distributed-transaction, vibe-coding] tech_stack: { language: "TS", applicable_to: ["Backend"] } applied_in: [] aliases: [saga, choreography, orchestration, compensation, distributed transaction] --- # Saga > 마이크로서비스 = 2PC 못 함. **여러 step 의 트랜잭션 + 실패 시 보상 (compensating)**. **Choreography (이벤트 연쇄) vs Orchestration (중앙 조정자)**. Temporal / AWS Step Functions 가 modern. ## 📖 핵심 개념 - 각 step = 로컬 트랜잭션. - 실패 시 compensation = 이전 step 의 반대 작업. - Choreography: 서비스 간 이벤트 흐름. - Orchestration: 한 saga manager 가 차례로 호출. ## 💻 코드 패턴 ### 시나리오: 주문 = 결제 + 재고 + 배송 ``` 1. Payment.charge → 실패 시: 끝 2. Inventory.reserve → 실패 시: Payment.refund 3. Shipping.create → 실패 시: Inventory.release + Payment.refund 4. Order.markComplete ``` ### Choreography (이벤트 연쇄) ```ts // payment-service on(PaymentCharged) { publish(new InventoryReserveRequested({ orderId, items })); } on(PaymentFailed) { publish(new OrderFailed({ orderId, reason: 'payment' })); } // inventory-service on(InventoryReserveRequested) { try { await reserve(orderId, items); publish(new InventoryReserved({ orderId })); } catch { publish(new InventoryReserveFailed({ orderId })); } } on(InventoryReserveFailed) { publish(new RefundRequested({ orderId })); // 보상 트리거 } ``` 장점: 의존성 적음. 단점: flow 가 코드에 분산 — 추적 어려움. ### Orchestration (중앙) ```ts class OrderSaga { constructor(private orderId: string) {} async run(): Promise { const compensations: (() => Promise)[] = []; try { const payment = await Payment.charge(this.orderId); compensations.push(() => Payment.refund(payment.id)); const reservation = await Inventory.reserve(this.orderId); compensations.push(() => Inventory.release(reservation.id)); const shipping = await Shipping.create(this.orderId); compensations.push(() => Shipping.cancel(shipping.id)); await Order.markComplete(this.orderId); return { ok: true }; } catch (e) { // 역순 보상 for (const c of compensations.reverse()) { try { await c(); } catch (cerr) { /* 보상 실패 — 알람 */ } } return { ok: false, error: e }; } } } ``` ### Temporal (workflow engine) ```ts import { proxyActivities } from '@temporalio/workflow'; const { charge, refund, reserve, release, ship } = proxyActivities({ startToCloseTimeout: '1 minute', }); export async function orderSaga(orderId: string): Promise { let paymentId: string | undefined; let reservationId: string | undefined; try { paymentId = await charge(orderId); reservationId = await reserve(orderId); await ship(orderId); } catch (e) { if (reservationId) await release(reservationId); if (paymentId) await refund(paymentId); throw e; } } ``` Temporal: 자동 재시도, idempotent activity, history replay, time travel debug. ### Idempotent activity ```ts async function charge(orderId: string): Promise { // 같은 orderId 로 두 번 호출 — 한 번만 실제 charge const existing = await db.payments.find({ orderId }); if (existing) return existing.id; const id = await stripe.paymentIntents.create({ idempotencyKey: orderId, ... }); await db.payments.insert({ id, orderId }); return id; } ``` ### State machine + persistent ```ts type SagaState = | { step: 'INIT' } | { step: 'PAYMENT_DONE'; paymentId: string } | { step: 'INVENTORY_DONE'; paymentId: string; reservationId: string } | { step: 'COMPLETE' } | { step: 'COMPENSATING'; compensated: string[] }; async function step(state: SagaState): Promise { // 한 step 만 진행 후 DB 에 state 저장 // 실패 / crash 후에도 resume } ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 2-3 step 단순 | Orchestration (코드 안) | | 많은 서비스 + 비동기 | Choreography (이벤트) | | 강 보장 + 디버깅 | Temporal / Step Functions | | 단일 DB | 일반 트랜잭션 | | 외부 API 만 | Idempotency + retry | | 시간 오래 (며칠) | Temporal / Cadence | ## ❌ 안티패턴 - **2PC 시도**: HTTP / 다른 DB 간 원자성 불가. - **보상 누락**: 실패 후 일관성 깨짐. - **Idempotency 없음**: 재시도 시 두 번 실행. - **State 메모리만 — crash 시 사라짐**: persistent. - **순서 보장 가정 (이벤트 큐)**: 항상 보장 X. - **Choreography 거대**: flow 추적 불가. orchestration 으로. - **보상도 실패 — 무시**: 알람 + 수동 복구. ## 🤖 LLM 활용 힌트 - 단순 = orchestration in-code. - 복잡 = Temporal / Step Functions. - 모든 step idempotent + 보상 명시. ## 🔗 관련 문서 - [[Backend_Event_Sourcing]] - [[Backend_Outbox_Pattern]]