Skip to content

Reliability Patterns

Xây dựng hệ thống bền vững đòi hỏi các mẫu thiết kế xử lý lỗi một cách có kiểm soát. Trong hệ thống phân tán, lỗi là điều chắc chắn xảy ra: network gián đoạn, service quá tải, database chậm, hoặc bug lọt lên production. Câu hỏi không phải là "có lỗi hay không", mà là hệ thống của bạn chịu lỗi tốt đến đâu.

Tài liệu này tổng hợp các reliability pattern quan trọng mà backend engineer nên nắm để giữ hệ thống hoạt động ngay cả khi có sự cố.


Retry with Exponential Backoff

The Problem

Trong distributed systems, nhiều lỗi là transient (tạm thời): packet bị rơi, DB cạn kết nối trong chốc lát, service phụ thuộc đang restart. Nếu retry ngay lập tức, bạn có thể làm vấn đề tệ hơn.

Ví dụ 1,000 client đồng thời gọi một service đang quá tải. Service trả lỗi, và cả 1,000 client retry ngay lập tức. Kết quả là service nhận thêm áp lực và tiếp tục fail, tạo ra thundering herd.

The Solution

Exponential backoff tăng thời gian chờ theo cấp số nhân giữa các lần retry (100ms, 200ms, 400ms, 800ms...). Điều này cho downstream có thời gian hồi phục và giảm tải tức thời.

Jitter thêm ngẫu nhiên vào thời gian chờ để tránh retry đồng pha. Không có jitter, các client có cùng cấu hình sẽ bắn retry gần như cùng thời điểm.

When to Use Retry

Tình huốngNên Retry?Lý do
Network timeout✅ YesLỗi kết nối tạm thời
5xx server errors✅ YesServer có thể quá tải/restart
429 rate limit✅ YesChờ reset rồi thử lại
4xx client errors❌ NoRetry không sửa được lỗi client
401 auth failures❌ NoSai credential sẽ không tự đúng
404 not found❌ NoResource không tồn tại
Business logic errors❌ NoKhông phải lỗi tạm thời

Implementation Considerations

1. Không retry non-idempotent operation theo mặc định

typescript
// BAD: có thể charge 2 lần
await retry(() => paymentService.charge(customerId, amount));

// GOOD: retry khi có idempotency key
await retry(() =>
  paymentService.charge(customerId, amount, {
    idempotencyKey: generateIdempotencyKey(),
  }),
);

2. Đặt giới hạn retry hợp lý

Use CaseMax RetriesLý do
Internal service calls3-5Cân bằng resilience và latency
External API calls2-3Tôn trọng rate limit bên ngoài
User-facing requests1-2Fail nhanh để UX tốt hơn
Background jobs5-10Chấp nhận delay dài hơn

3. Luôn có jitter

typescript
// Không jitter: tất cả client retry 100ms, 200ms, 400ms...
// Có jitter: retry được dàn đều theo thời gian

4. Log retry attempts

typescript
logger.info(`Retrying ${operationName}`, {
  attempt: attempt + 1,
  maxRetries,
  delayMs,
  error: error.message,
});

Jitter Strategies Explained

StrategyCông thứcĐặc điểm
Full Jitterrandom(0, base * 2^attempt)Khuyến nghị mặc định, phân tán tốt
Equal Jitterbase * 2^attempt + random(0, base)Giảm cực đại waiting time
Decorrelated Jitterrandom(base, min(cap, base * 3 * rand))Adaptive, phù hợp chạy dài

Full Jitter thường là lựa chọn mặc định tốt nhất vì đơn giản và hiệu quả.

Common Pitfalls

  1. Retry vô hạn
  2. Retry operation không idempotent
  3. Không có jitter
  4. Không có timeout tổng cho cả vòng retry
  5. Retry cả lỗi phía client

Production Checklist

  • ✓ Chỉ retry lỗi transient (5xx, timeout, 429)
  • ✓ Dùng exponential backoff
  • ✓ Có jitter
  • ✓ Giới hạn số lần retry
  • ✓ Có overall timeout
  • ✓ Đảm bảo operation idempotent
  • ✓ Ghi log retry đầy đủ context
  • ✓ Theo dõi retry rate qua metrics

