EngineeringSenior DeveloperTraining Sessions

Session 10 - Distributed Systems Thinking

Khi bạn đặt 1 network call giữa 2 components, MỌI THỨ thay đổi. Session này không dạy tools, mà dạy tư duy để ra quyết định đúng khi thiết kế distributed systems.


Mục tiêu

  • Hiểu Fallacies of Distributed Computing (8 giả định sai)
  • CAP theorem - thực sự nghĩa là gì
  • Eventual Consistency - sống chung thế nào
  • Idempotency - tại sao BẮT BUỘC

1) 8 Fallacies - Những Giả Định Sai

Khi viết monolith, bạn NGẦM giả định:

1. Network luôn available     → ❌ Network FAIL (timeout, partition)
2. Latency = zero             → ❌ Network call = 1-100ms (vs in-process = 0.001ms)
3. Bandwidth vô hạn           → ❌ Transfer 10MB JSON giữa services = chậm
4. Network an toàn            → ❌ MITM, sniffing, tampering
5. Topology không đổi         → ❌ Services di chuyển, IP đổi, scale in/out
6. Có 1 admin duy nhất        → ❌ Nhiều team, nhiều services, khác deploy cycle
7. Transport cost = 0         → ❌ Serialize/deserialize, encryption, DNS lookup
8. Network đồng nhất          → ❌ Khác region, khác latency, khác bandwidth

→ Mỗi network call là 1 ĐIỂM FAILURE TIỀM TÀNG
→ Monolith: method call LUÔN thành công (cùng process)
→ Microservice: HTTP call CÓ THỂ fail (bất cứ lúc nào)

2) CAP Theorem - Simplified

CAP: trong distributed system, bạn chỉ chọn được 2 trong 3:

C (Consistency): mọi node trả về data GIỐNG NHAU tại cùng thời điểm
A (Availability): mọi request đều nhận response (không timeout)
P (Partition Tolerance): system hoạt động khi network bị partition

P luôn xảy ra trong distributed systems → bạn thực sự chọn giữa C và A:

CP (Consistent + Partition Tolerant):
  - Khi partition xảy ra → system reject writes (unavailable)
  - Ví dụ: bank account balance (KHÔNG ĐƯỢC sai)
  - Tech: Azure SQL (strong consistency)

AP (Available + Partition Tolerant):
  - Khi partition xảy ra → system vẫn serve, nhưng data có thể stale
  - Ví dụ: product catalog (OK nếu hiện price cũ vài giây)
  - Tech: Cosmos DB (eventual consistency)

Thực tế: Chọn theo USE CASE

STRONG consistency khi:
  ✅ Tiền bạc (balance, transactions)
  ✅ Inventory (stock count → overselling)
  ✅ Auth (quyền thay đổi → phải apply ngay)

EVENTUAL consistency khi:
  ✅ Product catalog (price update delay vài giây = OK)
  ✅ Notifications (email gửi trễ vài giây = OK)
  ✅ Analytics (số liệu delay vài phút = OK)
  ✅ Search index (Elasticsearch sync delay = OK)

3) Eventual Consistency - Cách Sống Chung

Eventual Consistency nghĩa là:
  "Data CUỐI CÙNG sẽ consistent, nhưng CÓ THỂ không consistent NGAY LẬP TỨC."

Ví dụ: User đặt hàng
  1. Order Service: tạo order        (ngay lập tức)
  2. Inventory Service: giảm stock   (sau 200ms)
  3. Email Service: gửi confirmation (sau 2 giây)
  4. Analytics: cập nhật report      (sau 5 phút)

→ Giữa step 1 và step 2: stock CHƯA giảm → có thể oversell!

Strategies

csharp
// Strategy 1: Saga - compensate nếu step sau fail
// Order created → Reserve stock → Charge payment
// Nếu payment fail → Release stock → Cancel order

// Strategy 2: Optimistic UI - hiển thị trước, sync sau 
// "Order placed successfully!" → hiển thị NGAY
// Backend vẫn đang xử lý stock, payment...
// Nếu fail → thông báo sau (email, push notification)

// Strategy 3: Reservation - lock tạm thời
// "Giữ hàng 15 phút" → Stock giảm tạm
// Payment thành công → Confirm reservation
// Timeout → Release reservation

