System Observability: "Nhìn Thấu" Hệ thống Production

Hãy tưởng tượng bạn vừa deploy model AI lên production. 10 phút sau, người dùng phàn nàn: "Hệ thống chậm quá!". Bạn kiểm tra server - CPU bình thường, RAM OK. Nhưng... tại sao lại chậm?

Không có observability = bạn đang lái xe trong đêm tối, không có đèn pha, không có đồng hồ tốc độ, không có GPS. Có gì đó sai nhưng bạn không biết cái gì, ở đâu, và tại sao.

Observability không chỉ là "theo dõi" - nó là khả năng hiểu trạng thái nội bộ của hệ thống dựa trên outputs bên ngoài. Theo Google SRE: "Monitoring cho bạn biết khi nào có vấn đề. Observability cho bạn biết tại sao có vấn đề đó."

Một nghiên cứu của Datadog cho thấy: Các tổ chức có observability tốt giải quyết sự cố nhanh hơn 60% và giảm thời gian ngừng hoạt động 73% so với những nơi chỉ có monitoring cơ bản.

Ba Trụ Cột của Observability

1. Logs: "Nhật ký" Hệ thống

Logs là gì? Records có timestamp ghi lại các sự kiện rời rạc trong hệ thống.

Ví dụ:

2024-02-02T10:30:15.123Z [INFO] Người dùng 12345 đăng nhập
2024-02-02T10:30:16.456Z [INFO] Yêu cầu dự đoán: user_id=12345, model=fraud_v2
2024-02-02T10:30:16.789Z [ERROR] Database timeout sau 5000ms
2024-02-02T10:30:17.012Z [WARN] Thử lại lần 2/3
2024-02-02T10:30:18.234Z [INFO] Dự đoán thành công: fraud_score=0.23

Các loại Logs:

Log ứng dụng:

import logging

logger = logging.getLogger(__name__)

def predict(features):
    logger.info(f"Bắt đầu dự đoán: user_id={features['user_id']}")
    
    try:
        result = model.predict(features)
        logger.info(f"Dự đoán thành công: score={result}")
        return result
    except Exception as e:
        logger.error(f"Dự đoán thất bại: {e}", exc_info=True)
        raise

Log web server:

192.168.1.1 - - [02/Feb/2024:10:30:15] "POST /api/predict HTTP/1.1" 200 1234 0.456

Log hệ thống:

Feb 2 10:30:15 server1 kernel: Hết bộ nhớ: Kill process 1234 (python) score 856

Mức độ Log:

DEBUG:    Thông tin chi tiết để debug
INFO:     Thông tin chung
WARNING:  Có gì đó bất thường nhưng chưa phải lỗi
ERROR:    Lỗi xảy ra, thao tác thất bại
CRITICAL: Lỗi nghiêm trọng cấp hệ thống

Best Practices:

Structured Logging (JSON):

# ❌ Khó query
logger.info("User 12345 dự đoán xong, score: 0.8, mất 123ms")

# ✅ Dễ query
logger.info("Dự đoán hoàn thành", extra={
    "user_id": 12345,
    "model_version": "v2.1",
    "prediction_score": 0.8,
    "latency_ms": 123,
    "timestamp": "2024-02-02T10:30:15Z"
})

Output dạng JSON:

{
  "level": "INFO",
  "message": "Dự đoán hoàn thành",
  "user_id": 12345,
  "model_version": "v2.1",
  "prediction_score": 0.8,
  "latency_ms": 123,
  "timestamp": "2024-02-02T10:30:15Z"
}

Correlation IDs:

import uuid

def handle_request(request):
    correlation_id = str(uuid.uuid4())
    
    logger.info("Nhận request", extra={
        "correlation_id": correlation_id,
        "endpoint": "/predict"
    })
    
    result = service.process(request, correlation_id)
    
    logger.info("Request hoàn thành", extra={
        "correlation_id": correlation_id,
        "duration_ms": 234
    })

→ Theo dõi request qua nhiều services:

Service A: correlation_id=abc-123
  → Service B: correlation_id=abc-123
    → Service C: correlation_id=abc-123

Log Sampling (với traffic cao):

import random

def log_with_sampling(message, sample_rate=0.1):
    if random.random() < sample_rate:
        logger.info(message)  # Chỉ log 10% requests

Những sai lầm thường gặp:

# ❌ Log dữ liệu nhạy cảm
logger.info(f"Mật khẩu user: {password}")  # TUYỆT ĐỐI KHÔNG!

# ❌ Quá chi tiết trong production
logger.debug(f"Biến x={x}, y={y}, z={z}...")  # Dùng DEBUG ít thôi

# ❌ Không có context
logger.error("Dự đoán thất bại")  # TẠI SAO? User nào? Model nào?