Circuit Breaker

The Problem

Khi downstream bị down hoặc degraded nặng, việc tiếp tục gọi đến nó sẽ:

  1. Lãng phí tài nguyên (CPU, connection, memory)
  2. Tăng latency diện rộng
  3. Gây cascading failure

The Solution

Circuit Breaker theo dõi lỗi và "ngắt mạch" khi vượt ngưỡng, từ đó fail-fast thay vì chờ timeout.

How It Works

Circuit breaker có 3 trạng thái:

  • CLOSED: bình thường, request đi qua
  • OPEN: chặn request ngay lập tức
  • HALF-OPEN: cho phép một phần request để test hồi phục

When to Use Circuit Breaker

Tình huốngVì sao cần
External API callsAPI có thể down/throttle
Database connectionsTránh cạn pool
Microservice dependenciesTránh lỗi lan truyền
Expensive operationsFail-fast thay vì chờ lâu
Rate-limited servicesGiảm áp khi bị giới hạn

Configuration Guidelines

Failure Threshold

Traffic PatternNgưỡng đề xuất
< 10 req/s3-5
10-100 req/s5-10
> 100 req/s10-20

Reset Timeout

Tình huốngTimeout đề xuất
Hồi phục nhanh (restart)10-30 giây
Bình thường30-60 giây
Sự cố chậm (DB issues)2-5 phút

Half-Open Success Threshold

RecommendationValue
Mặc định3-5
High-stakes5-10
Low-stakes1-2

Implementation Best Practices

1. Phân loại lỗi đúng

typescript
class CircuitBreaker {
  private shouldCountAsFailure(error: Error): boolean {
    if (error instanceof TimeoutError) return true;
    if (error instanceof NetworkError) return true;
    if (error.response?.status >= 500) return true;

    if (error.response?.status === 404) return false;
    if (error.response?.status === 401) return false;

    return true;
  }
}

2. Có fallback khi OPEN

typescript
try {
  return await circuitBreaker.execute(() => fetchRecommendations());
} catch (error) {
  if (error.message.includes('Circuit breaker is OPEN')) {
    return await getCachedRecommendations();
  }
  throw error;
}

3. Theo dõi state qua metrics

typescript
metrics.gauge('circuit_breaker.state', { service: 'payment-gateway' }, state);
metrics.counter('circuit_breaker.failures', { service: 'payment-gateway' }).increment();
metrics.counter('circuit_breaker.successes', { service: 'payment-gateway' }).increment();

Common Pitfalls

  1. Quá nhạy, mở mạch do dao động bình thường
  2. Quá chậm hồi phục
  3. Không có fallback
  4. Không monitor
  5. Phân loại lỗi sai
  6. Thiếu pha HALF-OPEN

Circuit Breaker vs Retry

AspectCircuit BreakerRetry
Mục tiêuNgừng gọi service đang hỏngThử lại request bị lỗi
Phạm viService-levelRequest-level
Trạng tháiCó stateThường stateless
Dùng cùng nhau?✅ Yes✅ Yes

Production Checklist

  • ✓ Chọn ngưỡng lỗi phù hợp traffic
  • ✓ Cấu hình reset timeout hợp lý
  • ✓ Có HALF-OPEN
  • ✓ Phân biệt service errors và client errors
  • ✓ Có fallback
  • ✓ Expose metrics + log state transitions
  • ✓ Test kịch bản trip/recovery

Rate Limiting

The Problem

Không có rate limit, service dễ bị:

  1. Abuse/DDoS
  2. Quá tải do bug traffic
  3. Chi phí tăng đột biến
  4. Mất công bằng tài nguyên
  5. Gây áp lực lên downstream

The Solution

Rate limiting kiểm soát tần suất request theo window để bảo vệ tài nguyên và đảm bảo fairness.

Rate Limiting Scope

