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ống | Nên Retry? | Lý do |
|---|---|---|
| Network timeout | ✅ Yes | Lỗi kết nối tạm thời |
| 5xx server errors | ✅ Yes | Server có thể quá tải/restart |
| 429 rate limit | ✅ Yes | Chờ reset rồi thử lại |
| 4xx client errors | ❌ No | Retry không sửa được lỗi client |
| 401 auth failures | ❌ No | Sai credential sẽ không tự đúng |
| 404 not found | ❌ No | Resource không tồn tại |
| Business logic errors | ❌ No | Không phải lỗi tạm thời |
Implementation Considerations
1. Không retry non-idempotent operation theo mặc định
// 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 Case | Max Retries | Lý do |
|---|---|---|
| Internal service calls | 3-5 | Cân bằng resilience và latency |
| External API calls | 2-3 | Tôn trọng rate limit bên ngoài |
| User-facing requests | 1-2 | Fail nhanh để UX tốt hơn |
| Background jobs | 5-10 | Chấp nhận delay dài hơn |
3. Luôn có jitter
// Không jitter: tất cả client retry 100ms, 200ms, 400ms...
// Có jitter: retry được dàn đều theo thời gian4. Log retry attempts
logger.info(`Retrying ${operationName}`, {
attempt: attempt + 1,
maxRetries,
delayMs,
error: error.message,
});Jitter Strategies Explained
| Strategy | Công thức | Đặc điểm |
|---|---|---|
| Full Jitter | random(0, base * 2^attempt) | Khuyến nghị mặc định, phân tán tốt |
| Equal Jitter | base * 2^attempt + random(0, base) | Giảm cực đại waiting time |
| Decorrelated Jitter | random(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
- Retry vô hạn
- Retry operation không idempotent
- Không có jitter
- Không có timeout tổng cho cả vòng retry
- 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ẽ:
- Lãng phí tài nguyên (CPU, connection, memory)
- Tăng latency diện rộng
- 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 quaOPEN: chặn request ngay lập tứcHALF-OPEN: cho phép một phần request để test hồi phục
When to Use Circuit Breaker
| Tình huống | Vì sao cần |
|---|---|
| External API calls | API có thể down/throttle |
| Database connections | Tránh cạn pool |
| Microservice dependencies | Tránh lỗi lan truyền |
| Expensive operations | Fail-fast thay vì chờ lâu |
| Rate-limited services | Giảm áp khi bị giới hạn |
Configuration Guidelines
Failure Threshold
| Traffic Pattern | Ngưỡng đề xuất |
|---|---|
| < 10 req/s | 3-5 |
| 10-100 req/s | 5-10 |
| > 100 req/s | 10-20 |
Reset Timeout
| Tình huống | Timeout đề xuất |
|---|---|
| Hồi phục nhanh (restart) | 10-30 giây |
| Bình thường | 30-60 giây |
| Sự cố chậm (DB issues) | 2-5 phút |
Half-Open Success Threshold
| Recommendation | Value |
|---|---|
| Mặc định | 3-5 |
| High-stakes | 5-10 |
| Low-stakes | 1-2 |
Implementation Best Practices
1. Phân loại lỗi đúng
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
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
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
- Quá nhạy, mở mạch do dao động bình thường
- Quá chậm hồi phục
- Không có fallback
- Không monitor
- Phân loại lỗi sai
- Thiếu pha HALF-OPEN
Circuit Breaker vs Retry
| Aspect | Circuit Breaker | Retry |
|---|---|---|
| Mục tiêu | Ngừng gọi service đang hỏng | Thử lại request bị lỗi |
| Phạm vi | Service-level | Request-level |
| Trạng thái | Có state | Thườ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ị:
- Abuse/DDoS
- Quá tải do bug traffic
- Chi phí tăng đột biến
- Mất công bằng tài nguyên
- 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
| Scope | Mô tả | Use Case |
|---|---|---|
| Global | Tất cả request của service | Bảo vệ năng lực tổng |
| Per User | Theo user đã auth | Fair usage |
| Per IP | Theo địa chỉ IP | Guest users, DDoS |
| Per API Key | Theo API key | Quản lý theo gói |
| Per Endpoint | Theo endpoint | Bảo vệ operation đắt |
Algorithm Comparison
| Algorithm | Memory | Precision | Phù hợp |
|---|---|---|---|
| Fixed Window | O(1)/key | Thấp | Dễ triển khai, ít tốn RAM |
| Sliding Window | O(n)/key | Cao | Fairness nghiêm ngặt |
| Token Bucket | O(1)/key | Trung bình | API rate limit, cho burst |
| Leaky Bucket | O(1)/key | Trung bình | Traffic shaping |
Fixed Window Counter
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
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
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ống | Thuật toán | Vì sao |
|---|---|---|
| API rate limit phổ thông | Token Bucket | Mượt và cho burst ngắn |
| Cần fairness nghiêm ngặt | Sliding Window | Chính xác cao |
| Traffic lớn, RAM hạn chế | Fixed Window | Nhẹ nhất |
| Traffic shaping | Leaky Bucket | Tốc độ đầu ra ổn định |
Rate Limiting Headers
| Header | Ví dụ | Mục đích |
|---|---|---|
X-RateLimit-Limit | 100 | Tổng request được phép |
X-RateLimit-Remaining | 97 | Số request còn lại |
X-RateLimit-Reset | 1711838400 | Thời điểm reset |
Retry-After | 60 | Số giây cần chờ khi bị chặn |
Common Pitfalls
- Chọn scope sai
- Không cho burst hợp lý
- Limit quá chặt
- Không trả header hữu ích
- Chỉ lưu in-memory trong distributed system
- 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
Primary Operation
│
├── Success → Return Result
│
└── Failure
│
├── Try Cache → Return Stale Data
│
└── Cache Miss
│
├── Try Secondary Service → Return Result
│
└── Secondary Fails
│
└── Return Safe DefaultFallback Strategies
| Strategy | Mô tả | Ví dụ | Trade-off |
|---|---|---|---|
| Static Fallback | Trả giá trị mặc định | Empty list/default user | Đơn giản nhưng ít thông tin |
| Cached Response | Trả dữ liệu cache | Product data 5 phút trước | Stale nhưng dùng được |
| Feature Disable | Tắt tính năng không critical | Tắt recommendations | Core vẫn chạy |
| Queue for Later | Nhận request, xử lý async | Email sending | Tăng phức tạp async |
| Secondary Service | Dùng service thay thế | Read replica DB | Tăng cost/complexity |
| Partial Response | Trả phần có thể cung cấp | Ẩn giá khi service giá lỗi | Incomplete nhưng hữu ích |
Example: Resilient User Service
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) |
|---|---|
| Authentication | Recommendations |
| Core transactions | Search filters |
| Payment processing | Analytics tracking |
| Data persistence | Real-time notifications |
| Security features | Social sharing |
2. Thiết kế fallback từ đầu
interface ProductCatalog {
getProducts(): Promise<Product[]>;
// Nếu lỗi thì fallback là gì?
}3. Hiển thị trạng thái degrade cho user
interface User {
id: string;
name: string;
stale?: boolean;
isDefault?: boolean;
}4. Theo dõi fallback usage
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
- Silent failure (không báo user đang degraded)
- Fallback phụ thuộc cùng hệ thống đang fail
- Không đánh dấu độ tươi dữ liệu
- Không có fallback path
- Fallback chain quá phức tạp
- 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ụ:
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ì $100The 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.
| Operation | Idempotent? | Lý do |
|---|---|---|
GET /users/1 | ✅ | Đọc không đổi state |
PUT /users/1 | ✅ | Payload giống nhau -> state cuối giống nhau |
DELETE /users/1 | ✅ | Xóa rồi thì vẫn là đã xóa |
POST /orders | ❌ | Tạo order mới mỗi lần |
POST /payments | ❌ | Charge mỗi lần |
Với non-idempotent operation, dùng idempotency key.
Idempotency Key Pattern
Server sẽ:
- Kiểm tra key đã xử lý chưa
- Nếu có, trả lại kết quả đã lưu
- Nếu chưa, xử lý và lưu kết quả theo key
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êmIdempotency Key Design
1. Key Generation
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
| Scope | Ví dụ | Khi dùng |
|---|---|---|
| Global | abc123 | Hệ thống đơn giản |
| Per-user | user123:abc123 | Multi-tenant |
| Per-operation | payment:abc123 | Nhiều loại operation |
| Combined | user123:payment:abc123 | Hệ thống phức tạp |
3. Key Expiration
| Use Case | TTL đề xuất |
|---|---|
| Payments | 24-48 giờ |
| Orders | 7 ngày |
| Email sending | 1 giờ |
| General purpose | 24 giờ |
Implementation
Simple In-Memory Version
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
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
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
POST /payments
Idempotency-Key: abc123
201 Created
X-Idempotency-Key: abc1234. HTTP Methods and Idempotency
| HTTP Method | Idempotent mặc định? | Có nên dùng Idempotency Key? |
|---|---|---|
| GET | ✅ | No |
| HEAD | ✅ | No |
| OPTIONS | ✅ | No |
| PUT | ✅ | Optional |
| DELETE | ✅ | Optional |
| POST | ❌ | Yes |
| PATCH | ❌ | Yes |
Common Pitfalls
- Không persist key
- Không có expiration
- Scope sai gây collision
- Trả response khác nhau trên idempotency hit
- Race condition khi request đồng thời
- 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:
1) Rate Limiting
2) Idempotency Check
3) Circuit Breaker
4) Retry (Backoff + Jitter)
5) Primary Operation + FallbackExample: Resilient Order Service
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
| Pattern | Key Parameters | Typical Values |
|---|---|---|
| Retry | maxRetries, baseDelayMs | 3-5 retries, 100-500ms |
| Circuit Breaker | failureThreshold, resetTimeoutMs | 5-10 failures, 30-60s |
| Rate Limiting | capacity, refillRate | 100-1000 req/window |
| Idempotency | ttlMs | 1-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
- Lỗi là điều chắc chắn - hãy thiết kế để chịu lỗi ngay từ đầu
- Retry + backoff + jitter cho lỗi tạm thời, nhưng không retry non-idempotent operation
- Circuit breaker ngăn cascading failure
- Rate limiting bảo vệ tài nguyên; chọn thuật toán theo trade-off precision/memory
- Graceful fallback: degraded service vẫn tốt hơn outage hoàn toàn
- Idempotency key làm POST/PATCH an toàn khi retry
- Các pattern bổ trợ lẫn nhau theo lớp phòng thủ
- Monitor đầy đủ để tối ưu đúng điểm
- Test failure paths (chaos/failure injection)
- 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".
