Load test API trước ngày go-live: đặt ngưỡng p95, đo throughput và kiểm tra caching bằng k6
Latency trung bình trông rất ổn vẫn có thể che đi những request chậm mà người dùng của khách sẽ gặp đúng ngày ra mắt.
Caching chỉ đáng tin khi dữ liệu cá nhân có private, revalidation trả 304 và điểm p95 bắt đầu cong lên dịch sang phải.
Đồ hoạ: FDE Times
Tóm tắt nhanh
- Đặt mục tiêu bằng p95 và requests/giây, đừng dùng latency trung bình.
- Threshold k6 vỡ thì exit code khác 0, nên load test có thể làm cổng chặn trong CI/CD.
- Caching là đòn bẩy đầu tiên, nhưng phản hồi cá nhân hoá phải đánh dấu private.
Thử hình dung 100 request vào một API: 94 request trả về trong 50ms, 6 request mất 2000ms. Trung bình là 167ms, dưới ngưỡng 200ms, và báo cáo gửi khách sẽ ghi “đạt”. Nhưng p95 của chính bộ số đó là 2000ms, tức cứ 100 người dùng thì 6 người chờ hai giây.
Cuốn sách SRE của Google gọi việc tin rằng phần lớn request nằm gần giá trị trung bình là mơ tưởng. Với một FDE, chuyện này không còn là lý thuyết vào tuần trước go-live: khách cần biết hệ thống chịu được ngày ra mắt hay không, và bạn là người phải trả lời bằng số liệu.
Hướng dẫn này đi qua một quy trình làm được trên laptop. Bạn sẽ viết một script load test có ngưỡng pass/fail, chạy nó như một cổng chặn trong pipeline, tăng tải để tìm điểm p95 bắt đầu cong lên, rồi thêm caching mà không làm rò dữ liệu giữa các user.
Cần chuẩn bị gì trước khi gõ lệnh?
Bạn cần k6 đã cài trên máy, một môi trường staging của API (đừng bắn tải vào production của khách), quyền xem dashboard CPU, bộ nhớ và database của staging, và curl.
Nếu team khách quen Postman hơn, Postman cũng có tính năng chạy virtual users song song theo một chuỗi request và đặt điều kiện pass theo trung bình, p90, p95 hoặc p99. Cách tư duy bên dưới áp dụng cho cả hai công cụ.
Quan trọng hơn công cụ là một cuộc trao đổi ngắn với khách. Bạn cần hai con số: tải mục tiêu và latency chấp nhận được.
Bước 1: chốt mục tiêu bằng requests/giây và p95
Hướng dẫn load test API của Grafana Labs lưu ý rằng tải thường được báo cáo bằng tốc độ request, tính theo giây hoặc theo phút. Vì thế đừng hỏi khách “có bao nhiêu user”, hãy hỏi “giờ cao điểm có bao nhiêu request mỗi giây vào endpoint này”. Con số user cần được quy đổi, còn requests/giây thì đo được trực tiếp.
Với latency, mẫu chuẩn mà Grafana đưa ra là 95% request phải dưới 200ms. Hãy viết mục tiêu theo đúng dạng đó.
Nếu API của bạn được một frontend gọi qua nhiều backend, hãy siết chặt hơn: sách SRE của Google cảnh báo rằng p99 của một backend rất dễ trở thành độ trễ trung vị ở frontend, vì một trang gọi nhiều backend sẽ thường xuyên dính ít nhất một request chậm.
Kiểm tra sau bước này: bạn có một dòng viết được vào test plan, ví dụ “endpoint /orders, 500 requests/giây, p95 dưới 200ms, lỗi dưới 1%” (con số tải ở đây là giả định), và khách đã đồng ý với nó.
Bước 2: biến mục tiêu thành threshold trong k6
k6 diễn đạt tiêu chí pass/fail bằng thresholds. Tài liệu k6 mô tả một threshold đánh giá tỷ lệ lỗi HTTP qua metric http_req_failed, và thuộc tính abortOnFail đặt bằng true để dừng test ngay khi vượt ngưỡng.
Script dưới đây là bản rút gọn: dùng số virtual users cố định cho dễ đọc, URL là giả định, và bạn nên đối chiếu cú pháp với tài liệu của phiên bản k6 đang dùng.
// load.js — phác thảo rút gọn
import http from 'k6/http';
export const options = {
vus: 20,
duration: '2m',
thresholds: {
http_req_duration: [{ threshold: 'p(95)<200', abortOnFail: true }],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
http.get('https://staging.example.com/api/orders');
}
abortOnFail đáng bật khi hệ thống hỏng rõ ràng: không cần đợi hết hai phút mới biết p95 đã vọt lên vài giây. Đặt threshold lỗi song song với latency cũng quan trọng không kém, vì một API trả lỗi 500 rất nhanh vẫn có p95 đẹp.
VUs không phải là requests/giây
Bước 1 chốt mục tiêu bằng requests/giây, còn script lại khai báo 20 VUs. Hai thứ này nối với nhau bằng một phép tính đơn giản: mỗi VU chạy vòng lặp liên tục, nên throughput xấp xỉ bằng số VUs chia cho thời gian một vòng. Với 20 VUs và mỗi request mất 50ms, lý thuyết bạn có khoảng 400 requests/giây.
Cái bẫy nằm ở chiều ngược lại. Khi hệ thống chậm đi và mỗi request mất 200ms, cùng 20 VUs đó chỉ còn tạo ra khoảng 100 requests/giây: test tự giảm tải đúng lúc hệ thống yếu nhất, và p95 trông dễ chịu hơn thực tế.
Vì thế, phép quy đổi trên chỉ để ước lượng số VUs ban đầu. Sau mỗi lần chạy, hãy đối chiếu requests/giây thực tế đạt được với mục tiêu trong test plan; nếu thấp hơn, tăng VUs rồi chạy lại cho đến khi chạm mục tiêu.
Bước 3: chạy và để exit code quyết định
k6 run load.js
echo $?
Grafana Labs nhấn mạnh rằng khi test thất bại, k6 CLI trả về exit code khác 0, điều kiện cần để tự động hoá. Bạn có thể đặt lệnh này vào pipeline CI/CD trước bước deploy: threshold vỡ thì pipeline đỏ, bản build không lên được.
Kiểm tra sau bước này: cố tình hạ ngưỡng xuống p(95)<1 rồi chạy lại. Nếu echo $? vẫn in 0, cổng chặn của bạn đang không chặn gì cả.
Bước 4: tăng tải từng nấc, rồi spike
Một lần chạy với tải cố định chỉ cho bạn biết hệ thống ổn ở một mức. Ngày go-live thường có dạng khác: tải dồn lên đột ngột khi khách gửi email thông báo hoặc mở tính năng. Grafana phân loại các kiểu test theo mục đích, trong đó spike test dùng để xem hệ thống phản ứng thế nào với lượng truy cập tăng đột ngột và lớn.
Cách đơn giản nhất, không cần thêm cấu hình mới: sửa vus trong load.js và chạy lại ở từng nấc, mỗi lần ghi requests/giây đạt được, p95 và tỷ lệ lỗi. Đây là cách tăng tải thủ công, rút gọn cho dễ theo dõi. Bảng dưới là số liệu minh hoạ giả định cho một endpoint, để bạn thấy đường cong cần tìm trông ra sao.
| Nấc tải | Requests/giây đạt được | p95 | Tỷ lệ lỗi |
|---|---|---|---|
| 10 VUs | 180 | 60ms | 0% |
| 20 VUs | 350 | 75ms | 0% |
| 40 VUs | 600 | 140ms | 0% |
| 60 VUs | 640 | 380ms | 0,2% |
| 80 VUs | 610 | 900ms | 3% |
Đọc bảng này qua bốn golden signals mà Google SRE đề xuất: latency, traffic, errors và saturation. Từ 40 lên 60 VUs, traffic gần như đứng yên trong khoảng 600–640 requests/giây trong khi p95 gần gấp ba, còn lỗi mới chỉ nhích nhẹ. Sách SRE ghi nhận latency tăng thường là chỉ báo sớm của saturation, và đây đúng là hình ảnh đó.
Trần thực tế của endpoint giả định này là khoảng 600–640 requests/giây, không phải 80 VUs nơi lỗi tràn ra. Khi thấy p95 cong lên, hãy mở dashboard staging tìm tài nguyên nào sắp cạn: CPU, connection pool của database hay hàng đợi, vì k6 chỉ cho bạn ba tín hiệu đầu.
Với spike, giữ tải nền ở nấc 10 VUs rồi nhảy thẳng lên nấc gấp vài lần mức cao điểm khách dự kiến, thay vì tăng dần. Điều bạn cần trả lời là hệ thống có trả lỗi hàng loạt hay chỉ chậm đi rồi hồi lại.
Bước 5: thêm caching và chạy lại
Khi p95 vượt ngưỡng ở endpoint đọc dữ liệu, caching thường là đòn bẩy đầu tiên. Nordic APIs giải thích lý do đơn giản: đưa response vào cache giúp tránh những truy vấn database không cần thiết. Nhưng caching trong dự án của khách có một cái bẫy về dữ liệu.
MDN ghi rõ: nếu response chứa nội dung cá nhân hoá và bạn chỉ muốn lưu trong cache riêng, bạn phải chỉ định directive private. Thiếu nó, một shared cache như CDN hoặc proxy có thể trả đơn hàng của người này cho người khác. Kiểm tra header hiện tại:
curl -I https://staging.example.com/api/orders
Lớp thứ hai là revalidation bằng ETag. Theo MDN, server trả 304 Not Modified khi ETag nó tính cho tài nguyên trùng với giá trị If-None-Match trong request, nên không phải gửi lại cả payload. Thử bằng cách copy ETag từ lệnh trên:
curl -I -H 'If-None-Match: "<etag-vừa-nhận>"' https://staging.example.com/api/orders
Kiểm tra sau bước này: response cá nhân hoá có private, request có If-None-Match nhận 304, và chạy lại các nấc tải ở Bước 4 cho thấy điểm cong của p95 dịch sang phải. Lưu ý rằng script k6 gọi lặp một URL sẽ có tỷ lệ cache hit cao hơn thực tế; hãy đa dạng hoá tham số nếu muốn con số trung thực.
Những lỗi hay gặp
Lỗi phổ biến nhất là báo cáo latency trung bình, như ví dụ 167ms ở đầu bài. Lỗi thứ hai là chỉ đặt threshold latency mà quên tỷ lệ lỗi. Lỗi thứ ba là báo cáo số VUs thay cho requests/giây đạt được, trong khi VUs cố định sẽ tự giảm tải khi hệ thống chậm đi.
Lỗi thứ tư là chạy load test vào môi trường dùng chung mà không báo team khách, khiến người khác tưởng hệ thống sập. Lỗi thứ năm là đo sau khi bật cache nhưng không giữ bảng kết quả trước đó, nên không chứng minh được cải thiện bao nhiêu.
Mang kỹ năng này đến chỗ khách thế nào?
Thử hình dung CTO bên khách hỏi: “Ngày mai mở cho toàn bộ chi nhánh thì có chịu nổi không?”. Câu trả lời thuyết phục nhất bạn có thể đưa ra là một test plan bằng requests/giây, một script nằm trong pipeline, và bảng p95 theo từng nấc tải trước và sau khi thêm cache.
Nếu bạn đang chuyển sang vai trò FDE, hãy để ý trong job description những cụm như “production readiness”, “performance” hay “go-live support”. Trong CV, thay vì “có kinh nghiệm load test”, hãy viết một dòng cụ thể: “đặt threshold p95 và tỷ lệ lỗi bằng k6 làm cổng CI trước deploy, tìm trần tải qua ramp-up và spike test, giảm p95 bằng Cache-Control và ETag”.
Ngày ra mắt, khách sẽ không hỏi bạn con số trung bình. Họ chỉ để ý xem có ai phải chờ hay không.
6 nguồn
- API load testing: A beginner's guide (Grafana Labs) · 2024-01-31
- Thresholds | Grafana k6 documentation
- Monitoring Distributed Systems (Google SRE Book)
- Simulate user traffic to test your API performance (Postman Docs)
- 10 Tips for Improving API Performance (Nordic APIs) · 2023-11-08
- HTTP caching (MDN Web Docs)