Product Management
Đăng nhập
ESC

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

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

Common mistakes & anti-patterns

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

Sau gần 60 bài học, bạn đã có trong tay đủ công cụ và kỹ thuật để dựng một hệ thống performance testing hoàn chỉnh: từ JMeter, k6, đến CI/CD, Grafana, capacity planning. Nhưng có một sự thật phũ phàng mà mình muốn chia sẻ thật lòng với bạn: phần lớn các dự án performance testing thất bại không phải vì thiếu công cụ, mà vì làm sai một cách có hệ thống.

Mình đã chứng kiến những đội QA bỏ ra ba tuần chạy load test, xuất ra hàng chục biểu đồ đẹp long lanh, gửi report cho sếp với kết luận "hệ thống chịu được 10.000 user" — rồi đến ngày sự kiện thật, hệ thống sập trong 15 phút. Không phải công cụ sai. Không phải người chạy thiếu năng lực. Vấn đề nằm ở những cái bẫy tư duy và anti-pattern khiến toàn bộ con số họ đo được trở nên vô nghĩa.

Bài học này chính là "danh sách những viên đạn bạn sẽ ăn nếu không cẩn thận". Mình sẽ không dạy bạn thêm kỹ thuật mới — thay vào đó, mình gom lại những sai lầm phổ biến nhất mà mình và đồng nghiệp đã trả giá để học được, để bạn tránh được chúng ngay từ đầu. Nắm vững bài này, bạn sẽ trở thành người trong đội biết đặt câu hỏi "Khoan đã, con số này có đáng tin không?" — và đó chính là dấu hiệu của một Performance Engineer thực thụ, không phải một người chỉ biết bấm nút Run.

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

Anti-pattern trong performance testing là những cách làm trông có vẻ đúng, cho ra kết quả, nhưng dẫn đến kết luận sai lệch nguy hiểm. Cái nguy hiểm nhất của performance testing sai không phải là "không có kết quả", mà là "có kết quả nhưng sai" — bạn tự tin đưa hệ thống lên production dựa trên một con số không phản ánh thực tế.

Mình chia các sai lầm thành bốn nhóm lớn để bạn dễ nhớ và tự kiểm tra.

Nhóm 1 — Sai từ khâu thiết kế môi trường

Test trên môi trường dev/staging rồi suy ra production. Đây là sai lầm kinh điển nhất. Môi trường dev thường chạy trên máy yếu hơn, database chỉ có vài nghìn record thay vì vài triệu, không có load balancer, cache Redis cấu hình khác, network topology khác hẳn. Một query chạy 20ms trên database 5.000 dòng có thể ngốn 3 giây trên database 5 triệu dòng vì thiếu index. Bạn test trên dev thấy "ngon", lên prod là thảm họa. Kết quả performance test chỉ đáng tin khi môi trường test giống production về mọi mặt trọng yếu: cấu hình phần cứng, khối lượng dữ liệu, cấu hình cache, network.

Bỏ qua "state" của dữ liệu. Test đăng ký user mới 10.000 lần trên một database trống thì nhanh, nhưng khi bảng users đã có 50 triệu dòng, mỗi lần INSERT phải cập nhật index khổng lồ — hoàn toàn khác. Dữ liệu test phải phản ánh đúng quy mô và độ "già" của dữ liệu production.

Nhóm 2 — Sai từ khâu thiết kế kịch bản

Test một endpoint thay vì cả user journey. Bạn dí 5.000 request/giây vào riêng endpoint /api/products và tuyên bố hệ thống chịu tốt. Nhưng user thật không chỉ xem sản phẩm — họ đăng nhập, tìm kiếm, thêm giỏ hàng, thanh toán. Mỗi bước chạm vào những service khác nhau, và điểm nghẽn thật thường nằm ở bước thanh toán (chạm payment gateway, ghi transaction) chứ không phải ở việc đọc danh sách sản phẩm. Test một endpoint đơn lẻ cho bạn cảm giác an toàn giả tạo.

Bỏ qua think time. User thật không bắn request liên tục không nghỉ. Họ đọc trang, suy nghĩ, gõ phím — có "think time" 3-10 giây giữa các thao tác. Nếu bạn không đưa think time vào script, 100 virtual user của bạn tạo ra tải tương đương 1.000 user thật, khiến bạn kết luận sai về sức chịu tải. Ngược lại, quá nhiều think time lại làm bạn đánh giá quá lạc quan.

