Home > Blog > tech

Error Handling คืออะไร? Resilience Patterns สำหรับ Backend ที่ต้องรองรับ Production 2026

error handling resilience patterns guide
Error Handling Resilience Patterns Guide 2026
2026-04-11 | tech | 3500 words

ระบบ Backend ที่ดีไม่ใช่ระบบที่ไม่มี Error แต่เป็นระบบที่จัดการ Error ได้อย่างเหมาะสม ในโลกจริง Network ล่ม Database ช้า Third-party API พัง เซิร์ฟเวอร์หน่วยความจำเต็ม สิ่งเหล่านี้เกิดขึ้นทุกวัน ระบบที่ดีต้องรับมือกับสถานการณ์เหล่านี้ได้โดยไม่ล่มทั้งระบบ

บทความนี้จะพาคุณเรียนรู้ Error Handling ตั้งแต่พื้นฐานไปจนถึง Resilience Patterns ระดับ Production ที่บริษัทอย่าง Netflix, Amazon และ Google ใช้จริงในปี 2026 ครอบคลุม Retry, Circuit Breaker, Bulkhead, Timeout, Fallback และอีกมากมาย

ทำไม Error Handling ถึงสำคัญ?

Graceful Degradation

เมื่อส่วนใดส่วนหนึ่งของระบบล้มเหลว ระบบโดยรวมควรยังคงทำงานได้ในระดับที่ลดลง แทนที่จะพังทั้งหมด ตัวอย่างเช่น ถ้า Recommendation Engine ล่ม เว็บไซต์ E-commerce ควรยังแสดงสินค้ายอดนิยมทั่วไปได้ แทนที่จะแสดง Error Page ทั้งหน้า การออกแบบ Graceful Degradation ต้องคิดล่วงหน้าว่า Feature ไหนเป็น Critical Feature ไหนเป็น Nice-to-have แล้ววางแผนว่าจะทำอย่างไรเมื่อ Nice-to-have ล้มเหลว

User Experience

Error Message ที่ดีต้องบอกผู้ใช้ว่าเกิดอะไรขึ้น และเขาควรทำอย่างไรต่อ "Something went wrong" ไม่ใช่ Error Message ที่ดี แต่ "ไม่สามารถบันทึกข้อมูลได้เนื่องจากเครือข่ายมีปัญหา กรุณาลองอีกครั้ง" คือ Error Message ที่ดี เพราะบอกทั้งสาเหตุและวิธีแก้ไข ใน API ก็เช่นกัน Error Response ต้องมีข้อมูลเพียงพอให้ Developer เข้าใจและแก้ไขปัญหาได้

ประเภทของ Error

การจัดประเภท Error ช่วยให้เราเลือกวิธีจัดการที่เหมาะสม

Recoverable Errors

Error ที่ระบบสามารถกู้คืนได้เอง เช่น Network Timeout ที่ลองใหม่แล้วสำเร็จ Database Connection ที่ขาดชั่วคราวแล้วเชื่อมต่อใหม่ได้ หรือ Rate Limit ที่รอสักพักแล้วลองใหม่ Error ประเภทนี้ควรมี Retry Logic ที่เหมาะสม

Unrecoverable Errors

Error ที่ไม่สามารถกู้คืนได้ด้วยการลองใหม่ เช่น Configuration ผิด, Database Schema ไม่ตรง, Permission Denied ที่ไม่มีสิทธิ์จริงๆ หรือ Out of Memory Error ประเภทนี้ต้อง Log ไว้ แจ้งเตือนทีม และอาจต้อง Shutdown อย่าง Graceful

Transient Errors

Error ชั่วคราวที่เกิดจากสภาวะแวดล้อมไม่เสถียร เช่น Network Glitch, DNS Resolution Failure ชั่วคราว หรือ Upstream Service กำลัง Deploy Error ประเภทนี้มักจะหายไปเองภายในไม่กี่วินาทีถึงนาที เป็นกลุ่มที่เหมาะกับ Retry Pattern มากที่สุด

Error Handling ในภาษาต่างๆ

Go — Error Values