ScopeMô tảUse Case
GlobalTất cả request của serviceBảo vệ năng lực tổng
Per UserTheo user đã authFair usage
Per IPTheo địa chỉ IPGuest users, DDoS
Per API KeyTheo API keyQuản lý theo gói
Per EndpointTheo endpointBảo vệ operation đắt

Algorithm Comparison

AlgorithmMemoryPrecisionPhù hợp
Fixed WindowO(1)/keyThấpDễ triển khai, ít tốn RAM
Sliding WindowO(n)/keyCaoFairness nghiêm ngặt
Token BucketO(1)/keyTrung bìnhAPI rate limit, cho burst
Leaky BucketO(1)/keyTrung bìnhTraffic shaping

Fixed Window Counter

typescript
class FixedWindowRateLimiter {
  private counters: Map<string, { count: number; windowStart: number }> = new Map();

  isAllowed(key: string, maxRequests: number, windowMs: number): boolean {
    const now = Date.now();
    const current = this.counters.get(key);

    if (!current || now - current.windowStart >= windowMs) {
      this.counters.set(key, { count: 1, windowStart: now });
      return true;
    }

    if (current.count < maxRequests) {
      current.count++;
      return true;
    }

    return false;
  }
}

Ưu điểm: đơn giản, nhanh. Nhược điểm: burst ở biên window.

Sliding Window Log

typescript
class SlidingWindowRateLimiter {
  private timestamps: Map<string, number[]> = new Map();

  isAllowed(key: string, maxRequests: number, windowMs: number): boolean {
    const now = Date.now();
    const windowStart = now - windowMs;

    let timestamps = this.timestamps.get(key) || [];
    timestamps = timestamps.filter((ts) => ts > windowStart);

    if (timestamps.length < maxRequests) {
      timestamps.push(now);
      this.timestamps.set(key, timestamps);
      return true;
    }

    return false;
  }
}

Ưu điểm: chính xác. Nhược điểm: tốn bộ nhớ hơn.

Token Bucket

typescript
class TokenBucketRateLimiter {
  private tokens: Map<string, { count: number; lastRefill: number }> = new Map();

  constructor(
    private readonly capacity: number,
    private readonly refillRate: number,
    private readonly refillInterval: number = 1000,
  ) {}

  isAllowed(key: string, tokensNeeded: number = 1): boolean {
    const now = Date.now();
    let state = this.tokens.get(key);

    if (!state) {
      state = { count: this.capacity, lastRefill: now };
      this.tokens.set(key, state);
    }

    const elapsed = now - state.lastRefill;
    if (elapsed >= this.refillInterval) {
      const tokensToAdd = Math.floor((elapsed / this.refillInterval) * this.refillRate);
      state.count = Math.min(this.capacity, state.count + tokensToAdd);
      state.lastRefill = now;
    }

    if (state.count >= tokensNeeded) {
      state.count -= tokensNeeded;
      return true;
    }

    return false;
  }
}

Ưu điểm: O(1), mượt, cho burst ngắn. Nhược điểm: cần tuning cẩn thận.

Choosing the Right Algorithm

Tình huốngThuật toánVì sao
API rate limit phổ thôngToken BucketMượt và cho burst ngắn
Cần fairness nghiêm ngặtSliding WindowChính xác cao
Traffic lớn, RAM hạn chếFixed WindowNhẹ nhất
Traffic shapingLeaky BucketTốc độ đầu ra ổn định

Rate Limiting Headers

HeaderVí dụMục đích
X-RateLimit-Limit100Tổng request được phép
X-RateLimit-Remaining97Số request còn lại
X-RateLimit-Reset1711838400Thời điểm reset
Retry-After60Số giây cần chờ khi bị chặn

Common Pitfalls

  1. Chọn scope sai
  2. Không cho burst hợp lý
  3. Limit quá chặt
  4. Không trả header hữu ích
  5. Chỉ lưu in-memory trong distributed system
  6. Không xử lý tốt boundary condition

Production Checklist

  • ✓ Chọn scope đúng
  • ✓ Chọn algorithm theo yêu cầu thực tế
  • ✓ Dùng Redis/persistent storage cho distributed system
  • ✓ Trả đầy đủ rate-limit headers
  • ✓ Log vi phạm để phát hiện abuse
  • ✓ Theo dõi hiệu quả rate limiting

