Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 51 — Capacity Planning & Load Testing

Đã đọc 0/15 mục · còn khoảng 11 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là DevOps Engineer tại một sàn thương mại điện tử Việt Nam. Đêm 11/11 (ngày sale lớn nhất năm), lượng truy cập tăng vọt gấp 12 lần so với ngày thường. Đến 0h05, checkout bắt đầu trả về lỗi 503, database CPU chạm trần 100%, và trong 40 phút hệ thống "sập". Ước tính doanh thu mất khoảng 3,5 tỷ đồng, chưa kể hàng loạt bài đăng bức xúc trên fanpage và uy tín thương hiệu bị tổn hại. Sáng hôm sau, câu hỏi đầu tiên sếp hỏi không phải "ai gây ra lỗi" mà là "tại sao chúng ta không lường trước được tải này?".

Đó chính là lý do Capacity Planning (lập kế hoạch năng lực) và Load Testing (kiểm thử tải) tồn tại. Ở các bài trước bạn đã học cách scale (HPA/VPA ở Bài 25), cách đo lường (Bài 26–28) và định nghĩa mục tiêu tin cậy (SLI/SLO ở Bài 31). Bài này ghép chúng lại thành một câu hỏi kinh doanh cực kỳ thực tế: Hệ thống của tôi chịu được bao nhiêu? Khi nào cần thêm tài nguyên? Và làm sao tôi biết trước điều đó, thay vì phát hiện lúc đang cháy nhà?

Đây là kỹ năng phân biệt một DevOps "biết bấm nút scale" với một DevOps thực sự làm chủ hệ thống ở quy mô lớn.

Khái niệm cốt lõi

Capacity Planning là gì?

Capacity Planning là quá trình dự báo nhu cầu tài nguyên trong tương lai (CPU, RAM, băng thông, số pod, kết nối database...) dựa trên tăng trưởng dự kiến, để đảm bảo hệ thống luôn phục vụ được tải trong khi vẫn tối ưu chi phí. Nó trả lời hai câu hỏi song song:

  • Đủ không? — Có đủ tài nguyên khi tải tăng không (tránh downtime)?
  • Có lãng phí không? — Có đang trả tiền cho tài nguyên nhàn rỗi không (tránh đốt tiền)?
Cân bằng hai câu hỏi này chính là nghệ thuật của capacity planning. Provision quá ít thì sập; provision quá nhiều thì hoá đơn cloud "bốc hơi".

Các chỉ số nền tảng bạn phải nắm

  • Throughput — số request xử lý được mỗi giây (thường viết là RPS — requests per second, hoặc QPS cho query).
  • Latency — độ trễ phản hồi. Luôn nhìn theo percentile: p50 (trung vị), p95, p99. Đừng bao giờ chỉ nhìn "latency trung bình" vì nó che giấu đuôi phân phối. Một hệ thống có p50 = 80ms nghe rất ngon, nhưng p99 = 4s nghĩa là cứ 100 người thì có 1 người đợi 4 giây — đủ để họ bỏ giỏ hàng.
  • Concurrency — số request/kết nối đang xử lý đồng thời.
  • Saturation — mức độ "đầy" của tài nguyên (CPU %, memory %, hàng đợi kết nối DB...).
  • Headroom — phần dư an toàn còn lại trước khi chạm giới hạn. Thông thường ta muốn giữ headroom 30–50% cho tải đột biến.

Little's Law — công cụ tính nhẩm quyền năng

Một công thức bạn nên thuộc lòng:

L = λ × W

Trong đó: L = số request đồng thời trung bình, λ = throughput (req/s), W = latency trung bình (giây).

Ví dụ nhanh: dịch vụ của bạn có latency trung bình 200ms (0,2s) và cần phục vụ 500 req/s. Vậy số request đồng thời cần xử lý là L = 500 × 0,2 = 100. Nếu mỗi worker xử lý được 1 request tại một thời điểm, bạn cần khoảng 100 worker (cộng headroom). Chỉ với phép nhân này, bạn đã ước lượng được cấu hình cần thiết mà không cần đoán mò.

Load Testing — các loại và mục đích

