Product Management
Đăng nhập
ESC

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

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

Project 4: Flash Sale Spike Test

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

Đúng 0 giờ ngày 12/12, hàng trăm nghìn người Việt cùng bấm nút "Săn sale" trong một khoảnh khắc. Traffic không tăng dần đều — nó dựng đứng như một bức tường. Từ vài trăm request mỗi giây, hệ thống bị đẩy lên hàng chục nghìn request chỉ trong 30 giây. Đây chính là spike — cú tăng vọt đột ngột — và nó là kịch bản đáng sợ nhất, cũng là đắt giá nhất, đối với bất kỳ nền tảng thương mại điện tử nào.

Bạn đã đi qua các loại test (Bài 5), đã học đọc metrics (Bài 6), đã viết được kịch bản user journey (Bài 28), đã làm quen với spike testing lý thuyết (Bài 37) và đã hoàn thành Project 1 (Load Test), Project 2 (Stress Test), Project 3 (Endurance). Bây giờ là lúc gom tất cả lại vào một project khó nhất: mô phỏng một trận flash sale 12.12 thật sự và trả lời câu hỏi sinh tử — hệ thống có sống sót không, và nếu chết thì chết ở đâu, chết như thế nào?

Đây không phải bài lý thuyết. Đây là một dự án end-to-end: bạn sẽ thiết kế profile tải hình cái búa (hammer profile), viết script k6 mô phỏng cơn spike, thiết lập SLA gates, chạy, và viết một bản báo cáo mà một CTO có thể mang ra họp trước Black Friday. Một kỹ sư hiệu năng giỏi được trả lương cao chính vì họ có thể nói với ban lãnh đạo: "Với cấu hình hiện tại, hệ thống chịu được cú spike gấp 20 lần trong 2 phút, nhưng sẽ sập ở mốc 25 lần vì connection pool database cạn kiệt." Bài này dạy bạn cách đi đến câu nói đó.

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

Spike test khác gì các test khác

Điểm mấu chốt của spike test là tốc độ thay đổi tải (rate of change), không phải mức tải đỉnh. Stress test (Project 2) tăng tải từ từ để tìm điểm gãy. Spike test giữ tải thấp một lúc rồi tăng vọt gần như tức thời, giữ đỉnh vài phút, rồi rút xuống cũng đột ngột. Mục tiêu là kiểm tra ba thứ:

  • Khả năng hấp thụ (absorption): hệ thống có nuốt được cú sốc mà không sập không?
  • Khả năng phục hồi (recovery): sau khi spike qua đi, hệ thống có trở lại bình thường không, hay treo luôn?
  • Hành vi auto-scaling: nếu bạn dùng cloud, autoscaler có kịp phản ứng không? Thường thì không — và đó là phát hiện quan trọng nhất.

Hammer profile — hình dạng tải của flash sale

Một profile flash sale điển hình gồm bốn pha:

  • Baseline (nền): 500 RPS trong 3-5 phút, mô phỏng lưu lượng bình thường trước giờ G.
  • Spike (bùng nổ): tăng lên đỉnh (ví dụ 10.000 RPS) trong 20-30 giây. Đây là "cái búa gõ xuống".
  • Sustained peak (giữ đỉnh): giữ 10.000 RPS trong 2-3 phút — quãng thời gian người dùng thực sự tranh mua.
  • Recovery (rút lui): giảm nhanh về baseline, quan sát xem hệ thống có "hồi máu" không.
Con số 500 RPS baseline và đỉnh gấp 15-20 lần là mô hình sát thực tế với các sàn cỡ trung ở Việt Nam. Với sàn lớn như Shopee dịp 12.12, tỉ lệ đỉnh/nền có thể lên tới 30-50 lần.

Vì sao autoscaling không cứu được bạn