Go ใช้แนวคิด "Errors are values" คือฟังก์ชันคืนค่า Error กลับมาเป็น Return Value ปกติ ไม่ใช่ Exception ทำให้ต้อง Handle Error อย่างชัดเจนทุกจุด

// Go - Error as values
func GetUser(id string) (*User, error) {
    user, err := db.FindByID(id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, fmt.Errorf("user %s not found: %w", id, ErrNotFound)
        }
        return nil, fmt.Errorf("database error: %w", err)
    }
    return user, nil
}

// การใช้งาน
user, err := GetUser("123")
if err != nil {
    if errors.Is(err, ErrNotFound) {
        // Handle 404
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    // Handle 500
    log.Printf("Failed to get user: %v", err)
    http.Error(w, "Internal error", http.StatusInternalServerError)
    return
}
// ใช้ user ปกติ

Rust — Result และ Option

Rust ใช้ Type System บังคับให้จัดการ Error ทุกจุด ผ่าน Result<T, E> และ Option<T> Compiler จะไม่ยอมให้ Compile ถ้าไม่จัดการกรณี Error

// Rust - Result type
fn get_user(id: &str) -> Result<User, AppError> {
    let user = db::find_by_id(id)
        .map_err(|e| AppError::Database(e))?;

    match user {
        Some(u) => Ok(u),
        None => Err(AppError::NotFound(
            format!("User {} not found", id)
        )),
    }
}

// ใช้ ? operator สำหรับ Error Propagation
fn handle_request(id: &str) -> Result<Response, AppError> {
    let user = get_user(id)?;  // ถ้า Error จะ Return ทันที
    let profile = get_profile(&user)?;
    Ok(Response::json(&profile))
}

Python — try/except

Python ใช้ Exception Model แบบดั้งเดิม แต่ควรเขียนให้ Specific ไม่ใช่ catch ทุกอย่าง

# Python - Structured Exception Handling
class UserNotFoundError(Exception):
    def __init__(self, user_id: str):
        self.user_id = user_id
        super().__init__(f"User {user_id} not found")

class DatabaseError(Exception):
    pass

def get_user(user_id: str) -> User:
    try:
        user = db.find_by_id(user_id)
        if user is None:
            raise UserNotFoundError(user_id)
        return user
    except ConnectionError as e:
        raise DatabaseError(f"DB connection failed: {e}") from e

# การใช้งาน - catch เฉพาะ Error ที่คาดหวัง
try:
    user = get_user("123")
except UserNotFoundError:
    return jsonify({"error": "User not found"}), 404
except DatabaseError:
    logger.exception("Database error")
    return jsonify({"error": "Internal error"}), 500

TypeScript

TypeScript สามารถใช้ทั้ง try/catch แบบ JavaScript และ Result Pattern แบบ Functional

// TypeScript - Result Pattern
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

async function getUser(id: string): Promise<Result<User>> {
  try {
    const user = await db.findById(id);
    if (!user) {
      return { success: false, error: new Error(`User ${id} not found`) };
    }
    return { success: true, data: user };
  } catch (err) {
    return { success: false, error: err as Error };
  }
}

// การใช้งาน
const result = await getUser("123");
if (!result.success) {
  // TypeScript รู้ว่า result.error มีอยู่
  console.error(result.error.message);
  return;
}
// TypeScript รู้ว่า result.data เป็น User
console.log(result.data.name);

Structured Error Responses — RFC 7807

RFC 7807 (Problem Details for HTTP APIs) เป็นมาตรฐานสำหรับ Error Response ใน REST API ช่วยให้ Error Response มีรูปแบบที่สอดคล้องกันทุก Endpoint

// RFC 7807 Problem Details
{
  "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient Funds",
  "status": 422,
  "detail": "Your account balance is 30 THB but the transfer requires 50 THB",
  "instance": "/transfers/abc-123",
  "balance": 30,
  "required": 50
}

ข้อดีของ RFC 7807 คือมี Field ที่ชัดเจน type เป็น URI ที่ชี้ไปยัง Documentation ของ Error นั้น title เป็นคำอธิบายสั้น status เป็น HTTP Status Code detail เป็นคำอธิบายเฉพาะเจาะจงสำหรับกรณีนั้น และ instance เป็น URI ที่ระบุ Request ที่เกิด Error ทำให้ Client สามารถ Parse Error ได้อย่างเป็นระบบ

การออกแบบ Error Codes

Error Codes ที่ดีต้องเป็นระบบ สามารถ Parse ได้ด้วย Code และเข้าใจได้โดยคนอ่าน ควรมี Prefix ที่บอกว่า Error มาจากส่วนไหนของระบบ

// Error Code Design
// Format: DOMAIN_CATEGORY_SPECIFIC

// Auth errors
AUTH_TOKEN_EXPIRED    // Token หมดอายุ
AUTH_TOKEN_INVALID    // Token ไม่ถูกต้อง
AUTH_PERMISSION_DENIED // ไม่มีสิทธิ์

// User errors
USER_NOT_FOUND        // ไม่พบผู้ใช้
USER_EMAIL_EXISTS     // Email ซ้ำ
USER_VALIDATION_FAILED // ข้อมูลไม่ถูกต้อง

// Payment errors
PAY_INSUFFICIENT_FUNDS // เงินไม่พอ
PAY_CARD_DECLINED     // บัตรถูกปฏิเสธ
PAY_GATEWAY_ERROR     // Payment Gateway มีปัญหา

Retry Patterns

Retry เป็น Pattern พื้นฐานที่สุดสำหรับจัดการ Transient Errors แต่ต้องทำอย่างถูกวิธี ไม่ใช่แค่วนลูปลองใหม่เรื่อยๆ

Simple Retry

ลองใหม่จำนวนครั้งที่กำหนด โดยมี Delay คงที่ เหมาะสำหรับ Error ที่คาดว่าจะหายไปเร็ว แต่มีข้อเสียคืออาจทำให้ Downstream Service โดน Load มากขึ้นถ้ามี Client หลายตัวลองใหม่พร้อมกัน

Exponential Backoff

เพิ่มเวลา Delay เป็นทวีคูณทุกครั้งที่ลองใหม่ เช่น 1 วินาที, 2 วินาที, 4 วินาที, 8 วินาที ช่วยลด Load ที่ Downstream Service ได้ดีกว่า Simple Retry

Exponential Backoff with Jitter

เพิ่ม Random Jitter (ค่าสุ่ม) เข้าไปใน Delay เพื่อป้องกัน Thundering Herd Problem คือสถานการณ์ที่ Client หลายร้อยตัวลองใหม่พร้อมกันในเวลาเดียวกัน ทำให้ Service ที่กำลังฟื้นตัวถูกถล่มจน Crash อีกรอบ

# Python - Retry with Exponential Backoff + Jitter
import random
import time
from functools import wraps

def retry_with_backoff(
    max_retries: int = 3,
    base_delay: float = 1.0,
    max_delay: float = 60.0,
    retryable_exceptions: tuple = (ConnectionError, TimeoutError)
):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except retryable_exceptions as e:
                    if attempt == max_retries:
                        raise  # หมดจำนวนครั้ง Retry แล้ว
                    # Exponential backoff + jitter
                    delay = min(
                        base_delay * (2 ** attempt) + random.uniform(0, 1),
                        max_delay
                    )
                    logger.warning(
                        f"Attempt {attempt+1} failed: {e}. "
                        f"Retrying in {delay:.1f}s..."
                    )
                    time.sleep(delay)
        return wrapper
    return decorator

@retry_with_backoff(max_retries=3, base_delay=1.0)
def call_external_api(url: str) -> dict:
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response.json()
สำคัญมาก: Retry เฉพาะ Error ที่เป็น Transient เท่านั้น อย่า Retry Error อย่าง 400 Bad Request หรือ 403 Forbidden เพราะลองกี่ครั้งก็ได้ผลลัพธ์เดิม แถมเปลืองทรัพยากรอีก

Circuit Breaker Pattern

Circuit Breaker เป็น Pattern ที่ได้รับแรงบันดาลใจจากวงจรไฟฟ้า เมื่อ Downstream Service ล้มเหลวบ่อยเกินไป Circuit Breaker จะ "ตัดวงจร" หยุดส่ง Request ไปยัง Service นั้นชั่วคราว เพื่อให้ Service มีเวลาฟื้นตัว และป้องกันไม่ให้ระบบของเราถูกลากลงไปด้วย

สถานะ 3 สถานะ

สถานะพฤติกรรมเปลี่ยนเมื่อ
Closed (ปกติ)ส่ง Request ตามปกติ นับจำนวน ErrorError เกิน Threshold จะเปลี่ยนเป็น Open
Open (ตัดวงจร)ปฏิเสธ Request ทันที ไม่ส่งไป Downstreamหลังจากผ่าน Timeout จะเปลี่ยนเป็น Half-Open
Half-Open (ทดสอบ)ปล่อย Request จำนวนน้อยผ่านไปทดสอบถ้าสำเร็จ กลับเป็น Closed / ถ้าล้มเหลว กลับเป็น Open
// TypeScript - Simple Circuit Breaker
class CircuitBreaker {
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  private failureCount = 0;
  private lastFailureTime = 0;

  constructor(
    private readonly threshold: number = 5,
    private readonly timeout: number = 30000, // 30s
    private readonly monitorWindow: number = 60000 // 60s
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      console.warn('Circuit breaker OPENED');
    }
  }
}