Load Testing là cách bạn "ép" hệ thống trong môi trường kiểm soát để đo giới hạn thực tế thay vì tin vào giả định. Có bốn kiểu chính, dễ nhầm lẫn:

  • Load test — mô phỏng tải kỳ vọng (ví dụ tải giờ cao điểm bình thường) để xác nhận hệ thống đáp ứng SLO.
  • Stress test — đẩy tải vượt xa mức bình thường để tìm điểm gãy (breaking point) và xem hệ thống gãy như thế nào (gãy "duyên dáng" hay sập toàn bộ).
  • Soak test / Endurance test — chạy tải vừa phải trong thời gian dài (6–24h) để phát hiện memory leak, kết nối rò rỉ, log đầy đĩa.
  • Spike test — tăng tải đột ngột theo bậc thang để mô phỏng tình huống flash sale hay viral.

Công cụ phổ biến

  • k6 (Grafana) — viết script bằng JavaScript, nhẹ, hợp CI/CD, rất được ưa chuộng hiện nay.
  • JMeter — lâu đời, giao diện đồ họa, mạnh nhưng nặng.
  • Locust — viết bằng Python, dễ mô phỏng hành vi người dùng phức tạp.
  • Gatling — hiệu năng cao, viết bằng Scala/Java.

Tình huống thực tế

Ví dụ 1 — Sàn TMĐT "ShopViet" và ngày 12/12

ShopViet (công ty giả định, mô hình như Tiki/Sendo) có traffic ngày thường khoảng 800 RPS ở giờ cao điểm. Marketing thông báo campaign 12/12 với ngân sách quảng cáo gấp 5 lần, dự kiến traffic đỉnh 8.000 RPS — tức 10× ngày thường.

Đội DevOps làm capacity planning như sau: họ chạy load test bằng k6 trên môi trường staging giống production. Kết quả cho thấy mỗi pod API xử lý được ~150 RPS trước khi p99 latency vượt 500ms (ngưỡng SLO). Áp dụng Little's Law và trừ headroom 40%, họ tính cần khoảng 8.000 / 150 ≈ 54 pod, làm tròn lên 75 pod để có biên an toàn. Nhưng khi stress test tiếp, họ phát hiện điểm gãy thật sự không nằm ở API — mà ở database: connection pool chỉ có 200 kết nối, và ở 4.500 RPS thì pool cạn, request xếp hàng chờ, latency tăng vọt.

Bài học: Bottleneck (nút thắt cổ chai) hầu như luôn nằm ở tầng bạn ít ngờ tới nhất — thường là database, cache, hoặc một dependency bên thứ ba. Họ đã kịp thêm read replica, tăng connection pool và bật PgBouncer trước ngày sale. Kết quả: 12/12 chạy êm ở đỉnh 7.200 RPS, p99 giữ ở 380ms.

Ví dụ 2 — Startup fintech và memory leak lộ ra sau 9 tiếng

Một startup fintech ở TP.HCM (giả định, gọi là PayFlow) chạy dịch vụ xử lý giao dịch trên Kubernetes. Họ đã load test đầy đủ, mọi thứ pass, và tự tin lên production. Hai tuần sau, cứ mỗi 3–4 ngày các pod lại tự restart hàng loạt vào ban đêm mà không rõ lý do, gây ra vài phút gián đoạn.

Điều tra ra: có một memory leak nhỏ trong thư viện parse JSON — mỗi request rò rỉ vài KB. Ở load test 30 phút thì không đủ thời gian để lộ, nhưng sau 9–10 tiếng chạy thật, RAM chạm limit và Kubernetes OOMKill pod.

Bài học: Load test ngắn không thay thế được soak test. Sau sự cố, PayFlow đưa soak test 12h vào quy trình release cho mọi dịch vụ xử lý tiền, chạy hằng tuần vào cuối tuần. Từ đó không còn sự cố "pod tự restart bí ẩn".

Ví dụ 3 — Grab và bài toán spike lúc trời mưa

Ở quy mô khu vực Đông Nam Á, các nền tảng gọi xe như Grab đối mặt với một dạng spike đặc thù: trời mưa. Khi Sài Gòn hoặc Jakarta đổ mưa lúc tan tầm, nhu cầu đặt xe tăng gấp nhiều lần chỉ trong vài phút. Đây là spike không theo lịch, không thể "chuẩn bị trước theo campaign".