Nhiều đội tin rằng "cloud sẽ tự scale". Sự thật phũ phàng: một autoscaler dựa trên metric CPU thường mất 2-5 phút để phát hiện tải cao, khởi tạo instance mới, chờ health check, rồi mới nhận traffic. Nhưng cơn spike flash sale chỉ kéo dài 2-3 phút. Đến lúc instance mới sẵn sàng thì trận sale đã kết thúc và khách đã bỏ đi. Đây là lý do spike test luôn phải chạy trên đúng cấu hình sẽ có mặt tại thời điểm spike, và tại sao các sàn lớn dùng pre-warming (làm nóng trước) — scale sẵn hạ tầng trước giờ G thay vì trông chờ autoscaler.

Đo cái gì mới đúng

Trong spike test, các số trung bình (average) trở nên vô nghĩa vì chúng che giấu thảm họa. Bạn phải nhìn:

  • Error rate theo giây (không phải tổng): thời điểm nào bắt đầu lỗi?
  • p95/p99 latency theo thời gian: đuôi phân phối phình ra khi nào?
  • Số connection bị từ chối (connection refused, 503, 429).
  • Thời gian phục hồi: kể từ khi spike kết thúc, mất bao lâu để error rate về 0.

Tình huống thực tế

Tình huống 1 — Sàn thời trang "Cocoon Market" và cú sập inventory service

Cocoon Market (giả định), một sàn thời trang nội địa, chạy chương trình flash sale áo khoác giá sốc 99.000đ lúc 20h00. Đội QA của họ trước đó chỉ chạy load test tăng dần và thấy hệ thống chịu được 8.000 RPS thoải mái, nên tự tin.

Ngày sale thật, đúng 20h00 traffic vọt lên 7.000 RPS trong 15 giây. Website vẫn hiển thị, nhưng nút "Mua ngay" trả về lỗi 500 cho 40% người dùng. Phân tích sau đó cho thấy: mỗi lần user bấm mua, hệ thống gọi inventory-service để kiểm tra và trừ tồn kho. Service này có connection pool tới database chỉ 50 connection. Load test tăng dần đã không bao giờ ép 7.000 request cùng lúc tranh giành 50 connection đó — traffic dàn trải nên pool đủ dùng. Nhưng spike thật khiến 7.000 request ập tới đồng thời, pool cạn trong 2 giây, request xếp hàng, timeout, rồi lỗi 500 dây chuyền.

Bài học: load test tăng dần và spike test đo hai thứ khác nhau. Điểm gãy của Cocoon không phải throughput, mà là contention (tranh chấp tài nguyên) tại thời điểm đồng thời cực đại. Chỉ có spike test — dựng đứng tải — mới phơi bày được nó. Sau sự cố, họ tăng pool lên 200, thêm cache đọc tồn kho, và chạy lại spike test để xác nhận.

Tình huống 2 — Ví điện tử "PayViet" và cơn spike đến từ thông báo push

PayViet (giả định) tổ chức "Giờ vàng hoàn tiền" lúc 12h00 trưa. Điểm khác biệt: họ gửi push notification cho 2 triệu user cùng lúc lúc 11h59. Kết quả là spike còn dữ dội hơn cả sàn TMĐT — vì tất cả nhận thông báo trong cùng một giây và cùng mở app.

Đội performance của PayViet đã khôn ngoan: họ mô phỏng đúng kịch bản này bằng k6 với executor ramping-arrival-rate, nhồi từ 800 RPS lên 20.000 RPS trong 10 giây. Test phơi bày rằng API gateway của họ có rate limit toàn cục là 15.000 RPS, và khi vượt ngưỡng, gateway trả 429 hàng loạt — kể cả cho các request thanh toán hợp lệ đang dở dang. Tệ hơn, retry logic phía client tự động thử lại, tạo thành retry storm (bão thử lại) khiến tải thực tế phình lên gấp đôi.

Bài học: nguồn gốc spike đôi khi nằm ở chính bạn (push notification). Và spike test phát hiện ra hai anti-pattern nguy hiểm — rate limit đặt sai chỗ và retry không có backoff. PayViet sau đó chuyển sang jittered exponential backoff (thử lại với độ trễ ngẫu nhiên tăng dần) ở client và giãn push notification thành các đợt cách nhau 5 giây (staggered rollout). Cú spike sau đó phẳng hơn hẳn.

Tình huống 3 — "GrabFood-style" combo trưa và bài học về queue

Một nền tảng giao đồ ăn (bối cảnh Đông Nam Á) chạy combo trưa 11h30-12h00 mỗi ngày. Traffic dạng "double hump" (hai bướu) — spike lúc 11h30 rồi lại spike lúc điểm cuối 11h55 khi khách sợ hết giờ. Đội QA thiết kế spike test có hai đỉnh liên tiếp để xem hệ thống phục hồi giữa hai đỉnh thế nào.

Phát hiện thú vị: giữa hai đỉnh, message queue xử lý đơn hàng (order queue) vẫn còn tồn đọng (backlog) từ đỉnh thứ nhất chưa tiêu hóa xong thì đỉnh thứ hai đã ập tới, khiến queue phình vô hạn và độ trễ xử lý đơn tăng từ 2 giây lên 45 giây. Người dùng thấy "đặt hàng thành công" nhưng nhà hàng nhận đơn trễ 45 giây — trải nghiệm tệ.

Bài học: spike test không chỉ đo tầng web đồng bộ (synchronous), mà phải theo dõi cả các thành phần bất đồng bộ (asynchronous) phía sau như queue. Recovery không có nghĩa là "web trả 200 nhanh trở lại" mà là "toàn bộ pipeline đã tiêu hóa hết backlog". Họ bổ sung consumer tự động scale theo độ dài queue và test lại với double-hump profile.

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

Ta sẽ xây dựng Project 4 bằng k6 (bạn hoàn toàn có thể làm tương tự với JMeter, dùng Ultimate Thread Group để tạo hình spike). Kịch bản: một luồng mua hàng flash sale gồm xem sản phẩm → thêm giỏ → checkout.

Bước 1 — Xác định mục tiêu và SLA rõ ràng

Trước khi viết một dòng code, chốt SLA. Ví dụ cho Cocoon Market:

  • Error rate < 1% xuyên suốt spike.
  • p95 của checkout < 1500ms.
  • Hệ thống phục hồi (error rate về dưới 0.5%) trong vòng 60 giây sau khi spike kết thúc.
Không có SLA thì test chỉ ra được đồ thị đẹp mà không kết luận được "pass hay fail".

Bước 2 — Dựng hammer profile bằng ramping-arrival-rate

Dùng executor arrival-rate (Bài 15) vì flash sale là mô hình tải mở (open model) — người dùng đến theo tốc độ nhất định bất kể server có chậm hay không, giống thực tế.

import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend } from 'k6/metrics';

const checkoutErrors = new Rate('checkout_errors'); const checkoutLatency = new Trend('checkout_latency', true);

export const options = { scenarios: { flash_sale_1212: { executor: 'ramping-arrival-rate', startRate: 500, // baseline 500 RPS timeUnit: '1s', preAllocatedVUs: 2000, // cấp phát sẵn VU đủ lớn maxVUs: 8000, stages: [ { target: 500, duration: '3m' }, // baseline { target: 10000, duration: '30s' }, // cú búa: vọt lên 10k RPS { target: 10000, duration: '2m' }, // giữ đỉnh { target: 500, duration: '30s' }, // rút nhanh { target: 500, duration: '2m' }, // quan sát phục hồi ], }, }, thresholds: { checkout_errors: ['rate<0.01'], // SLA: lỗi < 1% 'checkout_latency': ['p(95)<1500'], // SLA: p95 < 1.5s http_req_failed: ['rate<0.01'], }, };

Lưu ý sống còn: preAllocatedVUsmaxVUs phải đủ lớn. Nếu server chậm lại, mỗi request giữ VU lâu hơn, và k6 cần nhiều VU để duy trì arrival rate. Thiếu VU thì chính k6 trở thành nút thắt và số liệu vô nghĩa.

Bước 3 — Viết luồng flash sale thực tế

const BASE = 'https://staging.cocoon.example';

export default function () { group('flash_sale_purchase', () => { // Xem sản phẩm sale const product = http.get(${BASE}/api/products/flash-99k); check(product, { 'product ok': (r) => r.status === 200 });

// Thêm vào giỏ const cart = http.post(${BASE}/api/cart, JSON.stringify({ productId: 'flash-99k', qty: 1, }), { headers: { 'Content-Type': 'application/json' } }); check(cart, { 'cart ok': (r) => r.status === 200 });

// Checkout — điểm nóng nhất const t0 = Date.now(); const co = http.post(${BASE}/api/checkout, JSON.stringify({ cartId: cart.json('cartId'), payment: 'cod', }), { headers: { 'Content-Type': 'application/json' } }); checkoutLatency.add(Date.now() - t0); const ok = check(co, { 'checkout ok': (r) => r.status === 200 }); checkoutErrors.add(!ok); }); }

Bước 4 — Chạy trên môi trường giống production

Chạy k6 run flash-sale.js. Quan trọng: chạy trên staging có cấu hình giống hệt production (cùng số instance, cùng size database, cùng cache). Spike test trên môi trường yếu hơn cho kết quả sai lệch hoàn toàn. Đồng thời phải mở dashboard giám sát hệ thống (Grafana + APM, xem Bài 22 và 45) để nhìn CPU, memory, connection pool, độ dài queue song song với đồ thị k6.

Bước 5 — Đọc kết quả và định vị điểm nghẽn

Nhìn vào ba mốc thời gian:

  • Lúc búa gõ (giây 180-210): error bắt đầu nhảy ở đâu? Latency đuôi phình lúc nào?
  • Lúc giữ đỉnh: error rate ổn định hay leo dốc? Leo dốc nghĩa là tài nguyên đang cạn dần (memory, connection).
  • Lúc phục hồi: sau giây 300, error về 0 mất bao lâu? Nếu treo lâu → có backlog queue hoặc leak.
Đối chiếu với APM để xác định thủ phạm: database CPU 100%? Connection pool cạn? GC pause? Đây chính là kỹ năng bottleneck analysis (Bài 25) áp dụng vào bối cảnh spike.

Bước 6 — Viết findings và đề xuất

Báo cáo phải nêu: điểm gãy (bao nhiêu RPS/tỉ lệ spike), nguyên nhân gốc, tác động kinh doanh (bao nhiêu % đơn thất bại = bao nhiêu doanh thu mất), và đề xuất cụ thể (tăng pool, thêm cache, pre-warm, queue-based load leveling).

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

Lỗi 1 — Dùng closed model (VU cố định) cho spike test. Nếu bạn dùng executor ramping-vus, khi server chậm, mỗi VU chờ lâu hơn nên tải thực tế giảm — che giấu vấn đề. Flash sale là open model: khách cứ đến bất kể server có kịp không. Luôn dùng arrival-rate executor.

Lỗi 2 — Máy chạy load test quá yếu. Sinh 10.000 RPS ngốn CPU/network rất lớn. Nếu một máy k6 không kham nổi, bạn sẽ đo giới hạn của chính máy test chứ không phải của server. Giải pháp: chạy phân tán nhiều máy (JMeter master/slave — Bài 23, hoặc k6 Cloud — Bài 29) và luôn kiểm tra CPU của máy generate tải phải dưới 80%.

Lỗi 3 — Không có baseline trước spike. Nếu nhồi thẳng 10k RPS ngay từ giây 0, bạn không phân biệt được lỗi do "hệ thống chưa sẵn sàng" hay do "cú spike". Luôn có 3-5 phút baseline để hệ thống ổn định (JIT warm-up, cache ấm, connection pool đầy).

Lỗi 4 — Bỏ qua giai đoạn recovery. Nhiều người dừng test ngay khi spike đạt đỉnh. Nhưng câu hỏi "hệ thống có tự hồi phục không" quan trọng ngang việc "có sập không". Luôn để test chạy tiếp 2 phút sau khi rút tải.

Lỗi 5 — Quên retry storm. Nếu client thật có retry, hãy mô phỏng cả retry trong script, nếu không bạn đánh giá thấp tải thực tế. Đây là bài học từ PayViet.

Mẹo — Test cả cache miss. Flash sale thật thường có cache miss ban đầu (sản phẩm mới, cache lạnh). Hãy chạy một biến thể spike với cache trống để xem trường hợp xấu nhất, đừng chỉ test khi cache đã ấm.

Mẹo — Ghi lại thời điểm chính xác. Tag các pha trong k6 (Bài 20) để khi đọc Grafana bạn biết chính xác giây nào là "búa gõ", giây nào là "rút lui", dễ đối chiếu với metric hệ thống.

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

  • Dựng profile cơ bản: Viết script k6 với hammer profile cho một sàn giả định: baseline 300 RPS trong 2 phút, spike lên 6.000 RPS trong 20 giây, giữ 90 giây, rồi rút. Chạy trên một API test (có thể dùng https://test.k6.io hoặc mock server của bạn) và vẽ đồ thị RPS thực tế đạt được so với mục tiêu.
  • Tìm điểm gãy: Chạy lại nhiều lần, mỗi lần tăng đỉnh spike thêm 2.000 RPS (6k → 8k → 10k...). Ghi lại đỉnh mà error rate lần đầu vượt 1%. Đây là "spike ceiling" của hệ thống.
  • Đo recovery: Thêm một pha giữ baseline 3 phút sau spike. Đo chính xác số giây từ khi rút tải đến khi error rate về dưới 0.5%. So sánh recovery time giữa hai lần chạy: một lần với đỉnh vừa phải, một lần với đỉnh vượt spike ceiling.
  • Mô phỏng retry storm: Thêm logic vào script — nếu checkout trả lỗi, thử lại tối đa 2 lần. Chạy lại và so sánh tải thực tế lên server có/không có retry. Rút ra bài học về backoff.
  • Viết báo cáo 1 trang: Với kết quả từ bài 2 và 3, viết một trang findings gửi "CTO": spike ceiling là bao nhiêu, recovery time bao lâu, nguyên nhân gốc (nếu định vị được qua monitoring), và 2 đề xuất cải thiện cụ thể.

Tóm tắt

Spike test là bài kiểm tra khắc nghiệt nhất mô phỏng đúng bản chất của flash sale 12.12: tải không tăng dần mà dựng đứng như bức tường. Điều khiến nó khác biệt là tốc độ thay đổi tải, phơi bày những vấn đề mà load test tăng dần không bao giờ thấy — contention connection pool (Cocoon Market), rate limit và retry storm (PayViet), backlog queue bất đồng bộ (nền tảng giao đồ ăn).

Công thức của Project 4: chốt SLA rõ ràng → dựng hammer profile bốn pha (baseline, spike, giữ đỉnh, recovery) bằng ramping-arrival-rate open model → chạy trên môi trường giống production với monitoring song song → đọc kết quả ở ba mốc (búa gõ, giữ đỉnh, phục hồi) → định vị điểm nghẽn qua APM → viết findings có tác động kinh doanh. Nhớ rằng autoscaling thường quá chậm để cứu một cú spike 2 phút; câu trả lời thực sự nằm ở pre-warming, tối ưu tài nguyên và queue-based load leveling. Khi bạn có thể nói chính xác "spike ceiling của chúng ta là X và ta phục hồi trong Y giây", bạn đã tư duy như một Performance Engineer thực thụ.

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