Mở đầu — vì sao bài này quan trọng
Khi bạn viết script k6 đầu tiên, mọi thứ trông rất gọn: một hàm default, vài lệnh http.get, chạy k6 run script.js là xong. Nhưng đó chỉ là "bản demo" chạy với 1 người dùng ảo trong vài giây. Trong đời thực, khi sếp hỏi "hệ thống chịu được 500 người dùng đồng thời không?", "trước Black Friday cần chạy test tăng tải dần trong 30 phút", hay "test này phải fail nếu p95 vượt 800ms" — thì tất cả những điều đó không nằm trong hàm default. Chúng nằm trong options.
options chính là bảng điều khiển trung tâm của một bài test k6. Đây là nơi bạn khai báo có bao nhiêu virtual user (VU — người dùng ảo), test chạy bao lâu, tải tăng và giảm theo hình dạng nào, và test được coi là đạt hay không đạt. Nắm vững options và đặc biệt là stages là ranh giới giữa một người "biết chạy k6" và một người thực sự thiết kế được kịch bản kiểm thử hiệu năng sát với thực tế. Trong bài này, chúng ta đi sâu vào block options cùng cơ chế stages — thứ cho phép bạn mô phỏng đúng cách người dùng thật đổ vào hệ thống.
Khái niệm cốt lõi
options là gì và đặt ở đâu
Trong một script k6, bạn khai báo cấu hình bằng cách export một object tên là options ở cấp cao nhất (top-level) của file. k6 đọc object này TRƯỚC khi test bắt đầu để biết cần khởi tạo bao nhiêu VU và lịch trình ra sao.
import http from 'k6/http';
import { sleep } from 'k6';export const options = {
vus: 50, // 50 người dùng ảo
duration: '30s' // chạy liên tục trong 30 giây
};
export default function () {
http.get('https://test-api.example.com/products');
sleep(1);
}
Điểm mấu chốt cần hiểu: hàm default mô tả MỘT người dùng ảo làm gì (một vòng lặp — gọi là iteration). Còn options mô tả CÓ BAO NHIÊU người dùng ảo và họ hoạt động THEO LỊCH TRÌNH nào. Hai phần này tách bạch nhau. Bạn viết hành vi một lần, rồi nhân bản nó lên bằng options.
Mô hình đơn giản nhất: vus + duration
Đây là cặp đôi cơ bản nhất:
vus: số virtual user chạy song song, giữ ổn định trong suốt test.duration: tổng thời gian test chạy. Định dạng là chuỗi như'30s','5m','1h30m'.
vus: 50, duration: '30s', k6 tạo 50 VU, mỗi VU liên tục lặp lại hàm default cho đến khi hết 30 giây. Đây gọi là mô hình constant VUs — tải phẳng, không tăng không giảm. Nó tốt cho một bài smoke test hoặc khi bạn muốn giữ một mức tải cố định để quan sát. Nhưng nó KHÔNG giống thực tế: người dùng thật không xuất hiện đồng loạt 50 người ngay giây đầu tiên.stages — trái tim của việc mô phỏng tải thực tế
stages cho phép bạn thay đổi số VU theo thời gian, tạo thành một "hình dạng tải" (load profile). Mỗi stage có hai thuộc tính:
duration: thời gian của giai đoạn này.target: số VU mà k6 sẽ tiến tới (ramp) vào cuối giai đoạn.
target trong khoảng duration. Ví dụ kinh điển:export const options = {
stages: [
{ duration: '2m', target: 100 }, // tăng dần từ 0 lên 100 VU trong 2 phút
{ duration: '5m', target: 100 }, // giữ 100 VU trong 5 phút
{ duration: '2m', target: 0 }, // giảm dần về 0 trong 2 phút
],
};
Đây là hình tam giác thang (trapezoid) kinh điển: ramp-up → plateau → ramp-down. Giai đoạn ramp-up mô phỏng lượng người dùng tăng dần (ví dụ khi mở cổng bán vé). Plateau là lúc hệ thống chịu tải ổn định — đây là nơi bạn thu thập số liệu đáng tin cậy nhất. Ramp-down giúp bạn quan sát hệ thống có phục hồi (recover), giải phóng connection, dọn queue hay không.
Lưu ý quan trọng: target là số VU tuyệt đối, không phải mức tăng thêm. Nếu stage trước kết thúc ở 100 VU và stage sau có target: 200, k6 tăng từ 100 lên 200. Nếu target: 100 giống stage trước thì đó là giai đoạn "giữ nguyên".
Ngưỡng thời gian và các option khác thường dùng
Ngoài vus, duration, stages, block options còn chứa nhiều cấu hình quan trọng khác mà bạn nên biết ở mức khai báo:
export const options = {
stages: [ / ... / ], // Thời gian chờ VU kết thúc iteration đang chạy dở khi ramp-down
gracefulRampDown: '30s',
// Thời gian chờ khi kết thúc toàn bộ test
gracefulStop: '30s',
// Bỏ qua lỗi chứng chỉ TLS (hữu ích với môi trường staging self-signed)
insecureSkipTLSVerify: true,
// Không kết nối lại DNS mỗi request — cache trong suốt VU
noConnectionReuse: false,
// Gắn nhãn cho toàn bộ test khi đẩy metric ra ngoài
tags: { environment: 'staging', team: 'checkout' },
};
Trong đó gracefulRampDown đáng chú ý: khi giảm VU, k6 không "chặt đầu" iteration đang chạy dở. Nó cho VU đó thêm tối đa gracefulRampDown giây để hoàn thành vòng lặp hiện tại rồi mới dừng. Điều này tránh việc bạn thấy hàng loạt lỗi giả (request bị hủy giữa chừng) chỉ vì test đang ramp-down. Mặc định là 30 giây.
Bốn cách nạp cấu hình — thứ tự ưu tiên
Một điều nhiều người mới bối rối: cùng một option có thể được đặt ở nhiều nơi. k6 quyết định giá trị cuối cùng theo thứ tự ưu tiên từ THẤP đến CAO:
- Giá trị mặc định của k6 (thấp nhất).
export const optionstrong script — nguồn phổ biến nhất.- Biến môi trường (ví dụ
K6_VUS,K6_DURATION). - Cờ dòng lệnh (
--vus,--duration) — cao nhất, ghi đè tất cả.
k6 run --vus 5 --duration 10s script.js để ghi đè mà không sửa file. Chi tiết về các cờ CLI sẽ được đào sâu ở bài riêng; ở đây bạn chỉ cần nhớ CLI luôn thắng.Tình huống thực tế
Ví dụ 1 — Tiki chuẩn bị cho đợt sale 11.11
Đội QA của một sàn thương mại điện tử lớn tại Việt Nam (gọi là "sàn T") cần kiểm tra API danh mục sản phẩm trước đợt sale 11.11. Ban đầu, một bạn junior viết vus: 500, duration: '10m' và báo cáo "hệ thống chịu được 500 VU, p95 = 620ms, ổn". Nhưng khi sale thật diễn ra, hệ thống sập trong 3 phút đầu.
Vấn đề nằm ở chỗ test cũ đổ 500 VU lên đầu một cách... đột ngột nhưng đều. Nó bỏ qua đúng khoảnh khắc nguy hiểm nhất: lúc traffic tăng vọt. Đội QA viết lại bằng stages:
export const options = {
stages: [
{ duration: '30s', target: 50 }, // trước giờ G
{ duration: '1m', target: 800 }, // đúng lúc mở sale — tăng dốc
{ duration: '5m', target: 800 }, // đỉnh sale kéo dài
{ duration: '2m', target: 100 }, // hạ nhiệt
],
};
Lần này họ phát hiện: khi VU tăng dốc từ 50 lên 800 trong 1 phút, connection pool tới database bị cạn, error rate nhảy lên 12% trong khoảng giây 40–70. Số liệu 500 VU phẳng trước đó không bao giờ lộ ra vấn đề này. Bài học: hình dạng ramp-up quan trọng ngang với con số đỉnh. Đổ tải đột ngột (steep ramp) là kịch bản test bắt buộc cho các sự kiện sale.
Ví dụ 2 — Startup fintech và giá trị của ramp-down
Một startup ví điện tử ở TP.HCM test API xác thực OTP. Họ chạy stages với ramp-up và plateau đẹp, nhưng luôn kết thúc bằng { duration: '10s', target: 0 } — giảm về 0 quá nhanh. Kết quả: cuối mỗi test luôn có một "gai" lỗi 500 và họ tưởng hệ thống có bug.
Sau khi rà soát, trưởng nhóm nhận ra thủ phạm là gracefulRampDown mặc định 30 giây bị "va" với stage ramp-down chỉ 10 giây, đồng thời một số iteration gọi OTP (mất ~4s vì chờ SMS gateway) bị cắt ngang. Họ điều chỉnh:
export const options = {
stages: [
{ duration: '1m', target: 200 },
{ duration: '5m', target: 200 },
{ duration: '1m', target: 0 }, // ramp-down thong thả hơn
],
gracefulRampDown: '20s',
};
Gai lỗi biến mất. Bài học: ramp-down không phải phần thừa. Cấu hình duration ramp-down và gracefulRampDown hợp lý để tránh lỗi giả từ chính test của bạn — đừng đổ tội cho hệ thống trước khi loại trừ artifact của công cụ.
Ví dụ 3 — Cùng một script, ba môi trường
Một công ty outsourcing ở Đà Nẵng có một script test API duy nhất, nhưng cần chạy ở ba mức: smoke (kiểm tra nhanh mỗi lần commit), load (nightly), và stress (trước release). Thay vì viết ba file, họ dùng thứ tự ưu tiên của options:
Trong script để mặc định là smoke (vus: 2, duration: '30s'). Trong CI/CD, pipeline nightly chạy K6_VUS=100 K6_DURATION=15m k6 run api.js, còn job stress chạy k6 run --vus 1000 --duration 20m api.js. Một script, ba hồ sơ tải, không trùng lặp code.
Bài học: hiểu thứ tự ưu tiên (script < env < CLI) giúp bạn tái sử dụng một script cho nhiều mục đích, giữ nguyên tắc DRY và dễ tích hợp vào tự động hóa.
Hướng dẫn từng bước
Đây là quy trình thực tế để thiết kế options cho một bài test mới:
Bước 1 — Xác định mục tiêu tải. Trả lời: bao nhiêu người dùng đồng thời ở đỉnh? Con số này đến từ đâu (dữ liệu Google Analytics, log production, hay ước lượng cho sự kiện)? Ví dụ: "đỉnh dự kiến 300 người dùng đồng thời".
Bước 2 — Bắt đầu bằng smoke test. Trước khi chạy tải lớn, luôn xác nhận script CHẠY ĐÚNG với tải rất nhỏ:
export const options = { vus: 1, duration: '1m' };
Nếu 1 VU đã lỗi thì tăng lên 300 VU chỉ tạo ra 300 lần lỗi. Smoke test lọc sạch bug trong script trước.
Bước 3 — Chuyển sang stages với hình thang chuẩn. Thiết kế ramp-up chiếm khoảng 10–20% tổng thời gian, plateau là phần dài nhất:
export const options = {
stages: [
{ duration: '2m', target: 300 }, // ramp-up
{ duration: '10m', target: 300 }, // plateau — thu thập số liệu
{ duration: '2m', target: 0 }, // ramp-down
],
gracefulRampDown: '30s',
};
Bước 4 — Chạy và quan sát trên plateau. Chỉ tin số liệu ở giai đoạn plateau, khi số VU đã ổn định. Số liệu trong lúc ramp-up bị nhiễu vì tải đang biến động.
Bước 5 — Điều chỉnh hình dạng theo câu hỏi thực tế. Muốn test spike? Thêm stage tăng dốc. Muốn test độ bền? Kéo dài plateau. Muốn test khả năng phục hồi? Chú ý kỹ hành vi trong và sau ramp-down. (Các loại test chuyên biệt được đào sâu ở những bài riêng.)
Bước 6 — Đưa cấu hình vào CI/CD một cách linh hoạt. Giữ cấu hình chuẩn trong script, dùng env var hoặc CLI flag để điều chỉnh theo môi trường thay vì hard-code nhiều file.
Lỗi thường gặp & mẹo
Đặt cả vus/duration LẪN stages cùng lúc. Đây là lỗi phổ biến nhất. Khi có stages, k6 sẽ dùng stages và bỏ qua duration ở top-level (thực tế vus khi đó chỉ là số VU khởi tạo ban đầu). Đừng trộn hai mô hình — chọn một. Muốn tải phẳng thì dùng vus+duration; muốn tải biến đổi thì dùng stages.
Hiểu nhầm target là mức tăng thêm. target là con số tuyệt đối k6 hướng tới. { target: 100 } sau { target: 100 } nghĩa là giữ nguyên 100 VU, không phải cộng thêm 100.
Quên ramp-down, kết thúc bằng cách "cắt phựt". Kết thúc đột ngột ở target cao có thể tạo lỗi giả và làm bạn không quan sát được khả năng phục hồi. Luôn có một stage đưa target về 0 (hoặc mức thấp) một cách từ tốn.
Ramp-up quá dốc mà không chủ đích. Nếu bạn muốn test tình huống bình thường, ramp-up dốc sẽ vô tình biến nó thành spike test và tạo lỗi không phản ánh thực tế. Ngược lại, nếu bạn CẦN test sự kiện sale thì ramp dốc lại là đúng. Hãy chủ đích với độ dốc.
Mẹo — validate cấu hình trước khi chạy dài. Chạy k6 inspect script.js (hoặc chạy thử với duration ngắn) để xác nhận k6 đọc options đúng như bạn nghĩ, tránh chờ 20 phút rồi mới phát hiện sai.
Mẹo — dùng tags trong options ngay từ đầu. Gắn tags: { env: 'staging' } giúp về sau khi đẩy metric ra dashboard, bạn lọc và so sánh giữa các lần chạy dễ dàng.
Mẹo — comment rõ mục đích từng stage. Một dòng comment // mở cổng sale bên cạnh stage tăng dốc giúp cả đội hiểu ngay ý đồ, thay vì phải đoán từ những con số.
Bài tập thực hành
- Chuyển đổi mô hình. Cho script dùng
vus: 100, duration: '5m'. Hãy viết lại bằngstagessao cho: dành 1 phút tăng dần lên 100 VU, giữ 100 VU trong 5 phút, rồi 1 phút giảm về 0. So sánh biểu đồ số VU của hai phiên bản.
- Thiết kế hồ sơ tải cho sự kiện. Giả sử bạn test API đặt vé một concert, cổng mở lúc 20:00 và lượng người dùng dự kiến vọt từ ~20 lên ~1000 chỉ trong 90 giây, giữ đỉnh 4 phút rồi giảm. Viết
options.stagesmô phỏng đúng hồ sơ này và giải thích vì sao bạn chọn độ dốc đó.
- Tận dụng thứ tự ưu tiên. Viết một script có
optionsmặc định là smoke test (vus: 2, duration: '30s'). Sau đó viết ba lệnh chạy (dùng CLI flag hoặc env var) để cùng script đó chạy ở chế độ load (200 VU, 10 phút) và stress (800 VU, 15 phút) mà KHÔNG sửa file.
- Thử nghiệm
gracefulRampDown. Tạo một script cósleep(5)trong hàm default và stage ramp-down chỉ{ duration: '5s', target: 0 }. Chạy vớigracefulRampDown: '2s'rồi vớigracefulRampDown: '10s'. Quan sát sự khác biệt về số iteration bị gián đoạn và ghi lại nhận xét.
Tóm tắt
Block export const options là bảng điều khiển trung tâm quyết định bài test k6 của bạn có bao nhiêu người dùng ảo, chạy bao lâu và theo hình dạng tải nào. Cặp vus + duration cho bạn tải phẳng đơn giản, phù hợp smoke test. Nhưng công cụ mạnh nhất là stages: mỗi stage khai báo một duration và target (số VU tuyệt đối), ghép lại thành hồ sơ ramp-up → plateau → ramp-down mô phỏng cách người dùng thật đổ vào hệ thống. Hình dạng tải — đặc biệt là độ dốc ramp-up và cách ramp-down — thường tiết lộ những vấn đề mà con số VU đỉnh phẳng không bao giờ lộ ra, như ba tình huống thực tế đã cho thấy. Đừng quên các option hỗ trợ như gracefulRampDown để tránh lỗi giả, và tận dụng thứ tự ưu tiên (script < env var < CLI flag) để tái sử dụng một script cho nhiều môi trường. Nắm chắc options và stages, bạn đã có nền tảng để thiết kế mọi loại kịch bản kiểm thử hiệu năng chuyên sâu ở các bài tiếp theo.