// การใช้งาน
const breaker = new CircuitBreaker(5, 30000);
try {
  const data = await breaker.execute(() => fetchFromAPI('/users'));
} catch (err) {
  if (err.message.includes('Circuit breaker')) {
    // ใช้ Fallback Data
    return getCachedData();
  }
  throw err;
}

เครื่องมือ Circuit Breaker

Resilience4j เป็น Library สำหรับ Java/Kotlin ที่ครอบคลุม Resilience Patterns ทั้งหมด รวมถึง Circuit Breaker, Retry, Rate Limiter, Bulkhead และ Time Limiter มี Integration กับ Spring Boot ที่ดีมาก ใช้ Annotation ได้เลย

opossum เป็น Circuit Breaker สำหรับ Node.js ใช้งานง่าย มี Events ให้ Monitor สถานะ และสามารถกำหนด Fallback Function ได้

Bulkhead Pattern

Bulkhead ได้ชื่อมาจากฝาผนังกั้นในเรือ ที่ป้องกันไม่ให้น้ำท่วมลามไปทั้งลำ ในซอฟต์แวร์ Bulkhead คือการแยก Resource Pools ออกจากกัน เพื่อให้ปัญหาในส่วนหนึ่งไม่กระทบส่วนอื่น

ตัวอย่างเช่น ถ้า Application ของคุณเรียกใช้ 3 External Services ได้แก่ Payment, Email และ Analytics แทนที่จะใช้ Thread Pool เดียวกัน ควรแยก Thread Pool แต่ละ Service ถ้า Analytics Service ช้ามาก Thread Pool ของ Analytics จะเต็ม แต่ Thread Pool ของ Payment และ Email ยังทำงานได้ปกติ ถ้าใช้ Pool เดียวกัน Analytics ที่ช้าจะกิน Thread จนหมด ทำให้ Payment ไม่สามารถทำงานได้ด้วย