Graceful Failure and Fallback

The Problem

Nhiều hệ thống khi lỗi sẽ trả error cứng và làm trải nghiệm người dùng tệ đi nhanh chóng. Thực tế production thường gặp partial failure; một dependency hỏng không nên kéo sập toàn bộ hệ thống.

The Solution

Graceful failure: hệ thống vẫn cung cấp giá trị dù bị suy giảm chức năng.

Fallback: có phương án thay thế khi đường chính thất bại.

The Fallback Hierarchy

text
Primary Operation

        ├── Success → Return Result

        └── Failure

                ├── Try Cache → Return Stale Data

                └── Cache Miss

                        ├── Try Secondary Service → Return Result

                        └── Secondary Fails

                                └── Return Safe Default

Fallback Strategies

StrategyMô tảVí dụTrade-off
Static FallbackTrả giá trị mặc địnhEmpty list/default userĐơn giản nhưng ít thông tin
Cached ResponseTrả dữ liệu cacheProduct data 5 phút trướcStale nhưng dùng được
Feature DisableTắt tính năng không criticalTắt recommendationsCore vẫn chạy
Queue for LaterNhận request, xử lý asyncEmail sendingTăng phức tạp async
Secondary ServiceDùng service thay thếRead replica DBTăng cost/complexity
Partial ResponseTrả phần có thể cung cấpẨn giá khi service giá lỗiIncomplete nhưng hữu ích

Example: Resilient User Service

typescript
class ResilientUserService implements UserService {
  constructor(
    private primary: UserService,
    private cache: CacheService,
    private fallback: UserService,
    private metrics: MetricsService,
  ) {}

  async getUser(id: string): Promise<User> {
    try {
      const user = await this.primary.getUser(id);
      await this.cache.set(`user:${id}`, user, { ttl: 300 });
      return user;
    } catch (primaryError) {
      this.metrics.increment('user_service.primary_failure');

      try {
        const cached = await this.cache.get<User>(`user:${id}`);
        if (cached) {
          this.metrics.increment('user_service.cache_hit');
          cached.stale = true;
          return cached;
        }
      } catch (cacheError) {
        this.metrics.increment('user_service.cache_failure');
      }

      try {
        const user = await this.fallback.getUser(id);
        this.metrics.increment('user_service.fallback_success');
        return user;
      } catch (fallbackError) {
        this.metrics.increment('user_service.fallback_failure');
        return this.getDefaultUser(id);
      }
    }
  }

  private getDefaultUser(id: string): User {
    return {
      id,
      name: 'Guest User',
      isDefault: true,
      isAnonymous: true,
    };
  }
}

Principles of Graceful Degradation

1. Tách rõ critical vs non-critical paths

Critical (phải chạy)Non-Critical (có thể degrade)
AuthenticationRecommendations
Core transactionsSearch filters
Payment processingAnalytics tracking
Data persistenceReal-time notifications
Security featuresSocial sharing

2. Thiết kế fallback từ đầu

typescript
interface ProductCatalog {
  getProducts(): Promise<Product[]>;
  // Nếu lỗi thì fallback là gì?
}

3. Hiển thị trạng thái degrade cho user

typescript
interface User {
  id: string;
  name: string;
  stale?: boolean;
  isDefault?: boolean;
}

4. Theo dõi fallback usage

typescript
if (metrics.get('user_service.cache_hit').rate > 0.5) {
  alert('Primary user service is degraded! 50% requests served from cache.');
}

Real-World Examples

  • Netflix: recommendation lỗi thì fallback sang trending/history/curated collections.
  • Amazon: inventory service lỗi vẫn cho add-to-cart, xác nhận lại ở checkout.
  • Slack: lưu message pending và đồng bộ lại khi service phục hồi.

Common Pitfalls

  1. Silent failure (không báo user đang degraded)
  2. Fallback phụ thuộc cùng hệ thống đang fail
  3. Không đánh dấu độ tươi dữ liệu
  4. Không có fallback path
  5. Fallback chain quá phức tạp
  6. Không log/monitor fallback usage

