Menu
ESC

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

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

Đang tải...

Capstone — Production-ready Performance Suite

Performance Testing with JMeter and k6 Bài 59/60

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

Chúng ta đã đi qua gần 60 bài. Bạn biết cách viết script JMeter, cách khai báo stages trong k6, cách đặt thresholds, cách đọc report, cách phân tích bottleneck, cách chạy trong CI/CD. Nhưng có một khoảng cách rất lớn giữa "biết từng mảnh ghép" và "ghép được một hệ thống performance testing thật, cho một hệ thống thật, mà đội của bạn dám tin để ra quyết định release".

Bài Capstone này chính là cầu nối đó. Bạn sẽ không học thêm một công cụ mới. Thay vào đó, bạn sẽ đóng vai một Performance Engineer vừa được nhận vào VietBank — một ngân hàng số giả định có 4 triệu người dùng, đang chuẩn bị lên phiên bản mới của Core Banking API. Ban lãnh đạo yêu cầu: "Trước mỗi lần release, phải có một bộ performance suite tự động chạy, cho biết rõ ràng hệ thống có sẵn sàng hay không — chứ không phải mỗi lần lại một anh dev tay ngang bật JMeter GUI rồi đoán."

Nhiệm vụ của bạn là xây dựng một bộ performance suite production-ready: có cấu trúc thư mục rõ ràng, chạy được trên 3 môi trường (staging, pre-prod, prod canary), phủ 10 endpoint quan trọng nhất, có SLA gate tự động, có báo cáo, và cắm được vào pipeline. Đây là thứ bạn sẽ đưa vào portfolio, và cũng là thứ nhà tuyển dụng muốn thấy khi phỏng vấn vị trí Performance Engineer.

Nói cách khác: các bài trước dạy bạn "chơi từng nốt nhạc". Bài này bạn phải chơi cả bản giao hưởng — và chơi cho một sân khấu thật.

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

Performance suite là gì (khác với một cái test)

Một "test" là một file script chạy một kịch bản. Một "suite" (bộ) là một hệ thống có tổ chức gồm nhiều test, dữ liệu, cấu hình môi trường, ngưỡng SLA, cơ chế báo cáo và tài liệu — được thiết kế để chạy lặp đi lặp lại, đáng tin cậy, và cho ra quyết định "pass/fail" một cách khách quan.

Sự khác biệt then chốt là tính tái lập (reproducibility)tính quyết định (decision-making). Một suite production-ready phải trả lời được câu hỏi: "Nếu tôi chạy lại đúng lệnh này vào tuần sau, trên cùng môi trường, tôi có nhận được kết quả so sánh được không?" và "Kết quả này có tự động chặn được một release tồi không?"

Bốn trụ cột của một suite production-ready

1. Tách cấu hình khỏi kịch bản (config vs logic). Cùng một script test đăng nhập, nhưng chạy trên staging thì gọi https://staging-api.vietbank.vn, trên prod canary thì gọi https://api.vietbank.vn với header canary. Bạn KHÔNG được sửa script cho mỗi môi trường — bạn truyền config vào. Trong k6 dùng biến môi trường (__ENV) hoặc file JSON config; trong JMeter dùng file .properties và biến ${__P(...)}.

2. Phân tầng môi trường (environment tiers). Ba môi trường có ba mục đích khác nhau:

  • Staging: kiểm nghiệm chức năng của chính bộ test, tải nhẹ, chạy thường xuyên. Ở đây bạn hỏi "script còn chạy đúng không?".
  • Pre-prod: cấu hình gần production nhất, dùng để chạy load test đầy đủ, tìm bottleneck thật. Đây là nơi bạn hỏi "hệ thống chịu được bao nhiêu?".
  • Prod canary: chạy tải rất nhẹ (smoke) trực tiếp trên production, chỉ trên một phần nhỏ hạ tầng (canary), để xác nhận "phiên bản mới trên môi trường thật có degrade không?". Đây là nơi bạn hỏi "release này có an toàn không?".
3. SLA gate tự động. Suite phải có ngưỡng rõ ràng — ví dụ p95 latency < 800ms, error rate < 0.5% — và tự động fail nếu vi phạm. Trong k6, đây là thresholds; trong JMeter, đây là kết hợp Assertions + một bước phân tích kết quả (thường là plugin hoặc script hậu xử lý).

