Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn đang chạy một bài load test bằng k6 cho hệ thống đặt vé của một hãng xe khách lớn ở miền Nam. Test kéo dài 30 phút, mô phỏng 5.000 người dùng đồng thời. Kết thúc, k6 in ra một bảng tổng kết gọn gàng: http_req_duration avg=340ms p95=1.2s. Con số đẹp. Nhưng sếp bạn hỏi: "Ở phút thứ 12, khi tôi bấm nút bật khuyến mãi, hệ thống có bị chậm không?" — và bạn đứng hình. Vì bảng tổng kết của k6 chỉ cho bạn một con số duy nhất cho toàn bộ 30 phút. Nó không cho bạn biết chuyện gì xảy ra ở từng thời điểm.
Đây chính là lý do bài học này tồn tại. Bảng summary cuối cùng của k6 (end-of-test summary) rất hữu ích, nhưng nó là ảnh chụp trung bình — nó "làm phẳng" mọi biến động theo thời gian. Trong khi đó, vấn đề performance thật sự luôn nằm ở diễn biến theo thời gian: latency tăng vọt khi số user vượt ngưỡng, error rate nhảy lên khi database cạn connection pool, throughput sụt xuống đúng lúc garbage collector chạy. Bạn cần nhìn thấy những biến động này ngay khi chúng đang xảy ra, không phải đợi test kết thúc rồi mới đoán.
Giải pháp kinh điển là bộ ba k6 → InfluxDB → Grafana: k6 bắn từng metric ra một cơ sở dữ liệu chuỗi thời gian (InfluxDB), rồi Grafana đọc dữ liệu đó và vẽ thành biểu đồ trực quan, cập nhật real-time. Bạn vừa chạy test vừa nhìn dashboard, thấy đường latency bắt đầu cong lên là biết ngay ngưỡng chịu tải của hệ thống nằm ở đâu. Đây là kỹ năng tách biệt một người "biết chạy k6" với một Performance Engineer thực thụ.
Khái niệm cốt lõi
Vì sao cần một time-series database?
Metric performance có một đặc điểm: mỗi điểm dữ liệu đều gắn với một mốc thời gian (timestamp). "Lúc 14:03:22, request này mất 210ms." Loại dữ liệu này gọi là time-series data (dữ liệu chuỗi thời gian). Nếu nhét vào một database quan hệ thông thường (MySQL), việc ghi hàng chục nghìn điểm mỗi giây và truy vấn theo khoảng thời gian sẽ rất chậm.
InfluxDB là database chuyên dụng cho time-series. Nó được thiết kế để nuốt (ingest) lượng lớn điểm dữ liệu tốc độ cao và trả lời nhanh các câu hỏi kiểu "cho tôi p95 latency mỗi 5 giây trong 10 phút qua". Đây là mắt xích lưu trữ giữa k6 và Grafana.
Luồng dữ liệu — ai làm gì
Kiến trúc rất rõ ràng, mỗi thành phần một nhiệm vụ:
k6 (chạy test, tạo tải)
→ InfluxDB (lưu metric theo thời gian)
→ Grafana (đọc & vẽ biểu đồ real-time)
- k6 là "người tạo tải và đo đạc". Trong lúc chạy, mỗi khi hoàn thành một request, k6 sinh ra các metric (
http_req_duration,http_reqs,vus...). Thay vì chỉ giữ trong bộ nhớ để tính summary, k6 stream (bắn liên tục) các metric này sang InfluxDB. - InfluxDB là "sổ ghi chép theo thời gian". Nó nhận metric, gắn timestamp, lưu lại theo tổ chức measurement/tag/field.
- Grafana là "màn hình quan sát". Nó kết nối tới InfluxDB như một data source, chạy query định kỳ (ví dụ mỗi 5 giây) và cập nhật các panel biểu đồ.
--out influxdb=.... Với k6 phiên bản open-source, output InfluxDB v1 (dạng k6 run --out influxdb=http://localhost:8086/k6) là con đường ổn định và phổ biến nhất — đó là lý do đa số dashboard mẫu trên mạng vẫn dùng InfluxDB 1.8.Vì sao thường dùng docker-compose
Cài đặt InfluxDB và Grafana thủ công, đúng version, đúng cấu hình mạng, đúng quyền — rất dễ sai. docker-compose cho phép bạn khai báo cả hai dịch vụ trong một file duy nhất, chạy một lệnh là có nguyên bộ hạ tầng dashboard. Đây là cách setup được khuyến nghị vì nó tái lập được (reproducible): đồng nghiệp của bạn git clone về, gõ docker compose up, là có y hệt môi trường của bạn, không "trên máy tôi chạy được" nữa.
metric của k6 được lưu như thế nào
Khi bắn sang InfluxDB, mỗi metric của k6 trở thành một measurement. Ví dụ http_req_duration là một measurement, mỗi điểm dữ liệu kèm theo các tag (như status, method, name, scenario) và field (giá trị thực như thời gian ms). Nhờ các tag này, trên Grafana bạn có thể lọc và tách biểu đồ — ví dụ chỉ xem latency của các request tới /api/checkout, hoặc chỉ xem những request trả về HTTP 500. Chính khả năng lọc theo tag là thứ biến dashboard từ "đẹp" thành "hữu dụng để chẩn đoán".
Tình huống thực tế
Ví dụ 1 — Tiki phát hiện ngưỡng gãy nhờ dashboard real-time
Một đội QA giả định tại một sàn thương mại điện tử lớn (gọi là "sàn A", quy mô như Tiki) cần biết trang danh sách sản phẩm chịu được bao nhiêu user trước khi "gãy". Họ dựng dashboard k6 + InfluxDB + Grafana, rồi chạy một test tăng dần từ 100 lên 3.000 VUs trong 20 phút.
Với bảng summary thông thường, họ chỉ thấy p95=2.4s — một con số trung bình vô hồn. Nhưng nhìn dashboard Grafana theo thời gian, họ thấy rõ: latency giữ ổn định dưới 400ms cho tới khoảng 1.800 VUs, sau đó đường p95 dựng đứng lên gần 6 giây, và cùng thời điểm đó đường error rate bắt đầu leo từ 0% lên 8%. Bài học rút ra: dashboard real-time cho họ một con số hành động được — "hệ thống hiện tại đỡ được ~1.800 người xem sản phẩm đồng thời" — thay vì một con số trung bình không nói lên điều gì. Họ báo cáo đúng ngưỡng cần nâng cấp trước mùa sale.
Ví dụ 2 — Ví điện tử phát hiện "răng cưa" do GC
Một đội performance tại một ví điện tử (bối cảnh giả định kiểu MoMo) chạy soak test 2 giờ cho API kiểm tra số dư. Trên bảng summary, mọi thứ ổn: avg=95ms. Nhưng trên dashboard Grafana, đường throughput có hình răng cưa đều đặn: cứ khoảng 4 phút lại có một cú sụt ngắn, kèm theo latency nhảy vọt lên 800ms trong vài giây rồi trở lại bình thường.
Vì họ thấy được thời điểm chính xác của mỗi cú sụt, đội hạ tầng đối chiếu với log của service và phát hiện đó là những lần garbage collector (GC) của JVM chạy "stop-the-world". Bài học rút ra: biểu đồ theo thời gian phơi bày các vấn đề chu kỳ mà con số trung bình che giấu hoàn toàn. Nếu chỉ nhìn avg=95ms, không ai nghi ngờ gì cả; nhưng khách hàng thật gặp cú giật 800ms đó sẽ tưởng ví bị treo.
Ví dụ 3 — Giám sát ngay lúc test để dừng sớm
Một startup logistics ở TP.HCM chạy load test lần đầu cho hệ thống theo dõi đơn hàng. Test dự kiến chạy 40 phút. Nhờ mở Grafana song song, ngay phút thứ 3 kỹ sư đã thấy error rate là 100% — mọi request đều lỗi. Họ dừng test ngay lập tức, kiểm tra và phát hiện họ trỏ nhầm sang môi trường staging đã bị tắt.
Bài học rút ra: dashboard real-time tiết kiệm thời gian và tài nguyên. Nếu không có nó, họ đã ngồi chờ 40 phút rồi mới đọc summary và phát hiện toàn bộ lần chạy là vô ích. Khả năng "nhìn thấy ngay" cho phép fail fast — dừng sớm, sửa nhanh, chạy lại.
Hướng dẫn từng bước
Bước 1 — Dựng InfluxDB + Grafana bằng docker-compose
Tạo file docker-compose.yml:
version: "3.8"
services:
influxdb:
image: influxdb:1.8
ports:
- "8086:8086"
environment:
- INFLUXDB_DB=k6 # tạo sẵn database tên "k6"
- INFLUXDB_HTTP_MAX_BODY_SIZE=0
volumes:
- influx-data:/var/lib/influxdb grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true # cho phép xem không cần login (chỉ dùng local)
- GF_AUTH_BASIC_ENABLED=false
depends_on:
- influxdb
volumes:
influx-data:
Chạy lệnh:
docker compose up -d
Ba dòng đáng chú ý: INFLUXDB_DB=k6 tạo sẵn database để k6 ghi vào; influx-data volume giúp dữ liệu không mất khi restart container; các biến GF_AUTH_* cho phép Grafana mở luôn dashboard mà không cần đăng nhập — chỉ nên dùng ở môi trường local, tuyệt đối không mở anonymous như vậy trên máy chủ công khai.
Bước 2 — Chạy k6 và bắn metric sang InfluxDB
Giả sử bạn có script test.js. Chạy với cờ --out:
k6 run --out influxdb=http://localhost:8086/k6 test.js
Cờ --out influxdb=... bảo k6: ngoài việc in summary như thường lệ, hãy stream toàn bộ metric sang InfluxDB tại địa chỉ đó, ghi vào database k6. Ngay khi test chạy, InfluxDB bắt đầu nhận dữ liệu.
Nếu k6 chạy trong container mà InfluxDB cũng trong Docker, nhớ thay localhost bằng tên service (http://influxdb:8086/k6) hoặc host.docker.internal tùy cách bạn nối mạng — đây là lỗi kết nối phổ biến nhất.
Bước 3 — Thêm InfluxDB làm data source trong Grafana
Mở trình duyệt vào http://localhost:3000. Vào Connections → Data sources → Add data source → InfluxDB. Điền:
- Query Language: InfluxQL
- URL:
http://influxdb:8086(dùng tên service vì Grafana đang trong cùng Docker network) - Database:
k6
Bước 4 — Nhập dashboard mẫu
Bạn không cần vẽ biểu đồ từ đầu. Cộng đồng k6 có sẵn dashboard mẫu trên Grafana.com. Vào Dashboards → Import → nhập ID (dashboard k6 phổ biến nhất là ID 2587), chọn data source InfluxDB vừa tạo, bấm Import. Ngay lập tức bạn có một dashboard đầy đủ: số VUs theo thời gian, request rate, các đường latency p90/p95/p99, error rate, và nhiều panel khác.
Bước 5 — Chạy test và quan sát live
Chạy lại k6 run --out influxdb=..., mở dashboard vừa import, đặt time range ở góc phải thành Last 5 minutes và bật auto-refresh 5s. Bạn sẽ thấy các đường biểu đồ bò dần sang phải theo thời gian thực. Đây chính là "live dashboard" — vừa test vừa nhìn hệ thống phản ứng.
Bước 6 — Đọc và diễn giải
Tập trung vào ba đường quan trọng nhất, và luôn xếp chúng cạnh nhau:
- VUs (số user ảo) — trục "nguyên nhân", cho biết mức tải hiện tại.
- Request rate / throughput — hệ thống đang phục vụ được bao nhiêu request mỗi giây.
- p95 latency & error rate — trục "hậu quả". Khi VUs tăng mà p95 và error rate vẫn phẳng, hệ thống còn khỏe. Khi VUs tăng mà p95 cong lên hoặc error rate nhích lên, bạn vừa tìm thấy điểm bắt đầu gãy.
Lỗi thường gặp & mẹo
Lỗi trỏ nhầm localhost trong Docker. Đây là lỗi số một. Khi k6 hoặc Grafana chạy trong container, localhost trỏ về chính container đó, không phải InfluxDB. Dùng tên service (influxdb) trong cùng docker network, hoặc host.docker.internal nếu k6 chạy ngoài Docker trên máy Mac/Windows.
Chọn nhầm phiên bản InfluxDB. Output --out influxdb= mặc định của k6 open-source nói chuyện với InfluxDB v1 (dùng influxdb:1.8). Nếu bạn vô tình kéo InfluxDB 2.x, giao thức và cách xác thực khác hẳn, k6 sẽ báo lỗi ghi. Với người mới, hãy bám chặt influxdb:1.8 cho đến khi thành thạo.
Cardinality bùng nổ vì tag URL động. Nếu URL của bạn chứa ID động (/order/12345, /order/12346...) và bạn để nguyên làm tag, InfluxDB sẽ sinh ra hàng triệu chuỗi khác nhau, làm nó phình bộ nhớ và chậm. Mẹo: dùng http.get(url, { tags: { name: '/order/:id' } }) trong k6 để gộp các request cùng loại về một cái tên, giữ cardinality thấp.
Test dài làm InfluxDB đầy ổ đĩa. Soak test nhiều giờ sinh khối lượng điểm dữ liệu khổng lồ. Nhớ đặt volume và, với môi trường lâu dài, cấu hình retention policy để tự dọn dữ liệu cũ. Với test ngắn thì không cần lo.
Đừng chỉ nhìn đường trung bình. Mẹo vàng của cả bài: luôn thêm panel p95 và p99, đừng chỉ xem avg. Trung bình che giấu những cú giật mà người dùng thật cảm nhận rõ nhất. Một hệ thống avg=100ms nhưng p99=3s nghĩa là cứ 100 người thì có 1 người đợi 3 giây — hoàn toàn không chấp nhận được với luồng thanh toán.
Mẹo đối chiếu thời gian. Khi thấy một cú tăng latency lúc 14:12, hãy ghi lại mốc đó rồi vào log của backend đúng khoảng thời gian đó. Dashboard k6 cho bạn biết "khi nào có vấn đề"; log hệ thống cho bạn biết "vì sao". Ghép hai nguồn này lại là cách chẩn đoán nhanh nhất.
Bài tập thực hành
- Dựng stack. Tạo file
docker-compose.ymlnhư hướng dẫn, chạydocker compose up -d, xác nhận InfluxDB ở cổng 8086 và Grafana ở cổng 3000 đều truy cập được.
- Bắn metric. Viết một script k6 đơn giản gọi tới
https://test.k6.iovới 50 VUs trong 3 phút, chạy kèm--out influxdb=http://localhost:8086/k6. Kiểm tra InfluxDB đã có measurementhttp_req_durationchưa (dùnginfluxCLI:SHOW MEASUREMENTS).
- Import dashboard. Thêm InfluxDB làm data source trong Grafana, import dashboard ID 2587, và quan sát dữ liệu hiện lên.
- Tìm điểm gãy. Sửa script thành một stage tăng dần từ 100 lên 2.000 VUs trong 10 phút. Vừa chạy vừa mở Grafana với auto-refresh 5s. Ghi lại mốc VUs mà tại đó p95 bắt đầu vượt 1 giây và mốc mà error rate vượt 1%. Viết một câu kết luận về ngưỡng chịu tải.
- Nâng cao — gộp tag. Thêm một request tới URL có ID động, gắn
tags: { name: '/item/:id' }, và xác nhận trên Grafana các request này gom về một dòng thay vì tách rời.
Tóm tắt
Bảng summary cuối test của k6 chỉ cho bạn con số trung bình của toàn bộ lần chạy — nó làm phẳng mọi biến động theo thời gian và che giấu chính những vấn đề bạn cần tìm. Bộ ba k6 → InfluxDB → Grafana giải quyết điều đó: k6 stream metric qua cờ --out influxdb=, InfluxDB lưu chúng dưới dạng chuỗi thời gian, và Grafana vẽ thành dashboard real-time để bạn quan sát hệ thống phản ứng ngay khi test đang chạy.
Cách dựng nhanh và tái lập được là dùng docker-compose với influxdb:1.8 và grafana:latest, sau đó import dashboard mẫu (ID 2587) thay vì vẽ tay. Khi đọc dashboard, hãy luôn đặt VUs cạnh p95 latency và error rate để nhìn ra điểm bắt đầu gãy, và luôn ưu tiên p95/p99 hơn giá trị trung bình. Ba tình huống — sàn thương mại điện tử tìm ngưỡng 1.800 VUs, ví điện tử phát hiện răng cưa do GC, và startup dừng test sớm nhờ thấy error 100% — đều minh họa cùng một chân lý: nhìn thấy diễn biến theo thời gian mới là chẩn đoán, còn nhìn một con số trung bình chỉ là báo cáo. Ở các bài sau, bạn sẽ dùng chính kỹ năng đọc dashboard này để đào sâu vào phân tích điểm nghẽn và lập kế hoạch năng lực.