Cách tiếp cận thực tế cho tình huống này không phải cố provision sẵn cho đỉnh cao nhất mọi lúc (quá tốn kém), mà là kết hợp: (1) autoscaling phản ứng nhanh với buffer pod luôn sẵn sàng (warm pool), (2) load test spike để biết hệ thống scale kịp trong bao lâu, và (3) cơ chế graceful degradation — khi quá tải thì ưu tiên chức năng cốt lõi, tạm giảm tính năng phụ.

Bài học: Với tải không dự đoán được, capacity planning chuyển từ "provision tĩnh" sang "thiết kế khả năng co giãn". Bạn không tính ra một con số cố định, mà đảm bảo hệ thống biết cách phình ra đủ nhanh — và load test chính là thứ chứng minh nó làm được.

Hướng dẫn từng bước

Đây là quy trình thực hành bạn có thể áp dụng ngay cho một dịch vụ bất kỳ.

Bước 1 — Xác định SLO và tải mục tiêu. Trước khi test, phải biết đích: ví dụ "p99 latency < 300ms ở 5.000 RPS, tỷ lệ lỗi < 0,1%". Không có mục tiêu thì test xong cũng không biết đậu hay rớt.

Bước 2 — Thu thập baseline từ production. Dùng Prometheus/Grafana lấy số liệu thật: RPS đỉnh hiện tại, tỷ lệ tăng trưởng tháng, RPS mỗi pod. Đây là dữ liệu đầu vào cho dự báo.

Bước 3 — Dự báo nhu cầu. Kết hợp tăng trưởng tự nhiên (organic growth) và các sự kiện đặc biệt (sale, campaign). Áp dụng Little's Law để chuyển throughput mục tiêu thành số tài nguyên cần.

Bước 4 — Viết kịch bản load test. Mô phỏng đúng hành vi người dùng thật, không chỉ gọi một endpoint. Ví dụ với k6:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 200 },   // ramp-up
    { duration: '5m', target: 200 },   // giữ tải ổn định
    { duration: '2m', target: 1000 },  // spike
    { duration: '3m', target: 0 },     // ramp-down
  ],
  thresholds: {
    http_req_duration: ['p(99)<300'],  // gắn SLO vào test
    http_req_failed: ['rate<0.001'],
  },
};

export default function () {
  const res = http.get('https://staging.shopviet.vn/api/products');
  check(res, { 'status 200': (r) => r.status === 200 });
  sleep(1);
}

Bước 5 — Chạy trên môi trường giống production. Staging phải cùng cấu hình, cùng phiên bản, cùng loại instance. Test trên môi trường "mini" cho kết quả sai lệch nguy hiểm.

Bước 6 — Chạy đủ loại test. Load → Stress (tìm điểm gãy) → Soak (bắt leak) → Spike (kiểm tra autoscaling).

Bước 7 — Phân tích và tìm bottleneck. Vừa test vừa nhìn dashboard: khi latency tăng, tài nguyên nào chạm trần trước? CPU? DB connection? Network? Đó là nút thắt cần xử lý.

Bước 8 — Đặt ngưỡng cảnh báo dựa trên kết quả. Nếu biết hệ thống gãy ở 85% CPU, hãy đặt alert ở 70% để có thời gian phản ứng. Đây là cầu nối tới bài Alerting (Bài 50).

Bước 9 — Lặp lại định kỳ. Capacity planning không phải làm một lần. Hệ thống thay đổi, code thay đổi, tải thay đổi. Chạy lại mỗi quý hoặc trước mỗi sự kiện lớn.