Không có ramp-up, đấm thẳng full load. Bung 10.000 user cùng lúc trong giây đầu tiên là kịch bản phi thực tế (trừ khi test spike có chủ đích). Hệ thống thật cần thời gian "khởi động" cache, connection pool. Ramp-up từ từ mới mô phỏng đúng cách tải tăng dần trong đời thực.

Nhóm 3 — Sai từ khâu đo lường và đọc kết quả

Chỉ nhìn giá trị trung bình (average). Đây là cái bẫy giết chết nhiều report. Average latency 200ms nghe rất đẹp, nhưng nó che giấu sự thật. Nếu 90% request trả về trong 50ms và 10% trả về trong 1.700ms, average vẫn ra khoảng 215ms — trong khi thực tế cứ 10 user thì có 1 người phải chờ gần 2 giây. Luôn nhìn percentile: p95, p99. p99 = 1.700ms nghĩa là 1% user tệ nhất chờ 1,7 giây — đủ để họ bỏ giỏ hàng.

Bỏ qua error rate khi đọc throughput. Hệ thống "xử lý 8.000 request/giây" nghe hoành tráng, nhưng nếu 40% trong số đó là lỗi HTTP 500 hoặc timeout thì con số throughput đó vô nghĩa — thực chất server đang chết và trả lỗi rất nhanh. Response time thấp đôi khi là dấu hiệu xấu: server fail nhanh còn nhanh hơn xử lý đúng.

Coordinated omission — điểm mù chết người. Đây là lỗi tinh vi mà ngay cả người có kinh nghiệm cũng mắc. Khi công cụ load test gửi request theo kiểu "gửi xong request này mới gửi request kế", nếu server bị nghẽn khiến một request treo 5 giây, thì trong 5 giây đó công cụ không gửi thêm request nào — nghĩa là nó bỏ sót đúng khoảng thời gian tệ nhất. Kết quả: report của bạn đẹp hơn thực tế rất nhiều. k6 với executor kiểu constant-arrival-rate xử lý vấn đề này tốt hơn kiểu chạy theo iteration thuần.

Nhóm 4 — Sai từ khâu vận hành và diễn giải

Load generator chính là bottleneck. Bạn chạy JMeter GUI trên laptop, mở 2.000 thread, rồi thấy latency tăng vọt và kết luận "server yếu". Thực ra chính cái laptop của bạn hết CPU/RAM, JMeter không kịp gửi request. Bạn đang đo giới hạn của máy tạo tải, không phải của server. Luôn giám sát tài nguyên của cả máy load generator.

Chạy một lần rồi kết luận. Một lần test bị ảnh hưởng bởi vô số nhiễu: garbage collection, cron job chạy ngầm, network glitch. Kết quả phải lặp lại được thì mới đáng tin. Chạy tối thiểu 3 lần, xem có ổn định không.

Tình huống thực tế

Ví dụ 1 — Sàn thương mại điện tử và cú sập ngày Sale 11.11

Một sàn TMĐT tầm trung ở Việt Nam (gọi là "ShopViet") chuẩn bị cho đợt Sale 11.11. Đội QA chạy load test suốt hai tuần trên môi trường staging, kết luận: "Hệ thống chịu được 15.000 concurrent user, average response time 180ms, sẵn sàng cho sự kiện."

Đến 0h ngày 11.11, chỉ 6.000 user thật đổ vào, hệ thống bắt đầu treo, và đến 0h12 thì trang thanh toán trả lỗi 503 hàng loạt. Doanh thu giờ vàng bốc hơi.

Diễn giải điều gì đã sai:

  • Staging của ShopViet dùng database có 200.000 sản phẩm, còn production có 4,2 triệu sản phẩm. Query tìm kiếm không được đo trên khối dữ liệu thật (sai Nhóm 1).
  • Script test chỉ mô phỏng luồng "xem sản phẩm → thêm giỏ hàng", bỏ qua bước thanh toán vì "payment gateway ở staging là mock" (sai Nhóm 2). Đúng bước thanh toán thật lại là nơi nghẽn — nó gọi sang cổng thanh toán bên thứ ba có giới hạn 500 giao dịch/giây.
  • Report chỉ báo average 180ms, không ai nhìn p99 (sai Nhóm 3). Khi phân tích lại log staging, p99 thực ra đã là 4,1 giây ở mức 6.000 user — dấu hiệu cảnh báo đã có sẵn nhưng bị con số average che mất.
Bài học: Ba lỗi từ ba nhóm khác nhau cộng dồn tạo ra một report tự tin nhưng hoàn toàn sai. Không phải công cụ tệ — cách dùng tệ.

Ví dụ 2 — Fintech và cái laptop 8GB RAM