# Python - Bulkhead with Semaphore
import asyncio

class Bulkhead:
    def __init__(self, name: str, max_concurrent: int):
        self.name = name
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.active = 0

    async def execute(self, fn):
        try:
            await asyncio.wait_for(
                self.semaphore.acquire(), timeout=5.0
            )
        except asyncio.TimeoutError:
            raise BulkheadFullError(
                f"Bulkhead '{self.name}' is full"
            )

        self.active += 1
        try:
            return await fn()
        finally:
            self.active -= 1
            self.semaphore.release()

# แยก Bulkhead แต่ละ Service
payment_bulkhead = Bulkhead("payment", max_concurrent=20)
email_bulkhead = Bulkhead("email", max_concurrent=10)
analytics_bulkhead = Bulkhead("analytics", max_concurrent=5)

# Payment ทำงานได้แม้ Analytics เต็ม
await payment_bulkhead.execute(lambda: process_payment(order))
await analytics_bulkhead.execute(lambda: track_event(event))

Timeout Pattern

ทุก External Call ต้องมี Timeout ไม่มีข้อยกเว้น ถ้าไม่ตั้ง Timeout Request อาจค้างอยู่ตลอดกาล กิน Connection Pool จน Server ล่ม การตั้ง Timeout ที่เหมาะสมต้องพิจารณา P99 Latency ของ Service ที่เรียก บวกกับ Buffer เล็กน้อย