4. Báo cáo và lưu vết (reporting & artifacts). Mỗi lần chạy phải để lại "dấu vết": file kết quả thô, một báo cáo HTML/summary, và tốt nhất là đẩy metric vào một hệ thống lưu trữ để theo dõi xu hướng theo thời gian.

10 critical endpoint — chọn thế nào cho đúng

Với VietBank, đừng test 200 endpoint. Hãy chọn 10 endpoint mà nếu chậm hoặc chết thì khách hàng và doanh nghiệp đau nhất. Nguyên tắc chọn: theo doanh thu, theo tần suất, theo rủi ro.

Ví dụ danh sách 10 endpoint của VietBank: POST /auth/login, GET /accounts/balance, GET /transactions/history, POST /transfers/internal, POST /transfers/interbank (napas), POST /bills/pay, GET /cards/list, POST /qr/generate, POST /qr/pay, GET /notifications. Mỗi endpoint được gán một trọng số (weight) phản ánh tỷ lệ traffic thật — ví dụ balancelogin chiếm phần lớn, còn interbank ít hơn nhưng rủi ro cao nhất vì phụ thuộc bên thứ ba.

Tình huống thực tế

Ví dụ 1: VietBank — khi "một file JMeter" không đủ để ra quyết định release

Trước khi có bạn, đội QA của VietBank có một file test_all.jmx nặng 4.000 dòng, chứa mọi thứ. Mỗi lần release, một bạn QA mở JMeter GUI, sửa tay URL từ staging sang prod, nhấn chạy, rồi nhìn cột "Average" trong View Results Tree và nói "trông ổn". Kết quả: một lần lên bản mới, endpoint POST /transfers/interbank có p95 nhảy từ 900ms lên 3.200ms nhưng average vẫn "đẹp" vì đa số request nhanh — không ai phát hiện. Đến 9h sáng thứ Hai, hàng nghìn giao dịch liên ngân hàng timeout, tổng đài cháy máy.

Bạn vào và tái cấu trúc: tách suite thành 10 script k6 nhỏ (mỗi endpoint một module) + một file config/environments.json + thresholds gán riêng cho từng endpoint. Đặc biệt, với interbank bạn set http_req_duration: ['p95<1500'] như một gate cứng. Lần release kế tiếp, pipeline tự động fail vì p95 vượt ngưỡng, và bản build tồi bị chặn ngay ở pre-prod, trước khi chạm khách hàng.

Bài học: average che giấu thảm họa; suite production-ready ép ta nhìn percentile và biến ngưỡng thành gate tự động, thay cho cảm tính của một con người mệt mỏi lúc 5h chiều.

Ví dụ 2: Tiki (giả định hợp lý) — cùng một suite, ba môi trường, ba mục đích

Đội performance của một sàn TMĐT lớn tại Việt Nam từng mắc lỗi kinh điển: chạy full load test 5.000 VUs thẳng lên production vào giờ trưa để "cho chắc". Kết quả họ tự tay tạo ra một sự cố (self-inflicted outage) — chính họ làm sập hệ thống thật của mình.

Cách làm đúng, mà bạn sẽ áp dụng cho VietBank: cùng một script, nhưng khác stages:

  • Staging: 10 VUs / 2 phút — chỉ để xác nhận script chạy.
  • Pre-prod: 2.000 VUs / 30 phút — load test thật, tìm điểm nghẽn.
  • Prod canary: 20 VUs / 5 phút, chỉ đánh vào các instance được gắn nhãn canary, với ngưỡng cực gắt — chỉ để phát hiện regression, tuyệt đối không phải để "phá tải".
Suite của bạn đọc profile tải từ config, nên chuyển môi trường chỉ là đổi một tham số dòng lệnh: k6 run -e ENV=canary suite.js. Không ai còn phải sửa script, nên cũng không còn ai vô tình chạy 5.000 VUs lên prod.

Bài học: môi trường không chỉ khác URL — chúng khác cả ý định. Suite tốt mã hóa ý định đó thành config, biến sai lầm nguy hiểm thành điều "không thể xảy ra do vô ý".

Ví dụ 3: Fintech Đông Nam Á — suite gắn vào cổng release, biến performance thành "luật"