Một startup fintech ở TP.HCM cần kiểm tra API xác thực OTP trước khi ra mắt. Bạn engineer trẻ chạy JMeter chế độ GUI ngay trên MacBook 8GB RAM, cấu hình Thread Group 3.000 thread. Kết quả: latency tăng dần từ 200ms lên 8 giây khi số thread tăng. Cậu ấy báo cáo: "API OTP không scale được, cần tối ưu backend gấp."

Team backend mất ba ngày soi code, thêm cache, tối ưu query — nhưng số liệu không cải thiện.

Diễn giải: Khi mình vào xem, mở Activity Monitor trên máy cậu ấy thì CPU đã 100%, JVM heap của JMeter liên tục full GC. Chính JMeter GUI trên laptop là bottleneck (sai Nhóm 4), không phải API. GUI mode của JMeter ngốn tài nguyên khủng khiếp vì phải render listener realtime. Chuyển sang chạy non-GUI mode trên một server AWS riêng, kết quả cho thấy API OTP thực ra chịu tốt 8.000 request/giây với p95 dưới 300ms.

Bài học: Trước khi kết luận "server yếu", hãy kiểm tra chính máy tạo tải trước. Và không bao giờ dùng JMeter GUI để chạy test tải thật — GUI chỉ để dựng và debug script.

Ví dụ 3 — Coordinated omission ở một công ty giao đồ ăn

Một nền tảng giao đồ ăn ở Đông Nam Á test API đặt món. Report ban đầu rất đẹp: p99 = 250ms ở 5.000 RPS. Nhưng user thật liên tục phàn nàn app "đứng hình" vào giờ cao điểm trưa.

Diễn giải: Đội của họ dùng script chạy theo kiểu iteration tuần tự — mỗi virtual user gửi request, chờ xong mới gửi tiếp. Khi database bị lock vào giờ cao điểm, một số request treo 3-4 giây, và trong lúc treo đó, các VU ngừng gửi request mới (coordinated omission — Nhóm 3). Công cụ đã tự động bỏ sót đúng những khoảnh khắc tệ nhất. Khi chuyển sang k6 với executor constant-arrival-rate (giữ đúng tốc độ 5.000 RPS bất kể server phản hồi nhanh hay chậm), p99 thật lộ ra là 3.900ms — khớp hoàn toàn với trải nghiệm user.

Bài học: Chọn đúng mô hình phát tải (arrival-rate thay vì iteration-based) là yếu tố sống còn để tránh coordinated omission. Report đẹp một cách bất thường luôn đáng nghi ngờ.

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