2. Metrics: "Đo lường" Hiệu năng

Metrics là gì? Các phép đo số học được tổng hợp theo thời gian.

Các loại:

Counter: Chỉ tăng (không giảm)

from prometheus_client import Counter

prediction_requests = Counter(
    'prediction_requests_total',
    'Tổng số requests dự đoán',
    ['model_version', 'status']
)

# Tăng counter
prediction_requests.labels(model_version='v2', status='success').inc()
prediction_requests.labels(model_version='v2', status='error').inc()

Dùng cho: Số requests, số lỗi, số jobs hoàn thành

Gauge: Giá trị có thể tăng giảm

from prometheus_client import Gauge

active_connections = Gauge(
    'active_connections',
    'Số kết nối đang active'
)

# Set giá trị
active_connections.set(42)
active_connections.inc()  # 43
active_connections.dec()  # 42

Dùng cho: Memory usage, CPU usage, queue size, số requests đồng thời

Histogram: Phân phối giá trị

from prometheus_client import Histogram

request_duration = Histogram(
    'request_duration_seconds',
    'Thời gian xử lý request',
    buckets=[0.1, 0.5, 1.0, 2.0, 5.0]  # Ngưỡng phân nhóm
)

# Ghi nhận giá trị
with request_duration.time():
    process_request()

Kết quả:

request_duration_seconds_bucket{le="0.1"} 324   # 324 requests < 0.1s
request_duration_seconds_bucket{le="0.5"} 892   # 892 requests < 0.5s
request_duration_seconds_bucket{le="1.0"} 1234  # 1234 requests < 1.0s

Dùng cho: Latency, response time, request size

Summary: Tương tự Histogram nhưng tính percentiles

from prometheus_client import Summary

latency = Summary(
    'prediction_latency_seconds',
    'Thời gian dự đoán'
)

latency.observe(0.234)  # Ghi 234ms

Output:

prediction_latency_seconds_sum 123.45     # Tổng thời gian
prediction_latency_seconds_count 500      # Tổng requests
prediction_latency_seconds{quantile="0.5"} 0.2   # p50 (trung vị)
prediction_latency_seconds{quantile="0.95"} 0.8  # p95
prediction_latency_seconds{quantile="0.99"} 1.2  # p99

Metrics quan trọng cho AI Systems:

Model Metrics:

# Phân phối điểm dự đoán
prediction_score = Histogram('prediction_score', 'Điểm output của model')

# Thời gian inference
inference_duration = Histogram('inference_duration_ms', 'Thời gian inference')

# Theo dõi version model
model_version = Gauge('model_version', 'Version model hiện tại')

System Metrics:

# Request rate
requests_per_second = Counter('http_requests_total')

# Error rate
error_rate = Counter('http_errors_total')

# Resource usage
memory_usage = Gauge('memory_usage_bytes')
cpu_usage = Gauge('cpu_usage_percent')

Business Metrics:

# Tác động doanh thu
revenue_per_request = Summary('revenue_per_request')

# User engagement
active_users = Gauge('active_users')

# Conversion rate
conversions = Counter('conversions_total')

Ngưỡng Cảnh báo:

# Ví dụ: Prometheus alerting rules
groups:
  - name: api_alerts
    rules:
      # Error rate cao
      - alert: HighErrorRate
        expr: |
          rate(http_errors_total[5m]) / rate(http_requests_total[5m]) > 0.05
        for: 5m
        annotations:
          summary: "Tỷ lệ lỗi vượt 5%"
      
      # Latency cao
      - alert: HighLatency
        expr: |
          histogram_quantile(0.95, rate(request_duration_seconds_bucket[5m])) > 2
        for: 10m
        annotations:
          summary: "p95 latency vượt 2 giây"
      
      # Model giảm chất lượng
      - alert: ModelAccuracyDrop
        expr: |
          model_accuracy < 0.85
        for: 30m
        annotations:
          summary: "Độ chính xác model giảm xuống dưới 85%"

3. Traces: "Hành trình" của Request

Traces là gì? Ghi lại hành trình của một request đi qua hệ thống phân tán.

Ví dụ: User gửi request dự đoán

User Request
  ├─ API Gateway (50ms)
  │   ├─ Auth Service (20ms)
  │   └─ Rate Limiter (5ms)
  ├─ Prediction Service (150ms)
  │   ├─ Feature Store (30ms)
  │   ├─ Model Inference (100ms)  ← Nút thắt cổ chai!
  │   └─ Cache Check (20ms)
  └─ Logging Service (10ms)

Tổng: 235ms

Distributed Tracing:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider

# Setup tracer
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)

def predict(user_id, features):
    # Bắt đầu span
    with tracer.start_as_current_span("predict") as span:
        span.set_attribute("user_id", user_id)
        
        # Child span: lấy features
        with tracer.start_as_current_span("fetch_features"):
            features = feature_store.get(user_id)
        
        # Child span: inference
        with tracer.start_as_current_span("model_inference"):
            prediction = model.predict(features)
        
        span.set_attribute("prediction_score", prediction)
        return prediction

Trace Visualization:

┌─────────────────────────────────────────────┐
│ predict (235ms)                             │
├─────────────────────────────────────────────┤
│ ├─ fetch_features (30ms)                    │
│ ├─ model_inference (100ms) ← Chậm!          │
│ └─ cache_write (20ms)                       │
└─────────────────────────────────────────────┘

Thông tin quan trọng trong Traces:

  • Thời gian span: Mỗi thao tác mất bao lâu
  • Dependencies: Service nào gọi service nào
  • Errors: Lỗi xảy ra ở đâu
  • Metadata: Request IDs, user IDs, tham số

Use Cases:

Tìm nút thắt:

Trace cho thấy:
- API Gateway: 50ms
- Auth: 20ms
- Model Inference: 2000ms ← Vấn đề ở đây!
- Database: 30ms

→ Tập trung optimize model inference

Debug lỗi:

Request thất bại tại:
Service A → Service B → Service C [ERROR: timeout]

Trace cho thấy Service C đợi database 30 giây
→ Database là nguyên nhân gốc

Hiểu dependencies:

Prediction service phụ thuộc vào:
- Feature Store (quan trọng)
- Cache (optional, có thể fallback)
- Logging (async, không chặn)

Distributed Tracing trong Microservices

Vấn đề

Monolith:

Request → Application → Database → Response
           ↑ Dễ trace

Microservices:

Request → API Gateway
          ├→ Auth Service
          │   └→ User DB
          ├→ Prediction Service
          │   ├→ Feature Store
          │   │   └→ Redis Cache
          │   ├→ Model Server
          │   └─→ Result Cache
          └→ Logging Service
              └→ ElasticSearch

Làm sao trace flow này? 🤯

Giải pháp: Trace Context Propagation

Khái niệm chính: Truyền trace IDspan ID qua tất cả services.

# Service A
def call_service_b(request):
    trace_id = request.headers.get('X-Trace-ID') or generate_trace_id()
    span_id = generate_span_id()
    
    # Truyền sang Service B
    headers = {
        'X-Trace-ID': trace_id,
        'X-Parent-Span-ID': span_id
    }
    
    response = requests.post('http://service-b/api', headers=headers)
    return response

# Service B
def handle_request(request):
    trace_id = request.headers.get('X-Trace-ID')
    parent_span_id = request.headers.get('X-Parent-Span-ID')
    
    # Tạo child span
    span = create_span(trace_id, parent_span_id)
    
    # Xử lý request
    result = process()
    
    span.finish()
    return result

Kết quả: Tất cả spans được liên kết bởi trace_id → nhìn thấy toàn bộ flow request.

Sampling Strategies

Vấn đề: Trace mọi request = quá nhiều data (tốn kém, choáng ngợp).

Giải pháp:

Head-based Sampling:

import random

def should_trace():
    return random.random() < 0.1  # Trace 10% requests

Tail-based Sampling:

Thu thập TẤT CẢ traces ban đầu → Chỉ giữ:
- Errors (100%)
- Requests chậm (>1s) (100%)
- Requests bình thường (5%)

Adaptive Sampling:

def get_sample_rate():
    if error_rate > 0.05:
        return 1.0  # Trace 100% khi có sự cố
    elif cpu_usage > 0.8:
        return 0.01  # Trace 1% khi hệ thống stress
    else:
        return 0.1  # Bình thường: 10%

Best Practices về Observability

1. The Golden Signals (Google SRE)

Theo dõi 4 chỉ số này cho mọi service:

Latency: Requests mất bao lâu

p50, p95, p99 latency
Mục tiêu: p95 < 200ms

Traffic: Số lượng requests

Requests per second
Mục tiêu: Xử lý được 1000 req/s

Errors: Số requests thất bại

Error rate = lỗi / tổng requests
Mục tiêu: < 0.1% (99.9% thành công)

Saturation: Hệ thống "đầy" đến mức nào

CPU usage, Memory usage, Queue depth
Mục tiêu: < 80% capacity

2. Service Level Objectives (SLOs)

Định nghĩa mục tiêu rõ ràng:

SLO:
  availability: 99.9%  # Tối đa ngừng: 43 phút/tháng
  latency_p95: 200ms
  error_rate: 0.1%
  throughput: 1000 req/s

Error Budget:

SLO = 99.9% uptime
→ Error budget = 0.1% = 43 phút downtime/tháng