Lỗi thường gặp & mẹo

  • Chỉ nhìn latency trung bình. Trung bình che giấu đuôi. Luôn dùng p95/p99. Trải nghiệm tệ nhất mới là thứ khách hàng nhớ.
  • Test trên môi trường không giống production. Staging 2 core test ra "1.000 RPS ngon" rồi production 8 core lại sập vì cấu hình DB, network, cache khác nhau. Kết quả vô nghĩa nếu môi trường lệch.
  • Quên các dependency bên ngoài. Cổng thanh toán, SMS OTP, API bên thứ ba có rate limit riêng. Bạn scale hệ thống lên gấp 10 nhưng cổng thanh toán chỉ cho 100 req/s thì đó mới là trần thật.
  • Bỏ qua soak test. Như ví dụ PayFlow, leak chỉ lộ sau nhiều giờ. Test 5 phút xong tự tin là ảo tưởng.
  • Load test đánh vào production giờ cao điểm. Nếu buộc phải test production, làm lúc thấp điểm và có kế hoạch dừng khẩn cấp. Tốt nhất dùng staging.
  • Tưởng autoscaling là phép màu. Autoscaling cần thời gian để khởi động pod/node. Nếu spike nhanh hơn tốc độ scale, bạn vẫn sập trong "khoảng trống" đó. Load test spike để đo chính xác độ trễ này.
  • Mẹo — dùng "warm pool": giữ sẵn một lượng pod/node đệm để hấp thụ cú spike đầu tiên trong lúc autoscaler kịp thêm tài nguyên.
  • Mẹo — gắn threshold SLO trực tiếp vào script test (như ví dụ k6 trên) để test tự pass/fail trong CI/CD, không cần soi mắt thường.
  • Mẹo — capacity planning là bài toán tiền bạc: kết quả nên trình bày được cho sếp bằng cả hai ngôn ngữ — độ tin cậy và chi phí. Đó là cách bạn được duyệt ngân sách.

Bài tập thực hành

  • Tính nhẩm Little's Law: Dịch vụ của bạn cần phục vụ 1.200 req/s với latency trung bình 150ms. Hỏi: cần bao nhiêu request được xử lý đồng thời? Nếu mỗi pod xử lý được tối đa 30 concurrent request, cần bao nhiêu pod (thêm headroom 40%)?
  • Viết một load test bằng k6 cho một API bất kỳ (có thể dùng một API public như https://test-api.k6.io). Cấu hình 3 stage: ramp-up 1 phút lên 50 VU, giữ 3 phút, ramp-down 1 phút. Gắn threshold p(99)<500 và tỷ lệ lỗi <1%. Chạy và đọc kết quả.
  • Tìm bottleneck: Chạy stress test tăng dần tải lên một dịch vụ trên máy local (ví dụ một app Node/PHP + database). Mở dashboard tài nguyên, quan sát và ghi lại: ở mức tải nào latency bắt đầu tăng vọt, và tài nguyên nào chạm trần trước (CPU, RAM hay DB connection)?
  • Lập kế hoạch capacity trên giấy: Giả sử hệ thống hiện tại đỉnh 500 RPS, tăng trưởng 8%/tháng, và có campaign 3 tháng nữa dự kiến ×6. Tính RPS mục tiêu cần chuẩn bị và số pod cần (biết mỗi pod ~120 RPS, headroom 30%).

Tóm tắt

  • Capacity Planning cân bằng hai câu hỏi: đủ tài nguyên để không sập, và không lãng phí để không đốt tiền.
  • Luôn tư duy bằng các chỉ số: throughput, latency theo percentile (p95/p99), saturation, headroom — và dùng Little's Law (L = λ × W) để ước lượng nhanh.
  • Load Testing có bốn loại, mỗi loại một mục đích: load (xác nhận SLO), stress (tìm điểm gãy), soak (bắt leak), spike (kiểm tra co giãn). Bỏ sót loại nào là để lại một lỗ hổng tương ứng.
  • Bottleneck thật thường ẩn ở database, cache, hoặc dependency bên thứ ba — không phải ở nơi bạn nghĩ. Test để tìm ra nó, đừng đoán.
  • Test phải chạy trên môi trường giống production, gắn SLO làm ngưỡng pass/fail, và lặp lại định kỳ — đặc biệt trước các sự kiện lớn như 11/11, 12/12, Tết.
  • Với tải không dự đoán được, hãy chuyển từ "provision tĩnh" sang "thiết kế khả năng co giãn": autoscaling + warm pool + graceful degradation, và dùng spike test để chứng minh nó hoạt động.
Nắm vững bài này, bạn sẽ không còn ở thế bị động chờ hệ thống báo cháy — mà chủ động biết trước giới hạn của mình và bước vào mỗi mùa cao điểm với sự tự tin có căn cứ.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi