Product Management
Đăng nhập
ESC

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

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

Project 1: VN E-commerce Load Test

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

Suốt hơn 40 bài trước, bạn đã học rời rạc từng mảnh: cách viết Thread Group trong JMeter, cách khai báo optionsstages trong k6, cách đặt Threshold làm cổng SLA, cách parameterize dữ liệu test, cách đọc Latency và Throughput. Từng mảnh đó, khi đứng riêng, giống như những viên gạch nằm rải rác. Bài 51 là lúc bạn xây căn nhà đầu tiên: một bộ load test hoàn chỉnh cho một sàn thương mại điện tử Việt Nam thực tế, chạy được từ đầu tới cuối, cho ra một con số bạn dám mang đi báo cáo.

Đây là project đầu tiên trong chuỗi 4 project của khóa học, và nó cố tình được đặt ở đây vì thương mại điện tử là "bài toán kinh điển" của performance testing. Một sàn e-commerce có đủ mọi loại tình huống: người dùng đăng nhập, tìm kiếm, xem sản phẩm, thêm giỏ hàng, thanh toán — mỗi bước có đặc tính tải khác nhau. Nếu bạn làm chủ được project này, bạn có bộ khung áp dụng được cho gần như mọi hệ thống web sau này.

Điều quan trọng cần nhớ: mục tiêu của Bài 51 không phải là học thêm tính năng mới của JMeter hay k6 (những thứ đó bạn đã học rồi), mà là ghép chúng lại thành một quy trình dự án thật: xác định scope, xây workload model đúng, chạy, và rút ra kết luận có ý nghĩa kinh doanh. Chúng ta sẽ dùng k6 làm công cụ chính vì code gọn, dễ đọc trong khuôn khổ một bài, nhưng tư duy hoàn toàn áp dụng được cho JMeter.

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

Scope: 5 endpoint quan trọng của luồng mua hàng

Một sàn e-commerce có hàng trăm endpoint, nhưng bạn không load test tất cả. Bạn chọn critical path — con đường tạo ra doanh thu. Với dự án này, scope gồm 5 endpoint:

  • Login (POST /api/auth/login) — cổng vào, tạo session/token.
  • Search (GET /api/search?q=...) — thường là endpoint tốn tài nguyên nhất vì đụng vào search engine và database.
  • Product detail (GET /api/products/{id}) — endpoint có lượng gọi lớn nhất, thường được cache.
  • Add to cart (POST /api/cart) — ghi dữ liệu, đụng session.
  • Checkout (POST /api/checkout) — endpoint "vàng", ít gọi nhất nhưng quan trọng nhất, thường đụng payment và transaction.

Workload model: không phải mọi endpoint đều được gọi như nhau

Đây là khái niệm cốt lõi và cũng là chỗ người mới hay sai nhất. Trong thực tế, tỷ lệ người dùng đi qua từng bước tạo thành một phễu (funnel) thu hẹp dần. Nếu bạn để 5 endpoint được gọi với tỷ lệ 20-20-20-20-20 thì bạn đang test một hệ thống không tồn tại trên đời.

Một mô hình phễu thực tế cho e-commerce VN trông giống thế này:

BướcTỷ lệ user thực hiệnGhi chú
Search / Browse100%Ai vào cũng tìm/xem
Product detail80%Đa số click vào ít nhất 1 sản phẩm
Login30%Nhiều người xem mà chưa đăng nhập
Add to cart25%Một phần thêm giỏ
Checkout8%Tỷ lệ chuyển đổi thật thường 2–10%
Con số cụ thể của bạn nên lấy từ Google Analytics hoặc log server thật của sàn. Nếu chưa có, dùng con số ngành làm giả định và ghi rõ đó là giả định.

Concurrency (VU) vs. Request rate (RPS)

Bạn cần phân biệt hai cách mô tả tải, cả hai đã học ở bài Scenarios/executor:

  • VU (Virtual Users) — số người dùng ảo đồng thời. Dùng khi bạn nghĩ theo kiểu "hệ thống chịu được bao nhiêu người online cùng lúc?".
  • RPS (Requests per second) — số request mỗi giây. Dùng khi bạn có số liệu từ Analytics kiểu "giờ cao điểm hệ thống nhận 500 req/s".