Production Checklist

  • ✓ Xác định critical/non-critical features
  • ✓ Thiết kế fallback cho mọi operation quan trọng
  • ✓ Dùng cache như fallback lớp đầu
  • ✓ Có safe default cho user-facing data
  • ✓ Đánh dấu response degraded
  • ✓ Monitor và alert fallback usage cao
  • ✓ Test fallback path định kỳ

Idempotency

The Problem

Trong môi trường network không ổn định, request có thể timeout, response có thể mất, hoặc nhận 5xx. Client sẽ retry. Nếu operation không idempotent, retry có thể tạo side effect trùng lặp.

Ví dụ:

text
Client → Server: POST /charge $100 (timeout)
Client → Server: POST /charge $100 (timeout)
Client → Server: POST /charge $100 (success)

Kết quả: Khách bị charge $300 thay vì $100

The Solution

Operation gọi là idempotent khi thực thi nhiều lần cho cùng input vẫn ra cùng kết quả cuối như chạy một lần.

OperationIdempotent?Lý do
GET /users/1Đọc không đổi state
PUT /users/1Payload giống nhau -> state cuối giống nhau
DELETE /users/1Xóa rồi thì vẫn là đã xóa
POST /ordersTạo order mới mỗi lần
POST /paymentsCharge mỗi lần

Với non-idempotent operation, dùng idempotency key.

Idempotency Key Pattern

Server sẽ:

  1. Kiểm tra key đã xử lý chưa
  2. Nếu có, trả lại kết quả đã lưu
  3. Nếu chưa, xử lý và lưu kết quả theo key
text
Request 1: POST /payments
Headers: Idempotency-Key: abc123
Body: { amount: 100 }
→ Process payment, lưu result theo key abc123

Request 2 (retry): cùng key abc123
→ Trả lại result đã lưu, không charge thêm

Idempotency Key Design

1. Key Generation

typescript
const idempotencyKey = crypto.randomUUID();

function generateDeterministicKey(
  userId: string,
  operation: string,
  params: object,
): string {
  const hash = createHash('sha256');
  hash.update(userId);
  hash.update(operation);
  hash.update(JSON.stringify(params));
  return hash.digest('hex');
}

const composedKey = `${userId}:${operation}:${uuid()}`;

Khuyến nghị: ưu tiên UUID từ client.

2. Key Scope

ScopeVí dụKhi dùng
Globalabc123Hệ thống đơn giản
Per-useruser123:abc123Multi-tenant
Per-operationpayment:abc123Nhiều loại operation
Combineduser123:payment:abc123Hệ thống phức tạp

3. Key Expiration

Use CaseTTL đề xuất
Payments24-48 giờ
Orders7 ngày
Email sending1 giờ
General purpose24 giờ

Implementation

Simple In-Memory Version

typescript
class IdempotencyService {
  private results = new Map<string, { result: any; timestamp: number }>();

  async execute<T>(
    key: string,
    operation: () => Promise<T>,
    ttlMs: number = 86400000,
  ): Promise<T> {
    const existing = this.results.get(key);

    if (existing) {
      if (Date.now() - existing.timestamp < ttlMs) {
        console.log(`Returning cached result for idempotency key: ${key}`);
        return existing.result;
      } else {
        this.results.delete(key);
      }
    }

    const result = await operation();
    this.results.set(key, { result, timestamp: Date.now() });

    return result;
  }
}

Database-Persisted Version

typescript
interface IdempotencyRecord {
  key: string;
  result: string;
  createdAt: Date;
  expiresAt: Date;
  requestParams?: string;
}