Một công ty ví điện tử ở khu vực (kiểu MoMo/GrabPay) đưa suite performance vào GitLab CI như một stage bắt buộc trước khi merge vào nhánh release. Quy tắc: nếu smoke test trên prod canary có bất kỳ threshold nào đỏ, merge bị chặn, không ai — kể cả team lead — được override thủ công mà không mở một ticket giải trình.

Lúc đầu đội dev khó chịu vì "chậm". Nhưng sau ba tháng, số sự cố performance trên production giảm 70%, và điều quan trọng hơn: những cuộc tranh cãi cảm tính ("tôi thấy nó vẫn nhanh mà") biến mất, thay bằng một con số khách quan từ suite.

Bài học: giá trị cao nhất của một performance suite production-ready không phải là con số nó tạo ra, mà là quyết định nó tự động hóa. Nó biến performance từ "ý kiến" thành "cổng chặn".

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

Dưới đây là quy trình dựng bộ VietBank Performance Suite.

Bước 1 — Dựng cấu trúc thư mục. Một suite production-ready trông có tổ chức ngay từ cây thư mục:

vietbank-perf-suite/
├── config/
│   ├── environments.json      # URL, header, credential theo môi trường
│   └── thresholds.json        # SLA gate cho từng endpoint
├── data/
│   └── users.csv              # test data (parameterization)
├── scenarios/
│   ├── login.js
│   ├── balance.js
│   ├── transfer-internal.js
│   ├── transfer-interbank.js
│   └── ...                    # tổng 10 endpoint
├── lib/
│   └── helpers.js             # auth token, header chung
├── suite.js                   # entrypoint gom mọi scenario
├── run.sh                     # wrapper chạy theo môi trường
└── README.md                  # tài liệu vận hành

Bước 2 — Tách config môi trường. File environments.json:

{
  "staging":  { "baseUrl": "https://staging-api.vietbank.vn", "vus": 10,   "duration": "2m" },
  "preprod":  { "baseUrl": "https://preprod-api.vietbank.vn", "vus": 2000, "duration": "30m" },
  "canary":   { "baseUrl": "https://api.vietbank.vn", "vus": 20, "duration": "5m", "headers": { "X-Canary": "true" } }
}

Trong k6, đọc bằng const env = JSON.parse(open('./config/environments.json'))[__ENV.ENV || 'staging'].

Bước 3 — Định nghĩa SLA gate theo từng endpoint. Đừng dùng một ngưỡng chung cho tất cả. login có thể cho phép p95 < 800ms, nhưng balance (đọc thuần) phải < 300ms, còn interbank (gọi napas) khoan dung hơn, p95 < 1500ms. Trong k6:

export const options = {
  thresholds: {
    'http_req_duration{endpoint:balance}':  ['p95<300'],
    'http_req_duration{endpoint:login}':    ['p95<800'],
    'http_req_duration{endpoint:interbank}':['p95<1500'],
    'http_req_failed': ['rate<0.005'],   // error rate < 0.5% toàn cục
  },
};

Chú ý {endpoint:...}tag — bạn gắn tag trong từng request để threshold biết áp cho endpoint nào.

Bước 4 — Viết scenario dùng weight phản ánh traffic thật. Dùng k6 scenarios với executor ramping-vus, và trong hàm chính, gọi các endpoint theo tỷ lệ trọng số (ví dụ 40% balance, 20% login, 5% interbank). Điều này giữ cho load test giống hành vi thật chứ không phải nện đều mọi endpoint.

Bước 5 — Chuẩn hóa lệnh chạy. File run.sh:

#!/bin/bash
ENV=${1:-staging}
k6 run -e ENV=$ENV \
  --out json=results/$ENV-$(date +%Y%m%d-%H%M).json \
  --summary-export=results/$ENV-summary.json \
  suite.js

Giờ cả team chỉ cần: ./run.sh preprod. Đơn giản, không thể chạy sai.

Bước 6 — Cắm vào CI/CD như một gate. Trong pipeline, stage perf-canary chạy ./run.sh canary. Vì k6 trả về exit code khác 0 khi threshold đỏ, pipeline tự động fail — không cần logic thêm. Đây chính là chỗ suite biến thành cổng chặn release.

Bước 7 — Báo cáo và lưu vết. Lưu file JSON kết quả làm artifact, sinh một summary HTML (handleSummary trong k6), và với pre-prod thì đẩy metric sang InfluxDB + Grafana để theo dõi xu hướng qua các lần release. Mỗi lần chạy phải để lại bằng chứng.