// Go - Context Deadline
func GetUserProfile(ctx context.Context, userID string) (*Profile, error) {
    // ตั้ง Timeout 5 วินาที
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // ทุก Call ในนี้จะถูก Cancel อัตโนมัติเมื่อหมดเวลา
    user, err := userService.Get(ctx, userID)
    if err != nil {
        return nil, err
    }

    // ถ้ายังเหลือเวลา ดึง Orders ต่อ
    orders, err := orderService.GetByUser(ctx, userID)
    if err != nil {
        // Timeout จะ Propagate ผ่าน Context
        return nil, err
    }

    return &Profile{User: user, Orders: orders}, nil
}
กฎทอง: Timeout ที่ดีควรเป็น P99 latency ของ Downstream Service คูณ 2 หรือ 3 เช่น ถ้า Service มี P99 ที่ 200ms ให้ตั้ง Timeout ที่ 500ms ถึง 600ms อย่าตั้ง Timeout ยาวเกินไปเพราะจะทำให้ระบบช้าลงเมื่อ Downstream มีปัญหา

Fallback Pattern

Fallback คือการมีแผนสำรองเมื่อ Primary Path ล้มเหลว เป็นส่วนสำคัญของ Graceful Degradation

Default Values

คืนค่า Default เมื่อไม่สามารถดึงค่าจริงได้ เช่น ถ้าไม่สามารถดึง Configuration จาก Remote Config Service ได้ ให้ใช้ค่า Default ที่ Hardcode ไว้ในตัว

Cache Fallback

ใช้ข้อมูลจาก Cache เมื่อ Primary Data Source ไม่ตอบสนอง แม้ข้อมูลอาจ Stale แต่ยังดีกว่าไม่มีข้อมูลเลย Pattern นี้เรียกว่า "Stale-while-revalidate" ใน Web Caching

Degraded Mode

ลดฟีเจอร์ลงเมื่อระบบบางส่วนล่ม เช่น ปิด Real-time Features แล้วใช้ Polling แทน หรือแสดงข้อมูลแบบ Static แทน Dynamic Content

// TypeScript - Fallback Chain
async function getProductRecommendations(userId: string): Promise<Product[]> {
  // Strategy 1: Personalized recommendations
  try {
    return await recommendationService.getPersonalized(userId);
  } catch (err) {
    logger.warn('Personalized recs failed, trying popular');
  }

  // Strategy 2: Popular products (cached)
  try {
    return await cache.get('popular-products');
  } catch (err) {
    logger.warn('Cache miss for popular products');
  }

  // Strategy 3: Static fallback
  return STATIC_DEFAULT_PRODUCTS;
}

Health Checks

Health Checks เป็นกลไกที่ให้ Load Balancer, Orchestrator เช่น Kubernetes หรือ Monitoring System ตรวจสอบว่า Application ยังทำงานได้ปกติหรือไม่

Liveness Check

ตรวจสอบว่า Process ยังทำงานอยู่หรือไม่ ถ้าล้มเหลว Orchestrator จะ Restart Container เป็นการตรวจสอบพื้นฐาน เช่น HTTP 200 OK จาก /healthz Endpoint

Readiness Check

ตรวจสอบว่า Application พร้อมรับ Traffic หรือยัง รวมถึงการเชื่อมต่อกับ Database, Cache, Dependencies ต่างๆ ถ้าล้มเหลว Load Balancer จะหยุดส่ง Traffic มา แต่จะไม่ Restart

Startup Check

ตรวจสอบว่า Application เริ่มต้นเสร็จสมบูรณ์แล้วหรือยัง เหมาะสำหรับ Application ที่ใช้เวลา Startup นาน เช่น ต้องโหลด ML Model หรือ Warm Cache ก่อน ป้องกันไม่ให้ Liveness Check Kill Container ก่อนที่จะเริ่มต้นเสร็จ

// Express.js - Health Check Endpoints
app.get('/healthz', (req, res) => {
  // Liveness - แค่ตรวจว่า process ยังอยู่
  res.status(200).json({ status: 'alive' });
});

app.get('/readyz', async (req, res) => {
  // Readiness - ตรวจ Dependencies ทั้งหมด
  const checks = {
    database: await checkDatabase(),
    redis: await checkRedis(),
    externalApi: await checkExternalAPI(),
  };

  const allHealthy = Object.values(checks).every(c => c.healthy);
  res.status(allHealthy ? 200 : 503).json({
    status: allHealthy ? 'ready' : 'not ready',
    checks,
    timestamp: new Date().toISOString()
  });
});

Graceful Shutdown

เมื่อ Application ต้องปิดตัวลง ไม่ว่าจะเพราะ Deploy ใหม่ หรือ Scale Down มันต้องปิดอย่างสง่างาม ไม่ใช่ตัด Connection ทิ้งกลางคัน

// Node.js - Graceful Shutdown
process.on('SIGTERM', async () => {
  console.log('SIGTERM received. Starting graceful shutdown...');

  // 1. หยุดรับ Request ใหม่
  server.close();

  // 2. รอ Request ที่กำลังทำงานให้เสร็จ (timeout 30s)
  await Promise.race([
    waitForActiveRequests(),
    new Promise(resolve => setTimeout(resolve, 30000))
  ]);

  // 3. ปิด Database Connection
  await db.close();

  // 4. ปิด Message Queue Connection
  await messageQueue.close();

  console.log('Graceful shutdown complete');
  process.exit(0);
});

ขั้นตอนสำคัญคือ หยุดรับ Request ใหม่ก่อน รอ Request ที่ค้างอยู่ให้เสร็จ แล้วค่อยปิด Connection ต่างๆ ควรมี Timeout สำหรับ Graceful Shutdown ด้วย เพื่อป้องกันไม่ให้ค้างอยู่ตลอดกาล

Dead Letter Queues

เมื่อ Message ใน Queue ไม่สามารถ Process ได้สำเร็จ แม้จะ Retry แล้วหลายครั้ง ไม่ควรทิ้ง Message นั้นไป ควรส่งไปยัง Dead Letter Queue (DLQ) เพื่อเก็บไว้ตรวจสอบภายหลัง

DLQ ช่วยให้ไม่สูญเสียข้อมูล สามารถวิเคราะห์ว่า Message ล้มเหลวเพราะอะไร แก้ไข Bug แล้ว Replay Message กลับเข้า Queue หลักได้ ควรตั้ง Alert เมื่อมี Message เข้า DLQ เพื่อให้ทีมรู้ว่ามีปัญหาที่ต้องแก้ไข

Error Monitoring

การ Handle Error ในโค้ดอย่างเดียวไม่พอ ต้องมีระบบ Monitor และแจ้งเตือนด้วย

Sentry

Sentry เป็น Error Tracking Platform ที่ได้รับความนิยมสูงสุด จับ Error จาก Frontend และ Backend ได้ทั้งหมด แสดง Stack Trace, Breadcrumbs, User Context, Environment Info และ Release Information ช่วยให้ Debug ได้เร็วมาก มี Grouping อัตโนมัติที่รวม Error ที่เหมือนกัน และมี Performance Monitoring ด้วย

Datadog APM

Datadog APM ให้ End-to-end Visibility ของ Request ทั้ง Flow ตั้งแต่ Frontend ผ่าน API Gateway ไปจนถึง Database เห็น Latency ของแต่ละ Hop เมื่อ Error เกิดขึ้น สามารถ Drill Down ไปดูได้ว่าเกิดที่ Service ไหน Span ไหน ช่วยให้แก้ปัญหาใน Distributed System ได้อย่างมีประสิทธิภาพ

