Skip to content

🔄 DISTRIBUTED TRANSACTIONS

Two-Phase Commit, Saga Pattern, và Eventual Consistency


📋 Prerequisites

📚 Kiến thức cần có

Trước khi bắt đầu module này, hãy đảm bảo bạn đã nắm vững:


🎯 Mục tiêu

Sau khi hoàn thành module này, bạn sẽ:

  1. Hiểu vấn đề của transactions trong distributed systems
  2. Biết Two-Phase Commit (2PC) và tại sao nó không phù hợp cho Microservices
  3. Nắm vững Saga Pattern với hai approaches: Choreography và Orchestration
  4. Hiểu IdempotencyCompensation Transactions

🔥 The Problem: Transactions Across Services

Trong Monolith: ACID là đơn giản

Trong một Monolith với single database, transactions rất đơn giản:

typescript
// Monolith: Single database transaction
async function purchaseCourse(userId: string, courseId: string) {
  const transaction = await db.beginTransaction();
  
  try {
    // 1. Trừ tiền user
    await transaction.query(
      'UPDATE wallets SET balance = balance - ? WHERE user_id = ?',
      [coursePrice, userId]
    );
    
    // 2. Tạo order
    await transaction.query(
      'INSERT INTO orders (user_id, course_id, status) VALUES (?, ?, ?)',
      [userId, courseId, 'completed']
    );
    
    // 3. Cấp quyền truy cập course
    await transaction.query(
      'INSERT INTO enrollments (user_id, course_id) VALUES (?, ?)',
      [userId, courseId]
    );
    
    await transaction.commit();  // ✅ All or nothing
  } catch (error) {
    await transaction.rollback(); // ✅ Automatic rollback
    throw error;
  }
}

ACID guarantees:

  • Atomicity: Tất cả operations thành công hoặc tất cả rollback
  • Consistency: Database luôn ở trạng thái hợp lệ
  • Isolation: Transactions không ảnh hưởng lẫn nhau
  • Durability: Committed data không bị mất

Trong Microservices: ACID không còn khả thi

Khi chuyển sang Microservices với Database per Service, mỗi service có database riêng:

┌─────────────────────────────────────────────────────────────────┐
│                    MICROSERVICES ARCHITECTURE                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │Order Service│    │Payment Svc  │    │Course Svc   │         │
│  │             │    │             │    │             │         │
│  │ createOrder │───▶│ chargeUser  │───▶│ grantAccess │         │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘         │
│         │                  │                  │                 │
│         ▼                  ▼                  ▼                 │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │  PostgreSQL │    │    MySQL    │    │   MongoDB   │         │
│  │   (Orders)  │    │  (Payments) │    │  (Courses)  │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
│                                                                  │
│  ❌ Không thể dùng single transaction cho 3 databases!          │
└─────────────────────────────────────────────────────────────────┘

⚠️ Vấn đề cốt lõi

Không thể có ACID transaction across multiple databases.

Nếu Payment Service thành công nhưng Course Service fail:

  • User đã bị trừ tiền ✅
  • Nhưng không được cấp quyền truy cập course ❌
  • Data inconsistency!

Ví dụ thực tế: Mua khóa học trên HPN

Giả sử user mua khóa học "System Design Mastery" trên HPN:

StepServiceActionDatabase
1Order ServiceTạo orderPostgreSQL
2Payment ServiceTrừ tiền userMySQL
3Inventory ServiceGiảm slot (nếu limited)Redis
4Course ServiceCấp quyền truy cậpMongoDB
5Notification ServiceGửi email xác nhận-

Câu hỏi: Nếu Step 3 fail (hết slot), làm sao rollback Step 1 và 2?


🔐 Two-Phase Commit (2PC)

Cách hoạt động

Two-Phase Commit là protocol cổ điển để đảm bảo atomicity across multiple databases:

┌─────────────────────────────────────────────────────────────────┐
│                    TWO-PHASE COMMIT (2PC)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                    ┌─────────────────┐                          │
│                    │  COORDINATOR    │                          │
│                    │  (Transaction   │                          │
│                    │   Manager)      │                          │
│                    └────────┬────────┘                          │
│                             │                                    │
│         ┌───────────────────┼───────────────────┐               │
│         │                   │                   │               │
│         ▼                   ▼                   ▼               │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │Participant A│    │Participant B│    │Participant C│         │
│  │  (Order DB) │    │ (Payment DB)│    │ (Course DB) │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
│                                                                  │
│  PHASE 1: PREPARE (Voting)                                      │
│  ─────────────────────────                                      │
│  Coordinator → All: "Can you commit?"                           │
│  All → Coordinator: "Yes, I'm ready" or "No, I can't"          │
│                                                                  │
│  PHASE 2: COMMIT/ABORT                                          │
│  ────────────────────                                           │
│  If ALL voted YES → Coordinator: "COMMIT"                       │
│  If ANY voted NO  → Coordinator: "ABORT"                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Pseudocode

typescript
// Two-Phase Commit Coordinator
class TwoPhaseCommitCoordinator {
  async executeTransaction(participants: Participant[], operations: Operation[]) {
    // PHASE 1: PREPARE
    const votes: boolean[] = [];
    
    for (let i = 0; i < participants.length; i++) {
      try {
        // Ask each participant to prepare (lock resources, validate)
        const canCommit = await participants[i].prepare(operations[i]);
        votes.push(canCommit);
      } catch (error) {
        votes.push(false);
      }
    }
    
    // PHASE 2: COMMIT or ABORT
    const allVotedYes = votes.every(vote => vote === true);
    
    if (allVotedYes) {
      // All participants agreed - COMMIT
      for (const participant of participants) {
        await participant.commit();
      }
      return { success: true };
    } else {
      // At least one participant disagreed - ABORT
      for (const participant of participants) {
        await participant.abort();
      }
      return { success: false };
    }
  }
}

Limitations của 2PC

⚠️ Tại sao 2PC không phù hợp cho Microservices

LimitationGiải thích
Blocking ProtocolParticipants phải giữ locks trong suốt quá trình, blocking other transactions
Single Point of FailureNếu Coordinator chết giữa chừng → participants bị stuck
LatencyCần nhiều round-trips giữa coordinator và participants
Tight CouplingTất cả participants phải available cùng lúc
Not ScalableKhông phù hợp với high-throughput systems
2PC Failure Scenario:
─────────────────────

Timeline:
  T1: Coordinator sends PREPARE to all
  T2: All participants vote YES
  T3: Coordinator decides COMMIT
  T4: Coordinator sends COMMIT to Participant A ✅
  T5: Coordinator CRASHES! 💥
  
Result:
  - Participant A: COMMITTED
  - Participant B: WAITING (holding locks)
  - Participant C: WAITING (holding locks)
  
  → B và C không biết nên COMMIT hay ABORT
  → Resources bị locked vô thời hạn
  → DEADLOCK!

🔧 Raizo's Take on 2PC

"2PC là giải pháp từ thời databases còn chạy trên mainframe. Trong thế giới Microservices với hàng trăm services, 2PC là công thức cho disaster. Tôi đã thấy production systems bị stuck hàng giờ vì coordinator crash."


🎭 Saga Pattern: The Modern Solution

Saga là gì?

Saga Pattern là cách xử lý distributed transactions bằng chuỗi local transactions, mỗi transaction có một compensation transaction tương ứng để rollback nếu cần.

💡 Ý tưởng cốt lõi

Thay vì một big transaction, chia thành nhiều small transactions. Nếu một step fail, chạy compensation transactions để "undo" các steps trước đó.

SAGA = T1 → T2 → T3 → T4
       ↓    ↓    ↓    ↓
       C1   C2   C3   C4  (Compensation transactions)

Nếu T3 fail:
  Execute: C2 → C1 (reverse order)