4) Idempotency - Tại Sao BẮT BUỘC

Vấn đề: Network unreliable → retry là BẮT BUỘC
Nhưng retry có thể gọi operation 2+ lần!

Client: "Charge $100" → Network timeout
Client: "Charge $100" → retry
Server: nhận 2 lần → charge $200? 💀

Idempotent = gọi N lần, kết quả = gọi 1 lần.

Cách implement

csharp
// 1. Idempotency Key (phổ biến nhất)
[HttpPost("payments")]
public async Task<IActionResult> ProcessPayment(
    [FromHeader(Name = "Idempotency-Key")] string idempotencyKey,
    PaymentRequest request)
{
    // Check: đã xử lý key này chưa?
    var existing = await db.ProcessedRequests
        .FirstOrDefaultAsync(r => r.Key == idempotencyKey);

    if (existing is not null)
        return Ok(existing.Response); // Trả response cũ, KHÔNG xử lý lại

    // Process payment...
    var result = await ProcessPaymentAsync(request);

    // Lưu kết quả với key
    db.ProcessedRequests.Add(new ProcessedRequest
    {
        Key = idempotencyKey,
        Response = JsonSerializer.Serialize(result),
        ProcessedAt = DateTime.UtcNow
    });
    await db.SaveChangesAsync();

    return Ok(result);
}

// 2. Natural Idempotency (thiết kế operation tự thân idempotent)
// ❌ Non-idempotent: UPDATE balance = balance + 100
// ✅ Idempotent: UPDATE balance = 1100 WHERE version = 5
//   → Gọi 2 lần: lần 2 version đã là 6 → KHÔNG match → KHÔNG update

// 3. Upsert pattern
// ❌ Non-idempotent: INSERT INTO Orders...
// ✅ Idempotent:
//   INSERT INTO Orders (Id, ...) VALUES (@id, ...)
//   ON CONFLICT (Id) DO NOTHING

5) Distributed Transactions - Tại Sao Khó

Monolith:
  BEGIN TRANSACTION
    Debit Account A
    Credit Account B
  COMMIT
  → Cả 2 thành công hoặc cả 2 rollback ✅

Distributed:
  Service A: Debit Account A   ✅
  Network call...
  Service B: Credit Account B  ❌ (service down!)
  → Account A bị trừ, Account B KHÔNG được cộng! 💀
  → KHÔNG CÓ distributed COMMIT!

Solutions (từ simple → complex)

1. Tránh distributed transaction (tốt nhất!)
   → Đặt related operations CÙNG 1 service
   → Monolith hoặc Modular Monolith

2. Saga pattern (phổ biến)
   → Compensating actions: nếu B fail → undo A
   → Eventually consistent

3. Outbox pattern (reliable publishing)
   → Save entity + event trong CÙNG transaction
   → Background worker publish event

4. 2PC - Two Phase Commit (TRÁNH trong microservices)
   → Blocking, single point of failure
   → Chỉ dùng cho databases cùng instance

6) Decision Framework

Bạn đang xem xét tách service? Hãy hỏi:

1. HAI component này CÓ CẦN consistent CÙNG LÚC không?
   → YES → CÙNG service/transaction
   → NO → Tách OK, eventual consistency

2. HAI component có CÙNG deploy cycle không?
   → YES → Có thể giữ cùng service
   → NO → Tách giúp deploy independent

3. HAI component có KHÁC team ownership không?
   → YES → Tách giúp autonomy
   → NO → Không bắt buộc tách

4. Cost of distributed system chấp nhận được?
   → Network failures, eventual consistency, monitoring overhead
   → Nếu không → giữ monolith/modular monolith

Master Checklist ✅

□ Mỗi network call CÓ retry + timeout?
□ Operations idempotent? (retry safe)
□ Eventual consistency chấp nhận được cho use case này?
□ Saga/Compensation cho distributed workflows?
□ Outbox pattern cho reliable event publishing?
□ Circuit breaker cho external calls?
□ Health checks cho service dependencies?
□ Fallback strategy khi dependency down?

Tư duy master: "Đừng distribute cho đến khi bạn PHẢI distribute." Monolith đúng cách tốt hơn microservices sai cách. Khi bạn tách service, hãy hỏi: "Tôi có chấp nhận COST (network failure, eventual consistency, operational complexity) không?"