Error Budgets — แนวคิด SRE

Error Budget เป็นแนวคิดจาก Site Reliability Engineering ของ Google คือการกำหนดว่าระบบ "อนุญาต" ให้มี Error ได้มากแค่ไหนในช่วงเวลาหนึ่ง เช่น ถ้า SLO (Service Level Objective) คือ 99.9 เปอร์เซ็นต์ Uptime ต่อเดือน Error Budget คือ 0.1 เปอร์เซ็นต์ หรือประมาณ 43 นาทีต่อเดือนที่ระบบสามารถ Down ได้

เมื่อ Error Budget ยังเหลือเยอะ ทีมสามารถ Ship Feature ใหม่ได้อย่างรวดเร็ว เสี่ยงได้มากขึ้น แต่เมื่อ Error Budget ใกล้หมด ทีมต้องหยุด Ship Feature แล้วมาแก้ไข Reliability ก่อน แนวคิดนี้ช่วยสร้างสมดุลระหว่างการพัฒนาฟีเจอร์ใหม่และการรักษาความเสถียรของระบบ

Chaos Engineering

Chaos Engineering คือการทดสอบ Resilience ของระบบโดยการจงใจทำให้เกิด Error ในสภาพแวดล้อมที่ควบคุมได้ Netflix เป็นผู้บุกเบิกด้วยเครื่องมือ Chaos Monkey ที่สุ่ม Kill Instance ใน Production เพื่อทดสอบว่าระบบรับมือได้หรือไม่

ในปี 2026 เครื่องมือ Chaos Engineering มีหลากหลาย เช่น Litmus สำหรับ Kubernetes, Gremlin สำหรับ Multi-cloud, AWS Fault Injection Service สำหรับ AWS การเริ่มต้นไม่จำเป็นต้องทำใน Production สามารถเริ่มใน Staging ก่อน ทดสอบสถานการณ์พื้นฐาน เช่น Network Delay, Container Kill, CPU Stress แล้วค่อยขยายไปทำใน Production เมื่อมั่นใจ

ขั้นตอนพื้นฐานของ Chaos Engineering คือ กำหนด Steady State ของระบบก่อน สร้าง Hypothesis ว่าระบบจะตอบสนองอย่างไร ทำ Experiment ที่ทำให้เกิดความล้มเหลว สังเกตผลลัพธ์ และปรับปรุงระบบจากสิ่งที่เรียนรู้

เริ่มต้น Chaos Engineering: เริ่มจากสิ่งง่ายๆ เช่น ลอง Kill Container หนึ่งตัวแล้วดูว่าระบบ Recover ได้เองหรือไม่ หรือเพิ่ม Latency ให้ Database 2 วินาทีแล้วดูว่า Timeout ทำงานถูกต้องหรือไม่ อย่าเริ่มด้วยการทำ Network Partition ใน Production

สรุป

Error Handling และ Resilience Patterns ไม่ใช่สิ่งที่เพิ่มทีหลัง แต่ต้องคิดตั้งแต่ออกแบบระบบ ระบบ Production ที่ดีต้องมี Retry ที่ถูกวิธี มี Circuit Breaker ป้องกันไม่ให้ Cascading Failure มี Bulkhead แยก Resource มี Timeout ทุก External Call และมี Fallback สำหรับทุก Critical Path

เริ่มต้นด้วยการจัดประเภท Error ในระบบของคุณ ใส่ Structured Error Responses ตั้ง Timeout ทุก External Call เพิ่ม Retry ด้วย Exponential Backoff แล้วค่อยเพิ่ม Circuit Breaker และ Bulkhead เมื่อระบบซับซ้อนขึ้น ใช้ Sentry หรือ Datadog Monitor ทุก Error และอย่าลืมทดสอบ Resilience ด้วย Chaos Engineering จะได้มั่นใจว่าเมื่อเกิดปัญหาจริง ระบบของคุณพร้อมรับมือได้อย่างสง่างาม


Back to Blog | iCafe Forex | SiamLanCard | Siam2R