Hai Approaches của Saga

1. Choreography (Event-based)

Mỗi service tự quyết định next step dựa trên events:

┌─────────────────────────────────────────────────────────────────┐
│                    SAGA: CHOREOGRAPHY                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐         ┌─────────────┐         ┌───────────┐ │
│  │Order Service│         │Payment Svc  │         │Course Svc │ │
│  └──────┬──────┘         └──────┬──────┘         └─────┬─────┘ │
│         │                       │                      │        │
│         │  OrderCreated         │                      │        │
│         │──────────────────────▶│                      │        │
│         │                       │                      │        │
│         │                       │  PaymentCompleted    │        │
│         │                       │─────────────────────▶│        │
│         │                       │                      │        │
│         │                       │                      │ Grant  │
│         │                       │                      │ Access │
│         │                       │                      │        │
│         │  CourseAccessGranted  │                      │        │
│         │◀─────────────────────────────────────────────│        │
│         │                       │                      │        │
│         │ Complete              │                      │        │
│         │ Order                 │                      │        │
│                                                                  │
│  📬 Event Bus (Kafka/RabbitMQ)                                  │
│  ═══════════════════════════════════════════════════════════    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Đặc điểm:

  • Không có central coordinator
  • Services communicate qua events
  • Mỗi service subscribe events và react accordingly
  • Decentralized decision making

2. Orchestration (Central Coordinator)

Một Saga Orchestrator điều phối toàn bộ flow:

┌─────────────────────────────────────────────────────────────────┐
│                    SAGA: ORCHESTRATION                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                    ┌─────────────────┐                          │
│                    │ SAGA            │                          │
│                    │ ORCHESTRATOR    │                          │
│                    │ (Order Saga)    │                          │
│                    └────────┬────────┘                          │
│                             │                                    │
│         ┌───────────────────┼───────────────────┐               │
│         │                   │                   │               │
│         ▼                   ▼                   ▼               │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │Order Service│    │Payment Svc  │    │Course Svc   │         │
│  │             │    │             │    │             │         │
│  │ 1. Create   │    │ 2. Charge   │    │ 3. Grant    │         │
│  │    Order    │    │    User     │    │    Access   │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
│                                                                  │
│  Orchestrator controls the flow:                                │
│  Step 1 → Step 2 → Step 3                                       │
│  If Step 2 fails → Compensate Step 1                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Đặc điểm:

  • Central orchestrator biết toàn bộ flow
  • Orchestrator gọi từng service theo thứ tự
  • Dễ track state và handle failures
  • Centralized decision making

So sánh Choreography vs Orchestration

Tiêu chíChoreographyOrchestration
CouplingLoose (services độc lập)Tighter (phụ thuộc orchestrator)
ComplexityDistributed (khó debug)Centralized (dễ debug)
Single Point of FailureKhông cóOrchestrator là SPOF
VisibilityKhó track flowDễ track flow
ScalabilityTốt hơnOrchestrator có thể bottleneck
Use CaseSimple flows, few stepsComplex flows, many steps

📊 Saga Pattern: Mua khóa học trên HPN

Happy Path Flow

Compensation Flow (Khi có lỗi)

State Machine của Order Saga


📖 Key Terminology

Idempotency

💡 Định nghĩa: Idempotency

Idempotent operation là operation cho kết quả giống nhau dù thực hiện một hay nhiều lần.

f(x) = f(f(x)) = f(f(f(x))) = ...

Tại sao Idempotency quan trọng trong Distributed Systems?

Trong distributed systems, network failures có thể gây ra:

  • Duplicate messages: Message được gửi 2 lần
  • Retry logic: Client retry khi không nhận được response
  • At-least-once delivery: Message queue đảm bảo delivery bằng cách retry