Với e-commerce, thường ta mô hình theo VU đi qua một user journey (hành trình người dùng), vì nó phản ánh đúng hành vi thật hơn là bắn request rời rạc.

Định nghĩa tiêu chí Pass/Fail trước khi chạy

Nguyên tắc bất di bất dịch: định nghĩa "đạt" trước khi chạy test, không phải nhìn kết quả rồi mới nghĩ. Đây chính là Threshold trong k6. Ví dụ cho project này:

  • p95 latency của toàn hệ thống < 800ms.
  • p95 latency của checkout < 1500ms (được phép chậm hơn vì nặng).
  • Tỷ lệ lỗi (error rate) < 1%.
  • Checkout không được lỗi quá 0.5%.

Tình huống thực tế

Ví dụ 1 — Sàn thời trang "Sen Đỏ Fashion" và cái bẫy tỷ lệ đều

Một team QA của sàn thời trang giả định "Sen Đỏ Fashion" (khoảng 200.000 khách/ngày) được giao load test trước mùa sale. Bạn QA junior viết script k6 gọi 5 endpoint theo tỷ lệ đều 20% mỗi cái, chạy 500 VU. Kết quả: hệ thống "gãy" ở checkout, p95 checkout lên 6 giây, team hoảng loạn báo cáo "hệ thống không chịu nổi sale".

Khi mentor xem lại, vấn đề lộ ra: với tỷ lệ đều, script tạo ra 100 checkout đồng thời liên tục — tương đương 20% của 500 VU. Nhưng trong thực tế, tỷ lệ checkout của sàn này chỉ 6%. Tức là script đã ép hệ thống chịu lượng giao dịch payment cao gấp hơn 3 lần so với đời thực. Con số "6 giây" là kết quả của một kịch bản không bao giờ xảy ra.

Bài học: workload model sai làm toàn bộ kết luận vô nghĩa — thường theo hướng bi quan giả tạo, khiến team tốn tiền scale hạ tầng cho một tải không có thật. Luôn lấy tỷ lệ phễu từ dữ liệu thật.

Ví dụ 2 — Tiki-style search và endpoint bị bỏ quên

Một sàn điện máy (kiểu mini-Tiki) load test 4 endpoint và bỏ qua search vì "nó chỉ là GET, nhẹ mà". Test pass đẹp. Đến ngày sale 12.12, hệ thống sập — không phải checkout, mà là search. Lý do: search đụng thẳng vào Elasticsearch, và ở mùa sale, người dùng tìm kiếm điên cuồng ("máy lạnh giảm giá", "tủ lạnh trả góp"). Search chiếm 100% lượng user nhưng lại là endpoint bị loại khỏi scope.

Sau sự cố, team đo lại: search đóng góp tới 45% tổng tài nguyên CPU database trong giờ cao điểm, dù mỗi request "chỉ là GET".

Bài học: endpoint "nhẹ về mặt HTTP" không có nghĩa là "nhẹ về mặt tài nguyên". Search, filter, autocomplete là những kẻ ngốn CPU/DB thầm lặng và phải nằm trong scope với đúng trọng số cao.

Ví dụ 3 — Login token và số liệu error rate bị "làm đẹp" oan

Một team thương mại điện tử ở TP.HCM chạy test 1000 VU, thấy error rate 22% và định báo cáo "hệ thống yếu". Mentor yêu cầu bóc tách lỗi theo endpoint: 21% lỗi nằm ở add-to-cart và checkout, tất cả trả về HTTP 401. Nguyên nhân: script login lấy token nhưng không truyền token vào các request sau — một lỗi parameterization, không phải lỗi hệ thống.

Khi sửa để chuyền Authorization: Bearer <token> đúng cách qua từng bước, error rate rớt xuống 0.3%.

Bài học: rất nhiều "phát hiện performance" thực ra là bug trong chính script test. Trước khi kết tội hệ thống, hãy kiểm tra script của bạn có mô phỏng đúng luồng xác thực và trạng thái session không.

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

Ta xây dựng project bằng k6. Giả sử base URL là https://shop.example.vn.

Bước 1 — Chuẩn bị test data. Login cần tài khoản thật. Tạo file users.csv (đã học ở bài parameterization) với các cặp email/password test hợp lệ do team dev cung cấp trên môi trường staging. Tuyệt đối không test bằng tài khoản thật của khách trên production.