Đây là checklist mình dùng để "soi" một performance test trước khi tin vào kết quả của nó. Bạn có thể áp dụng cho chính mình hoặc khi review công việc của đồng nghiệp.

  • Kiểm tra môi trường (Environment parity). Hỏi: Database test có bao nhiêu dòng so với prod? Cấu hình phần cứng có tương đương không? Có load balancer, cache như prod không? Nếu khác quá nhiều, ghi rõ giới hạn này vào report — đừng giấu.
  • Kiểm tra kịch bản (Scenario realism). Script có mô phỏng full user journey không, hay chỉ một endpoint? Có think time hợp lý (3-10s) giữa các bước không? Có ramp-up từ từ không? Tỷ lệ giữa các loại request có khớp với dữ liệu analytics thật của production không?
  • Kiểm tra máy tạo tải (Load generator health). Trong lúc test, giám sát CPU/RAM/network của máy chạy JMeter/k6. Nếu nó vượt 70-80% tài nguyên, kết quả không đáng tin — cần phân tán tải qua nhiều máy (distributed testing).
  • Đọc kết quả đúng cách. Bỏ ngay thói quen nhìn average. Nhìn p95, p99. Kiểm tra error rate song song với throughput. Đối chiếu: throughput cao mà error rate cao thì throughput đó là "throughput lỗi".
  • Kiểm tra tính lặp lại. Chạy lại test ít nhất 3 lần vào các thời điểm khác nhau. Nếu kết quả dao động mạnh, tìm nguyên nhân nhiễu trước khi kết luận.
  • Viết finding kèm ngữ cảnh, không chỉ con số. Thay vì "hệ thống chịu 10.000 user", viết "ở 10.000 concurrent user với think time 5s trên môi trường giống prod, p99 = 850ms, error rate 0,3%; bottleneck bắt đầu ở connection pool database khi vượt 12.000 user".

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

  • Lỗi: Dùng dữ liệu test giống hệt nhau (không parameterize). Nếu 5.000 VU cùng đăng nhập một account, cùng query một keyword, thì cache sẽ "gánh" hết và bạn đo được tốc độ cache chứ không phải tốc độ hệ thống thật. Mẹo: Luôn dùng CSV Data Set (JMeter) hoặc SharedArray (k6) để mỗi user có dữ liệu riêng, đúng như đời thực.
  • Lỗi: Không "warm-up" hệ thống trước khi đo. Request đầu tiên luôn chậm hơn (JIT compile, kết nối pool chưa mở, cache lạnh). Mẹo: Bỏ qua giai đoạn warm-up đầu (30-60s) khi tính toán số liệu, hoặc thêm bước warm-up trước khi bắt đầu đo thật.
  • Lỗi: Chạy test tải trực tiếp trên production giờ cao điểm mà không cảnh báo ai. Bạn có thể tự tay gây ra sự cố thật. Mẹo: Nếu bắt buộc test trên prod, làm vào giờ thấp điểm, có kill switch, và thông báo toàn đội trước.
  • Lỗi: Bỏ qua giám sát phía server. Load test chỉ cho bạn góc nhìn từ ngoài (client-side). Nếu không xem CPU, memory, GC, DB connection ở phía server, bạn thấy "chậm" nhưng không biết vì sao. Mẹo: Luôn bật monitoring server-side song song với test.
  • Lỗi: Coi 1 lần fail là "hệ thống hỏng" hoặc 1 lần pass là "hệ thống ổn". Mẹo: Đặt ra ngưỡng SLA rõ ràng trước khi test (ví dụ p99 < 1s, error < 1%), rồi mới chạy — đừng để kết quả tự "định nghĩa" thành công.
  • Mẹo tổng quát: Mỗi khi một con số làm bạn quá vui hoặc quá hoảng, hãy tự hỏi "con số này có thể sai ở đâu?". Sự hoài nghi có kỷ luật là kỹ năng quan trọng nhất của nghề này.

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

  • Săn lỗi average. Lấy một report performance bất kỳ bạn từng thấy (hoặc tự tạo bằng k6/JMeter với một script đơn giản). Nếu report chỉ có average, hãy chạy lại và trích xuất p95, p99. So sánh khoảng cách giữa average và p99 — viết một đoạn nhận xét ngắn về việc average đã che giấu điều gì.
  • Tái hiện lỗi load generator. Chạy JMeter GUI với số thread rất lớn (ví dụ 5.000) trên máy cá nhân, mở song song một công cụ giám sát tài nguyên (Activity Monitor/Task Manager). Ghi lại thời điểm CPU máy bạn đạt 100%, đối chiếu với thời điểm latency đo được bắt đầu tăng bất thường. Rút ra kết luận về việc bạn đang đo cái gì.
  • Thiết kế lại một kịch bản sai thành đúng. Cho một script chỉ test riêng endpoint /login, hãy viết lại (bằng pseudo-code hoặc k6) thành một full user journey: login → xem dashboard → thực hiện một hành động → logout, có think time hợp lý giữa các bước. Giải thích vì sao phiên bản mới cho kết quả đáng tin hơn.
  • Viết một finding chuẩn. Chọn một kết quả test bất kỳ và viết lại phần kết luận theo công thức đầy đủ: [tải] + [điều kiện môi trường] + [think time] + [p95/p99] + [error rate] + [điểm nghẽn phát hiện]. So sánh với cách viết "hệ thống chịu được X user" để thấy sự khác biệt.

Tóm tắt

Performance testing thất bại hiếm khi vì thiếu công cụ — nó thất bại vì những anti-pattern có hệ thống khiến con số đo được không phản ánh thực tế. Bốn nhóm sai lầm bạn cần khắc cốt ghi tâm:

  • Môi trường sai: test trên dev/staging khác prod, dữ liệu không đủ quy mô.
  • Kịch bản sai: test một endpoint thay vì full journey, thiếu think time, thiếu ramp-up.
  • Đo lường sai: chỉ nhìn average thay vì p95/p99, bỏ qua error rate, dính coordinated omission.
  • Vận hành sai: load generator chính là bottleneck, chạy một lần rồi kết luận.
Kỹ năng quan trọng nhất mà bài này muốn trao cho bạn không phải là một kỹ thuật, mà là thái độ hoài nghi có kỷ luật: luôn hỏi "con số này có đáng tin không, nó có thể sai ở đâu?". Một report performance đẹp một cách bất thường thường là dấu hiệu của một anti-pattern đang ẩn nấp, chứ không phải của một hệ thống hoàn hảo. Người biết đặt câu hỏi đúng — chứ không phải người bấm nút Run nhiều nhất — mới là Performance Engineer mà mọi đội đều cần.

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