typescript
// ❌ NON-IDEMPOTENT: Mỗi lần gọi tăng balance
async function addBalance(userId: string, amount: number) {
  await db.query(
    'UPDATE wallets SET balance = balance + ? WHERE user_id = ?',
    [amount, userId]
  );
}
// Gọi 2 lần → balance tăng 2 lần! 💥

// ✅ IDEMPOTENT: Dùng idempotency key
async function addBalanceIdempotent(
  userId: string, 
  amount: number, 
  idempotencyKey: string
) {
  // Check if already processed
  const existing = await db.query(
    'SELECT * FROM processed_transactions WHERE idempotency_key = ?',
    [idempotencyKey]
  );
  
  if (existing) {
    return existing.result; // Return cached result
  }
  
  // Process and store idempotency key
  await db.transaction(async (tx) => {
    await tx.query(
      'UPDATE wallets SET balance = balance + ? WHERE user_id = ?',
      [amount, userId]
    );
    await tx.query(
      'INSERT INTO processed_transactions (idempotency_key, result) VALUES (?, ?)',
      [idempotencyKey, 'success']
    );
  });
  
  return 'success';
}
// Gọi 2 lần với cùng key → balance chỉ tăng 1 lần ✅

Common Idempotency Patterns:

PatternMô tảVí dụ
Idempotency KeyClient gửi unique key với mỗi requestX-Idempotency-Key: uuid-123
Conditional UpdateChỉ update nếu state matchUPDATE ... WHERE version = ?
UpsertInsert or Update based on keyINSERT ... ON CONFLICT DO UPDATE
DeduplicationTrack processed message IDsStore message_id in Redis

Compensation Transaction

💡 Định nghĩa: Compensation Transaction

Compensation Transaction là transaction được thiết kế để "undo" effect của một transaction trước đó.

Lưu ý quan trọng: Compensation KHÔNG phải là rollback. Nó là một forward transaction tạo ra effect ngược lại.

typescript
// Original Transaction
async function chargeUser(userId: string, amount: number, orderId: string) {
  await db.query(
    'UPDATE wallets SET balance = balance - ? WHERE user_id = ?',
    [amount, userId]
  );
  await db.query(
    'INSERT INTO transactions (order_id, type, amount) VALUES (?, ?, ?)',
    [orderId, 'CHARGE', amount]
  );
}

// Compensation Transaction (NOT a rollback!)
async function refundUser(userId: string, amount: number, orderId: string) {
  // Tạo một transaction MỚI để hoàn tiền
  // Không phải xóa transaction cũ!
  await db.query(
    'UPDATE wallets SET balance = balance + ? WHERE user_id = ?',
    [amount, userId]
  );
  await db.query(
    'INSERT INTO transactions (order_id, type, amount) VALUES (?, ?, ?)',
    [orderId, 'REFUND', amount]  // New transaction record
  );
}

Compensation vs Rollback:

AspectDatabase RollbackCompensation Transaction
TimingTrước khi commitSau khi commit
MechanismUndo logNew forward transaction
Audit TrailKhông cóCó (cả original và compensation)
ScopeSingle databaseCross-service

💻 Code Examples: Saga Implementation

Choreography Implementation (TypeScript)

typescript
// ============================================
// CHOREOGRAPHY SAGA: Event-based approach
// ============================================

// Event Types
interface OrderCreatedEvent {
  type: 'ORDER_CREATED';
  orderId: string;
  userId: string;
  courseId: string;
  amount: number;
}

interface PaymentCompletedEvent {
  type: 'PAYMENT_COMPLETED';
  orderId: string;
  transactionId: string;
}

interface PaymentFailedEvent {
  type: 'PAYMENT_FAILED';
  orderId: string;
  reason: string;
}

interface SlotReservedEvent {
  type: 'SLOT_RESERVED';
  orderId: string;
  courseId: string;
}

interface SlotReservationFailedEvent {
  type: 'SLOT_RESERVATION_FAILED';
  orderId: string;
  reason: string;
}