async function idempotentOperation<T>(
  db: Database,
  key: string,
  operation: () => Promise<T>,
  options: {
    ttlMs?: number;
    verifyParams?: object;
  } = {},
): Promise<T> {
  const { ttlMs = 86400000, verifyParams } = options;
  const now = new Date();

  const existing = await db.idempotencyKeys.findUnique({ where: { key } });

  if (existing) {
    if (existing.expiresAt > now) {
      if (verifyParams && existing.requestParams) {
        const storedParams = JSON.parse(existing.requestParams);
        if (!deepEqual(storedParams, verifyParams)) {
          throw new Error('Idempotency key reused with different parameters');
        }
      }

      console.log(`Returning cached result for idempotency key: ${key}`);
      return JSON.parse(existing.result);
    } else {
      await db.idempotencyKeys.delete({ where: { key } });
    }
  }

  const result = await operation();

  await db.idempotencyKeys.create({
    data: {
      key,
      result: JSON.stringify(result),
      requestParams: verifyParams ? JSON.stringify(verifyParams) : null,
      createdAt: now,
      expiresAt: new Date(now.getTime() + ttlMs),
    },
  });

  return result;
}

Best Practices

1. Trả lại đúng cùng response

Bao gồm status code, headers, body (và metadata cần thiết).

2. Xử lý concurrent requests

typescript
async function executeIdempotently<T>(key: string, operation: () => Promise<T>): Promise<T> {
  return await db.transaction(async (tx) => {
    const existing = await tx.idempotencyKeys.findUnique({ where: { key } });
    if (existing) return JSON.parse(existing.result);

    const result = await operation();
    await tx.idempotencyKeys.create({
      data: { key, result: JSON.stringify(result), expiresAt: ... },
    });
    return result;
  });
}

3. Trả idempotency key trong response

text
POST /payments
Idempotency-Key: abc123

201 Created
X-Idempotency-Key: abc123

4. HTTP Methods and Idempotency

HTTP MethodIdempotent mặc định?Có nên dùng Idempotency Key?
GETNo
HEADNo
OPTIONSNo
PUTOptional
DELETEOptional
POSTYes
PATCHYes

Common Pitfalls

  1. Không persist key
  2. Không có expiration
  3. Scope sai gây collision
  4. Trả response khác nhau trên idempotency hit
  5. Race condition khi request đồng thời
  6. Không verify params khi reuse key

Production Checklist

  • ✓ Dùng idempotency key cho POST/PATCH
  • ✓ Tạo key phía client (UUID)
  • ✓ Persist vào DB
  • ✓ Có TTL phù hợp
  • ✓ Trả đúng cùng response trên hit
  • ✓ Xử lý concurrent bằng transaction/lock
  • ✓ Trả key trong response headers
  • ✓ Theo dõi hit rate và usage

Putting It All Together

The Defense in Depth Approach

Các pattern này mạnh nhất khi dùng theo lớp:

text
1) Rate Limiting
2) Idempotency Check
3) Circuit Breaker
4) Retry (Backoff + Jitter)
5) Primary Operation + Fallback

Example: Resilient Order Service

typescript
class ResilientOrderService {
  private circuitBreaker: CircuitBreaker;
  private rateLimiter: TokenBucketRateLimiter;
  private idempotencyService: IdempotencyService;
  private cacheService: CacheService;
  private fallbackService: OrderService;

  constructor(
    private database: Database,
    private paymentGateway: PaymentGateway,
    private inventoryService: InventoryService,
    private messageQueue: MessageQueue,
  ) {
    this.circuitBreaker = new CircuitBreaker({
      failureThreshold: 5,
      resetTimeoutMs: 30000,
      halfOpenSuccessThreshold: 3,
    });

    this.rateLimiter = new TokenBucketRateLimiter({
      capacity: 100,
      refillRate: 100 / 60,
      refillInterval: 1000,
    });

    this.idempotencyService = new IdempotencyService({ ttlMs: 86400000 });
  }

  async createOrder(request: CreateOrderRequest): Promise<Order> {
    const { idempotencyKey, userId } = request;

    if (!this.rateLimiter.isAllowed(userId, 1)) {
      throw new RateLimitError({
        limit: 100,
        window: '1 minute',
        retryAfter: 60,
      });
    }

    return await this.idempotencyService.execute(
      idempotencyKey,
      async () => await this.processOrder(request),
      { ttlMs: 86400000, verifyParams: request },
    );
  }