Nếu hết error budget:
- Dừng release features mới
- Tập trung vào reliability
- Họp post-mortem

3. Dashboards

Tạo dashboard theo vai trò:

Operations Dashboard:

- Request rate (1 giờ qua)
- Error rate (1 giờ qua)
- p95 latency (1 giờ qua)
- Cảnh báo đang active
- Tình trạng services

Business Dashboard:

- Số user active hàng ngày
- Doanh thu theo giờ
- Tỷ lệ chuyển đổi
- Phân phối điểm dự đoán model

Engineering Dashboard:

- Tần suất deploy
- Thời gian phục hồi trung bình (MTTR)
- Tỷ lệ thất bại khi thay đổi
- Lead time cho changes

4. Triết lý Alerting

Alerts phải:

Actionable: Tôi có thể fix ngay
Urgent: Cần xử lý bây giờ
Specific: Tôi biết chính xác vấn đề gì

Tránh alert fatigue:

# ❌ ALERT TỆ
"CPU usage > 50%"  → Không khẩn, xảy ra suốt

# ✅ ALERT TỐT
"p95 latency > 2s trong 10 phút VÀ error rate > 1%"
→ Khẩn cấp, có thể hành động, cụ thể

Mức độ Cảnh báo:

P0 (Critical): Service down, tất cả users bị ảnh hưởng
  → Gọi on-call engineer ngay
  → Auto-rollback nếu do deploy gần đây

P1 (High): Hiệu năng giảm, một số users bị ảnh hưởng
  → Alert on-call, phản hồi trong 15 phút
  
P2 (Medium): Vấn đề nhỏ, có workaround
  → Tạo ticket, fix trong giờ hành chính
  
P3 (Low): Vấn đề cosmetic, không ảnh hưởng user
  → Backlog item

5. Xử lý Sự cố

Khi cảnh báo kêu:

Bước 1: Xác nhận

Nhận alert → Xác nhận → Ngăn duplicate pages

Bước 2: Đánh giá

- Cái gì bị hỏng? (kiểm tra dashboards, logs, traces)
- Bao nhiêu users bị ảnh hưởng?
- Có đang tệ hơn không?

Bước 3: Giảm thiểu

Fix tạm thời để khôi phục service:
- Rollback deployment
- Scale up resources
- Bật circuit breaker
- Chuyển hướng traffic

Bước 4: Phân tích Nguyên nhân Gốc

- Điều gì trigger sự cố?
- Tại sao monitoring không phát hiện sớm hơn?
- Làm sao ngăn tái diễn?

Bước 5: Post-mortem

Post-mortem không đổ lỗi:
- Timeline các sự kiện
- Nguyên nhân gốc
- Action items
- Tác động đến SLO

Hệ sinh thái Công cụ Observability

Quản lý Log:

  • ELK Stack: Elasticsearch, Logstash, Kibana
  • Loki: Log aggregation của Grafana
  • CloudWatch Logs: AWS native

Metrics:

  • Prometheus: Open-source, pull-based
  • Grafana: Visualization
  • Datadog: Commercial, full-stack
  • New Relic: APM + Metrics

Tracing:

  • Jaeger: Open-source distributed tracing
  • Zipkin: Thay thế cho Jaeger
  • OpenTelemetry: Standard cho instrumentation

All-in-one:

  • Datadog: Logs + Metrics + Traces + APM
  • New Relic: Platform observability đầy đủ
  • Dynatrace: Observability powered by AI

Key Takeaways

  • Ba Trụ Cột: Logs (điều gì xảy ra), Metrics (bao nhiêu), Traces (tại sao chậm)
  • Structured logging: Format JSON, correlation IDs, levels phù hợp
  • Key metrics: Golden Signals (Latency, Traffic, Errors, Saturation)
  • Distributed tracing: Theo dõi requests qua microservices với trace IDs
  • SLOs: Định nghĩa mục tiêu rõ ràng (99.9% uptime, p95 < 200ms)
  • Alerting: Actionable, urgent, specific - tránh alert fatigue
  • Sampling: Đừng trace mọi thứ - dùng adaptive strategies
  • Dashboards: Theo vai trò (Ops, Business, Engineering)
  • Xử lý sự cố: Xác nhận → Đánh giá → Giảm thiểu → RCA → Post-mortem

Nhớ rằng:

Monitoring: "Nó có bị hỏng không?"
Observability: "Tại sao nó bị hỏng?"

Trong bài tiếp theo, chúng ta sẽ khám phá Agile & Project Management - cách quản lý ML projects hiệu quả trong môi trường không chắc chắn.


Bài viết thuộc series "From Zero to AI Engineer" - Module 10: Scalability & Observability