// Order Service - Initiates Saga
class OrderService {
  constructor(
    private eventBus: EventBus,
    private orderRepository: OrderRepository
  ) {
    // Listen for completion/failure events
    this.eventBus.subscribe('SLOT_RESERVED', this.onSlotReserved.bind(this));
    this.eventBus.subscribe('PAYMENT_FAILED', this.onPaymentFailed.bind(this));
    this.eventBus.subscribe('SLOT_RESERVATION_FAILED', this.onSlotFailed.bind(this));
  }

  async createOrder(userId: string, courseId: string, amount: number): Promise<string> {
    const orderId = generateUUID();
    
    // T1: Create order in PENDING state
    await this.orderRepository.create({
      id: orderId,
      userId,
      courseId,
      amount,
      status: 'PENDING'
    });

    // Publish event to trigger next step
    await this.eventBus.publish<OrderCreatedEvent>({
      type: 'ORDER_CREATED',
      orderId,
      userId,
      courseId,
      amount
    });

    return orderId;
  }

  private async onSlotReserved(event: SlotReservedEvent): Promise<void> {
    // Saga completed successfully
    await this.orderRepository.updateStatus(event.orderId, 'COMPLETED');
  }

  private async onPaymentFailed(event: PaymentFailedEvent): Promise<void> {
    // C1: Cancel order (no payment to refund)
    await this.orderRepository.updateStatus(event.orderId, 'CANCELLED');
  }

  private async onSlotFailed(event: SlotReservationFailedEvent): Promise<void> {
    // Order will be cancelled after refund completes
    await this.orderRepository.updateStatus(event.orderId, 'REFUNDING');
  }
}

// Payment Service - Step 2
class PaymentService {
  constructor(
    private eventBus: EventBus,
    private paymentRepository: PaymentRepository
  ) {
    this.eventBus.subscribe('ORDER_CREATED', this.onOrderCreated.bind(this));
    this.eventBus.subscribe('SLOT_RESERVATION_FAILED', this.onSlotFailed.bind(this));
  }

  private async onOrderCreated(event: OrderCreatedEvent): Promise<void> {
    try {
      // T2: Charge user
      const transactionId = await this.chargeUser(
        event.userId, 
        event.amount,
        event.orderId
      );

      await this.eventBus.publish<PaymentCompletedEvent>({
        type: 'PAYMENT_COMPLETED',
        orderId: event.orderId,
        transactionId
      });
    } catch (error) {
      await this.eventBus.publish<PaymentFailedEvent>({
        type: 'PAYMENT_FAILED',
        orderId: event.orderId,
        reason: error.message
      });
    }
  }

  private async onSlotFailed(event: SlotReservationFailedEvent): Promise<void> {
    // C2: Refund user (compensation)
    await this.refundUser(event.orderId);
    
    await this.eventBus.publish({
      type: 'REFUND_COMPLETED',
      orderId: event.orderId
    });
  }

  private async chargeUser(
    userId: string, 
    amount: number, 
    orderId: string
  ): Promise<string> {
    // Idempotent charge using orderId as idempotency key
    return await this.paymentRepository.charge(userId, amount, orderId);
  }

  private async refundUser(orderId: string): Promise<void> {
    await this.paymentRepository.refund(orderId);
  }
}

// Inventory Service - Step 3
class InventoryService {
  constructor(
    private eventBus: EventBus,
    private inventoryRepository: InventoryRepository
  ) {
    this.eventBus.subscribe('PAYMENT_COMPLETED', this.onPaymentCompleted.bind(this));
  }

  private async onPaymentCompleted(event: PaymentCompletedEvent): Promise<void> {
    const order = await this.getOrderDetails(event.orderId);
    
    try {
      // T3: Reserve slot
      await this.inventoryRepository.reserveSlot(order.courseId, event.orderId);

      await this.eventBus.publish<SlotReservedEvent>({
        type: 'SLOT_RESERVED',
        orderId: event.orderId,
        courseId: order.courseId
      });
    } catch (error) {
      await this.eventBus.publish<SlotReservationFailedEvent>({
        type: 'SLOT_RESERVATION_FAILED',
        orderId: event.orderId,
        reason: error.message
      });
    }
  }
}