  private async processOrder(request: CreateOrderRequest): Promise<Order> {
    const { userId, items, paymentMethod } = request;

    try {
      const inventory = await this.withFallback(
        () => this.inventoryService.reserve(items),
        async () => {
          const available = await this.cacheService.getInventory();
          return this.inventoryService.reserveOptimistically(items, available);
        },
        () => ({ reserved: true, optimistic: true }),
      );

      if (!inventory.reserved) {
        throw new OutOfStockError();
      }

      const payment = await this.circuitBreaker.execute(async () => {
        return await retryWithBackoffAndJitter(
          () =>
            this.paymentGateway.charge({
              amount: this.calculateTotal(items),
              method: paymentMethod,
              userId,
            }),
          { maxRetries: 3, baseDelayMs: 100 },
        );
      });

      const order = await this.database.orders.create({
        userId,
        items,
        paymentId: payment.id,
        status: 'CONFIRMED',
        createdAt: new Date(),
      });

      await this.cacheService.set(`order:${order.id}`, order, { ttl: 300 });

      this.messageQueue.publish('orders.created', { orderId: order.id }).catch((err) => {
        console.error('Failed to publish order event:', err);
      });

      return order;
    } catch (error) {
      if (error instanceof CircuitBreakerOpenError) {
        await this.messageQueue.publish('orders.pending', { request });
        return this.createPendingOrder(request, 'PAYMENT_UNAVAILABLE');
      }

      if (error instanceof PaymentError) {
        throw new OrderCreationError('Payment failed', { code: error.code });
      }

      if (error instanceof DatabaseError) {
        try {
          return await this.fallbackService.createOrder(request);
        } catch (fallbackError) {
          await this.messageQueue.publish('orders.pending', { request });
          return this.createPendingOrder(request, 'SYSTEM_UNAVAILABLE');
        }
      }

      throw error;
    }
  }

  private async withFallback<T>(
    primary: () => Promise<T>,
    fallback: () => Promise<T>,
    defaultValue: () => T,
  ): Promise<T> {
    try {
      return await primary();
    } catch (primaryError) {
      console.error('Primary failed, trying fallback:', primaryError);
      try {
        return await fallback();
      } catch (fallbackError) {
        console.error('Fallback failed, using default:', fallbackError);
        return defaultValue();
      }
    }
  }

  private createPendingOrder(request: CreateOrderRequest, reason: string): Order {
    return {
      id: generateId(),
      userId: request.userId,
      items: request.items,
      status: 'PENDING',
      pendingReason: reason,
      createdAt: new Date(),
    };
  }
}

Configuration Summary

PatternKey ParametersTypical Values
RetrymaxRetries, baseDelayMs3-5 retries, 100-500ms
Circuit BreakerfailureThreshold, resetTimeoutMs5-10 failures, 30-60s
Rate Limitingcapacity, refillRate100-1000 req/window
IdempotencyttlMs1-48 giờ (theo use case)

Monitoring Checklist

  • ✓ Retry rate và success rate
  • ✓ Circuit breaker state transitions
  • ✓ Rate limit violations
  • ✓ Idempotency key hit rate
  • ✓ Fallback usage frequency
  • ✓ End-to-end latency
  • ✓ Error rates theo loại lỗi

Key Takeaways

  1. Lỗi là điều chắc chắn - hãy thiết kế để chịu lỗi ngay từ đầu
  2. Retry + backoff + jitter cho lỗi tạm thời, nhưng không retry non-idempotent operation
  3. Circuit breaker ngăn cascading failure
  4. Rate limiting bảo vệ tài nguyên; chọn thuật toán theo trade-off precision/memory
  5. Graceful fallback: degraded service vẫn tốt hơn outage hoàn toàn
  6. Idempotency key làm POST/PATCH an toàn khi retry
  7. Các pattern bổ trợ lẫn nhau theo lớp phòng thủ
  8. Monitor đầy đủ để tối ưu đúng điểm
  9. Test failure paths (chaos/failure injection)
  10. Document strategy để team vận hành đồng nhất

Một hệ thống "hỏng mềm" luôn đáng tin cậy hơn hệ thống "hỏng cứng".

Được phát hành theo giấy phép MIT.