Files
2nd/10_Wiki/Topic_Programming/Coding/Backend_Saga_Patterns.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

5.1 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
backend-saga-patterns Saga — 분산 트랜잭션 / 보상 Coding draft B conceptual 2026-05-09 2026-05-09
backend
saga
distributed-transaction
vibe-coding
language applicable_to
TS
Backend
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 (이벤트 연쇄)

// 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 (중앙)

class OrderSaga {
  constructor(private orderId: string) {}

  async run(): Promise<SagaResult> {
    const compensations: (() => Promise<void>)[] = [];
    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)

import { proxyActivities } from '@temporalio/workflow';

const { charge, refund, reserve, release, ship } = proxyActivities<typeof activities>({
  startToCloseTimeout: '1 minute',
});

export async function orderSaga(orderId: string): Promise<void> {
  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

async function charge(orderId: string): Promise<string> {
  // 같은 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

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<SagaState> {
  // 한 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 + 보상 명시.

🔗 관련 문서