Orchestration Implementation (TypeScript)

typescript
// ============================================
// ORCHESTRATION SAGA: Central coordinator
// ============================================

// Saga State
interface OrderSagaState {
  orderId: string;
  userId: string;
  courseId: string;
  amount: number;
  currentStep: 'INIT' | 'ORDER_CREATED' | 'PAYMENT_COMPLETED' | 'SLOT_RESERVED' | 'COMPLETED' | 'COMPENSATING' | 'CANCELLED';
  paymentTransactionId?: string;
  error?: string;
}

// Saga Orchestrator
class OrderSagaOrchestrator {
  constructor(
    private orderService: OrderServiceClient,
    private paymentService: PaymentServiceClient,
    private inventoryService: InventoryServiceClient,
    private notificationService: NotificationServiceClient,
    private sagaRepository: SagaRepository
  ) {}

  async execute(userId: string, courseId: string, amount: number): Promise<string> {
    const sagaId = generateUUID();
    
    // Initialize saga state
    const state: OrderSagaState = {
      orderId: sagaId,
      userId,
      courseId,
      amount,
      currentStep: 'INIT'
    };
    
    await this.sagaRepository.save(sagaId, state);

    try {
      // Step 1: Create Order
      await this.orderService.createOrder(state.orderId, userId, courseId, amount);
      state.currentStep = 'ORDER_CREATED';
      await this.sagaRepository.save(sagaId, state);

      // Step 2: Process Payment
      const transactionId = await this.paymentService.charge(userId, amount, state.orderId);
      state.paymentTransactionId = transactionId;
      state.currentStep = 'PAYMENT_COMPLETED';
      await this.sagaRepository.save(sagaId, state);

      // Step 3: Reserve Slot
      await this.inventoryService.reserveSlot(courseId, state.orderId);
      state.currentStep = 'SLOT_RESERVED';
      await this.sagaRepository.save(sagaId, state);

      // Step 4: Send Notification
      await this.notificationService.sendConfirmation(userId, courseId, state.orderId);
      
      // Complete saga
      state.currentStep = 'COMPLETED';
      await this.sagaRepository.save(sagaId, state);
      await this.orderService.completeOrder(state.orderId);

      return state.orderId;

    } catch (error) {
      // Compensation flow
      state.error = error.message;
      state.currentStep = 'COMPENSATING';
      await this.sagaRepository.save(sagaId, state);
      
      await this.compensate(state);
      
      state.currentStep = 'CANCELLED';
      await this.sagaRepository.save(sagaId, state);
      
      throw new SagaFailedError(state.orderId, error.message);
    }
  }

  private async compensate(state: OrderSagaState): Promise<void> {
    // Compensate in REVERSE order
    const compensationSteps: Array<() => Promise<void>> = [];

    // Only compensate steps that were completed
    switch (state.currentStep) {
      case 'SLOT_RESERVED':
        compensationSteps.push(() => 
          this.inventoryService.releaseSlot(state.courseId, state.orderId)
        );
        // Fall through to compensate previous steps
      case 'PAYMENT_COMPLETED':
        compensationSteps.push(() => 
          this.paymentService.refund(state.orderId)
        );
      case 'ORDER_CREATED':
        compensationSteps.push(() => 
          this.orderService.cancelOrder(state.orderId)
        );
        break;
    }

    // Execute compensations in order (which is reverse of original)
    for (const compensate of compensationSteps) {
      try {
        await compensate();
      } catch (compensationError) {
        // Log and continue - compensation must be best-effort
        console.error('Compensation failed:', compensationError);
        // In production: alert, manual intervention queue
      }
    }
  }
}

// Usage
const orchestrator = new OrderSagaOrchestrator(
  orderService,
  paymentService,
  inventoryService,
  notificationService,
  sagaRepository
);