Bước 8 — Viết README vận hành. Người kế nhiệm bạn phải chạy được suite mà không cần hỏi. Ghi rõ: prerequisite, cách chạy từng môi trường, ý nghĩa từng threshold, và ai được duyệt override khi gate đỏ.

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

Lỗi 1 — Hardcode URL và credential vào script. Đây là lỗi khiến suite "không production-ready". Mỗi lần đổi môi trường lại sửa code là công thức cho tai họa. Luôn tách ra config/.

Lỗi 2 — Chạy full load lên production. Prod canary là smoke, không phải stress. Nhầm lẫn này có thể khiến chính bạn gây sự cố. Tải trên canary phải nhẹ và có ngưỡng gắt để phát hiện regression, không phải để phá.

Lỗi 3 — Một threshold chung cho mọi endpoint. Endpoint đọc và endpoint gọi bên thứ ba có bản chất latency khác nhau. Ngưỡng chung hoặc quá lỏng (bỏ sót lỗi) hoặc quá chặt (báo động giả).

Lỗi 4 — Không lưu kết quả. Nếu không có artifact, bạn không so sánh được release này với release trước, và mất khả năng phát hiện regression từ từ (creeping regression).

Mẹo 1 — Test dữ liệu phải "sạch và riêng". Trên canary chạy prod thật, hãy dùng tài khoản test được đánh dấu riêng, đừng đụng dữ liệu khách thật; và với các endpoint ghi (transfer, pay) trên canary, cân nhắc chỉ chạy đến bước validate, không commit giao dịch thật.

Mẹo 2 — Gán exit code làm chân lý. Đừng để con người "nhìn báo cáo rồi quyết". Hãy để threshold + exit code quyết, con người chỉ xem xét khi gate đỏ.

Mẹo 3 — Version hóa cả suite. Đưa vietbank-perf-suite/ vào Git. Threshold thay đổi cũng phải qua code review — vì nới lỏng một ngưỡng chính là một quyết định về chất lượng.

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

  • Dựng khung suite. Tạo cấu trúc thư mục như trên cho một hệ thống bạn chọn (có thể là VietBank giả định hoặc một API công khai). Viết config/environments.json với 3 môi trường staging / pre-prod / canary.
  • Viết 3 scenario có tag và threshold riêng. Chọn 3 endpoint (ví dụ login, balance, transfer). Gắn tag {endpoint:...} cho từng request và đặt threshold khác nhau cho mỗi endpoint trong options.
  • Chạy hai môi trường, so sánh. Chạy ./run.sh staging rồi ./run.sh preprod (dùng profile tải nhỏ để không tốn tài nguyên). Quan sát cùng một script cho ra hai profile tải khác nhau chỉ nhờ đổi config.
  • Cố tình làm gate đỏ. Hạ một threshold xuống mức phi thực tế (ví dụ p95<10) và xác nhận k6 trả về exit code khác 0. Đây là bằng chứng suite của bạn thật sự chặn được release.
  • Viết README vận hành 1 trang. Giả sử bạn nghỉ việc ngày mai — người kế nhiệm đọc README có tự chạy được suite và hiểu từng gate không?

Tóm tắt

  • Một performance suite production-ready không phải một file test, mà là một hệ thống có tổ chức: config tách khỏi logic, phân tầng môi trường, SLA gate tự động, và báo cáo/lưu vết.
  • Ba môi trường ba ý định: staging kiểm nghiệm chính bộ test, pre-prod tìm bottleneck bằng load đầy đủ, prod canary chạy smoke gắt để phát hiện regression trên môi trường thật.
  • Chọn 10 endpoint quan trọng nhất theo doanh thu/tần suất/rủi ro, gán weight phản ánh traffic thật và threshold riêng cho từng endpoint.
  • Giá trị lớn nhất của suite là tự động hóa quyết định release: threshold + exit code biến performance từ "ý kiến cảm tính" thành "cổng chặn khách quan" trong CI/CD.
  • Ba ví dụ VietBank, sàn TMĐT và fintech khu vực đều dạy cùng một điều: sự cố performance hiếm khi vì thiếu công cụ, mà vì thiếu tổ chức và tính tự động. Capstone này chính là bằng chứng bạn đã có cả hai — hãy đưa nó vào portfolio của mình.