email,password
loadtest01@example.vn,Test@123
loadtest02@example.vn,Test@123

Bước 2 — Dựng bộ khung script với workload model đúng. Ta dùng scenarios để tách hành vi, và dùng xác suất để mô phỏng phễu.

import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { SharedArray } from 'k6/data';
import { Trend, Rate } from 'k6/metrics';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';

const users = new SharedArray('users', () => papaparse.parse(open('./users.csv'), { header: true }).data );

const checkoutDur = new Trend('checkout_duration', true); const checkoutErr = new Rate('checkout_errors');

export const options = { scenarios: { ecommerce: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 200 }, // ramp-up { duration: '5m', target: 200 }, // giữ tải ổn định { duration: '2m', target: 0 }, // ramp-down ], }, }, thresholds: { http_req_duration: ['p(95)<800'], http_req_failed: ['rate<0.01'], checkout_duration: ['p(95)<1500'], checkout_errors: ['rate<0.005'], }, };

const BASE = 'https://shop.example.vn';

Bước 3 — Viết user journey theo phễu. Mỗi VU chạy hàm default, và ta dùng Math.random() để quyết định user đó đi sâu tới đâu trong phễu.

export default function () {
  let token = null;

group('search', () => { const kw = ['ao thun', 'giay the thao', 'balo', 'dong ho']; const res = http.get(${BASE}/api/search?q=${kw[Math.floor(Math.random()*kw.length)]}); check(res, { 'search 200': (r) => r.status === 200 }); }); sleep(Math.random() * 3 + 1); // think time 1-4s

// 80% xem chi tiet san pham if (Math.random() < 0.8) { group('product_detail', () => { const id = Math.floor(Math.random() * 5000) + 1; const res = http.get(${BASE}/api/products/${id}); check(res, { 'product 200': (r) => r.status === 200 }); }); sleep(Math.random() * 3 + 2); }

// 30% dang nhap if (Math.random() < 0.3) { group('login', () => { const u = users[Math.floor(Math.random() * users.length)]; const res = http.post(${BASE}/api/auth/login, JSON.stringify(u), { headers: { 'Content-Type': 'application/json' } }); check(res, { 'login 200': (r) => r.status === 200 }); token = res.json('token'); }); }

// Chi user da login moi add cart + checkout if (token) { const authHeaders = { headers: { 'Authorization': Bearer ${token}, 'Content-Type': 'application/json' } };

if (Math.random() < 0.83) { // ~25% tong the group('add_to_cart', () => { const res = http.post(${BASE}/api/cart, JSON.stringify({ productId: Math.floor(Math.random()*5000)+1, qty: 1 }), authHeaders); check(res, { 'cart 200': (r) => r.status === 200 }); }); sleep(2);

if (Math.random() < 0.32) { // ~8% tong the group('checkout', () => { const res = http.post(${BASE}/api/checkout, JSON.stringify({ method: 'cod' }), authHeaders); const ok = check(res, { 'checkout 200': (r) => r.status === 200 }); checkoutDur.add(res.timings.duration); checkoutErr.add(!ok); }); } } } }

Chú ý ba điểm: (1) think time bằng sleep() mô phỏng người thật đọc trang, không bắn liên tục; (2) token login được truyền vào add-cart/checkout — tránh đúng lỗi ở Ví dụ 3; (3) custom metric checkout_durationcheckout_errors để soi riêng endpoint quan trọng nhất.

Bước 4 — Chạy smoke test trước. Đừng nhảy thẳng vào 200 VU. Chạy 1 VU trong 1 phút để chắc script không có bug:

k6 run --vus 1 --duration 1m ecommerce.js

Bước 5 — Chạy full test và ghi kết quả. Xuất ra file để phân tích sau (đã học ở bài CLI/output):

k6 run --out json=result.json ecommerce.js

Bước 6 — Đọc kết quả theo tiêu chí Pass/Fail. Nhìn phần summary: các threshold có dấu ✓ hay ✗? Nếu checkout_duration p(95) vượt 1500ms, đó là phát hiện đầu tiên cần đào sâu (sẽ làm ở bài Bottleneck Analysis). Ghi lại: VU tối đa đạt SLA, RPS đạt được, endpoint chậm nhất.

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

Lỗi 1 — Test thẳng lên production. Load test có thể làm sập hệ thống thật, gây thiệt hại doanh thu và ảnh hưởng khách hàng. Luôn test trên staging có cấu hình tương đương production, và nếu buộc phải chạm production thì làm ngoài giờ cao điểm với tải nhỏ, có sự đồng ý của cả team vận hành.

Lỗi 2 — Bỏ qua think time. Không có sleep(), mỗi VU bắn request nhanh gấp hàng chục lần người thật. 200 VU không think time có thể tạo tải tương đương 5000 người dùng thật — con số bạn đo được vô nghĩa. Luôn thêm think time 1–5 giây giữa các bước.

Lỗi 3 — Hardcode một sản phẩm/một user. Nếu 200 VU cùng gọi GET /api/products/123, cache sẽ trả lời tức thì và bạn đo được... sức mạnh của cache, không phải của hệ thống. Ngẫu nhiên hóa product ID và xoay vòng user để mô phỏng tải phân tán thật.

Lỗi 4 — Không tách metric cho endpoint quan trọng. Con số http_req_duration tổng thể bị "pha loãng" bởi hàng nghìn request product-detail nhẹ, che mất việc checkout đang chậm. Luôn tạo custom Trend/Rate riêng cho các endpoint sống còn.

Mẹo — Bắt đầu nhỏ, tăng dần. Chạy 50 → 100 → 200 → 400 VU thành nhiều lần, ghi lại điểm mà SLA bắt đầu vỡ. Đó chính là dữ liệu vàng cho việc capacity planning ở các bài sau.

Mẹo — Dùng tags để lọc. Gắn tag cho request (đã học ở bài Group/Tag) giúp bạn sau này lọc kết quả theo endpoint dễ dàng khi phân tích.

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

  • Xây dựng lại project cho một sàn bạn chọn. Chọn một e-commerce VN quen thuộc (hoặc tự dựng một API giả bằng công cụ như httpbin/mock server). Định nghĩa 5 endpoint và viết ra bảng workload model với tỷ lệ phễu của riêng bạn — kèm lý do cho mỗi con số.
  • Viết tiêu chí Pass/Fail bằng văn bản trước, rồi mới code Threshold. Ghi ra 4–5 câu SLA (ví dụ: "p95 toàn hệ thống dưới 800ms"). Sau đó dịch mỗi câu thành một dòng trong options.thresholds.
  • Chạy leo thang tải. Chạy script ở 50, 100, 200 VU. Lập bảng: mỗi mức có bao nhiêu RPS, p95 là bao nhiêu, threshold nào vỡ đầu tiên. Xác định "điểm gãy" gần đúng của hệ thống.
  • Tự tìm bug trong script. Cố tình bỏ dòng truyền token vào checkout, chạy lại, quan sát error rate tăng vọt. Điều này khắc sâu bài học từ Ví dụ 3: luôn nghi ngờ script trước khi kết tội hệ thống.

Tóm tắt

Bài 51 là project ghép mọi mảnh kiến thức rời rạc thành một bộ load test hoàn chỉnh cho e-commerce Việt Nam. Điểm mấu chốt không nằm ở việc học thêm tính năng, mà ở quy trình dự án đúng:

  • Scope đúng — chọn 5 endpoint trên critical path tạo ra doanh thu, không quên những endpoint "nhẹ HTTP nhưng nặng tài nguyên" như search.
  • Workload model đúng — mô phỏng phễu chuyển đổi thật (100% search → 8% checkout), không dùng tỷ lệ đều, lấy số liệu từ Analytics/log khi có thể.
  • Mô phỏng người thật — think time, ngẫu nhiên hóa data, truyền token đúng qua từng bước.
  • Định nghĩa Pass/Fail trước khi chạy — bằng Threshold, và tách metric riêng cho endpoint sống còn như checkout.
Ba câu chuyện thực tế dạy ta ba cạm bẫy lớn: workload model đều làm sai kết luận, bỏ quên search gây sập ngày sale, và bug parameterization bị nhầm thành lỗi hệ thống. Khi bạn có một bộ script chạy được và một con số dám mang đi báo cáo, bạn đã sẵn sàng cho bước tiếp theo: mổ xẻ xem điểm nghẽn nằm ở đâu.

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