try {
  const orderId = await orchestrator.execute('user-123', 'course-456', 100);
  console.log('Order completed:', orderId);
} catch (error) {
  console.error('Order failed:', error.message);
}

🎓 Giáo sư Tom's Insight

🎓 Góc nhìn Lý thuyết

BASE vs ACID

Trong distributed systems, chúng ta trade ACID cho BASE:

ACIDBASE
AtomicityBasically Available
ConsistencySoft state
IsolationEventual consistency
Durability

Saga Pattern là implementation của BASE model:

  • Basically Available: System vẫn hoạt động dù có failures
  • Soft State: State có thể thay đổi over time (PENDING → COMPLETED)
  • Eventual Consistency: Data sẽ consistent sau khi saga hoàn thành

CAP Theorem Connection

Saga Pattern chấp nhận Partition ToleranceAvailability, sacrifice strong Consistency cho eventual consistency.

CAP Theorem:
┌─────────────────────────────────────┐
│           Consistency               │
│              /\                     │
│             /  \                    │
│            /    \                   │
│           / SAGA \                  │
│          /   ⭐   \                 │
│         /          \                │
│        /____________\               │
│  Availability    Partition          │
│                  Tolerance          │
└─────────────────────────────────────┘

Semantic Locks

Saga sử dụng semantic locks thay vì database locks:

  • Order status = PENDING là một semantic lock
  • Các services khác biết order đang được xử lý
  • Không block database resources

🔧 Kỹ sư Raizo's Reality Check

🔧 Góc nhìn Thực chiến

"Saga không phải silver bullet"

"Saga giải quyết được distributed transactions, nhưng đừng nghĩ nó miễn phí. Đây là những gì tôi học được sau nhiều năm production:"

Pitfalls thực tế

PitfallMô tảGiải pháp
Compensation FailuresCompensation cũng có thể fail!Retry với exponential backoff, dead letter queue
Partial FailuresMột số compensations thành công, một số khôngSaga state machine, manual intervention queue
Ordering IssuesEvents đến không đúng thứ tựSequence numbers, event sourcing
Duplicate EventsNetwork retry gây duplicateIdempotency keys everywhere
Long-running SagasSaga chạy quá lâuTimeouts, saga expiration

Production Checklist

markdown
✅ Saga Checklist:
- [ ] Mọi operation đều idempotent
- [ ] Mọi step đều có compensation
- [ ] Compensation có retry logic
- [ ] Có timeout cho toàn bộ saga
- [ ] Có monitoring cho saga state
- [ ] Có alerting khi saga stuck
- [ ] Có manual intervention UI
- [ ] Có dead letter queue cho failed compensations

Khi nào KHÔNG dùng Saga

"Đừng dùng Saga khi bạn có thể tránh được distributed transactions:"

  1. Có thể dùng single database? → Dùng ACID transaction
  2. Có thể merge services? → Giảm số services
  3. Có thể dùng eventual consistency đơn giản? → Không cần compensation

💡 Think About It

🤔 Câu hỏi suy ngẫm

  1. Idempotency quan trọng như thế nào? Nếu payment service không idempotent, điều gì xảy ra khi network timeout và client retry?

  2. Tại sao Compensation Transaction không phải là rollback? Sự khác biệt này quan trọng như thế nào cho audit trail?

  3. Choreography vs Orchestration: Nếu bạn có 10 services trong một saga, approach nào dễ debug hơn? Approach nào scale tốt hơn?

  4. Trong ví dụ "Mua khóa học", nếu Notification Service fail, có cần rollback payment không? Tại sao?


🎯 Interactive Scenario

🎯 Tình huống: E-commerce Order Processing

Bối cảnh

Bạn đang thiết kế hệ thống order processing cho một e-commerce platform:

Thông tinChi tiết
ServicesOrder, Payment, Inventory, Shipping, Notification
Daily Orders100,000+ orders/day
SLA99.9% availability
RequirementKhông được mất tiền của customer

Flow hiện tại (có vấn đề)

typescript
async function processOrder(order: Order) {
  // Synchronous chain - BAD!
  await orderService.create(order);
  await paymentService.charge(order.userId, order.total);
  await inventoryService.reserve(order.items);
  await shippingService.createShipment(order);
  await notificationService.sendConfirmation(order);
}

Vấn đề: Nếu inventoryService.reserve() fail sau khi payment đã charge, customer mất tiền!

Câu hỏi

Bạn sẽ thiết kế lại như thế nào?

A) Two-Phase Commit - Dùng distributed transaction coordinator

B) Saga với Choreography - Mỗi service publish events, services khác react

C) Saga với Orchestration - Central OrderSaga coordinator điều phối flow

D) Giữ nguyên - Thêm try-catch và manual refund


🔧 Raizo's Verdict

Nếu chọn A (2PC):"2PC với 5 services và 100k orders/day? Bạn sẽ có latency khủng khiếp và coordinator sẽ là bottleneck. Đừng làm vậy."

Nếu chọn D (Giữ nguyên):"Manual refund cho 100k orders/day? Team support của bạn sẽ nghỉ việc trong tuần đầu tiên."

✅ Đáp án đúng: B hoặc C (Saga Pattern)

Phân tích

Cả Choreography (B)Orchestration (C) đều là valid choices, tùy thuộc vào context:

Chọn Choreography khi:

  • Team có experience với event-driven architecture
  • Muốn loose coupling tối đa
  • Có sẵn robust event infrastructure (Kafka)

Chọn Orchestration khi:

  • Flow phức tạp, cần visibility
  • Team mới với Saga pattern
  • Cần dễ debug và monitor
typescript
class OrderSaga {
  async execute(order: Order): Promise<void> {
    const saga = await this.initSaga(order);
    
    try {
      // Step 1: Create Order (PENDING)
      await this.orderService.create(order);
      saga.step = 'ORDER_CREATED';
      
      // Step 2: Reserve Inventory FIRST (before payment!)
      await this.inventoryService.reserve(order.items);
      saga.step = 'INVENTORY_RESERVED';
      
      // Step 3: Charge Payment (after inventory confirmed)
      await this.paymentService.charge(order);
      saga.step = 'PAYMENT_COMPLETED';
      
      // Step 4: Create Shipment
      await this.shippingService.createShipment(order);
      saga.step = 'SHIPMENT_CREATED';
      
      // Step 5: Notification (non-critical, no compensation needed)
      await this.notificationService.sendConfirmation(order);
      
      saga.step = 'COMPLETED';
    } catch (error) {
      await this.compensate(saga);
    }
  }
}

Key insight: Reserve inventory TRƯỚC payment để tránh charge customer khi hết hàng!

:::


📝 Summary

ConceptKey Takeaway
Distributed TransactionsACID không khả thi across multiple databases
Two-Phase CommitBlocking, không scalable, avoid trong Microservices
Saga PatternChuỗi local transactions + compensation transactions
ChoreographyEvent-based, loose coupling, khó debug
OrchestrationCentral coordinator, dễ debug, có SPOF
IdempotencyBẮT BUỘC cho mọi operation trong distributed systems
CompensationForward transaction để "undo", không phải rollback

🚀 Next Steps

Tiếp theo trong Phase 3

👉 Module 3.3: Advanced Patterns →

Học về CQRS, Event Sourcing, và Service Mesh.

TopicLiên quan
📊 Consistency ModelsStrong vs Eventual Consistency
📬 Async MessagingEvent-driven architecture foundation
🏛️ Monolith vs MicroservicesDatabase per Service principle

Case Studies áp dụng

Case StudyÁp dụng
🚗 Design UberSaga trong ride booking flow
🐦 Design TwitterEvent-driven architecture

📖 Further Reading