Phần 3 đo chi phí lưu và truy vấn log. Khi khối lượng vượt ngân sách, câu trả lời quen thuộc là lấy mẫu. Bài này đo xem lấy mẫu lấy đi cái gì.
Bố trí
200.000 yêu cầu mô phỏng: 99% bình thường (1–120 ms), 1% chậm (400–1.200 ms), 0,05% lỗi (800–3.000 ms). Ba dòng log cho mỗi trace_id.
Sự thật:
p50 61,1 ms | p95 115,1 ms | p99 414,9 ms | 100 lỗi
Sáu cách lấy mẫu
| Cách | Giữ lại | p50 | p95 | p99 | Lỗi giữ |
|---|---|---|---|---|---|
| Giữ hết | 200.000 | 61,1 | 115,1 | 414,9 | 100 |
| Ngẫu nhiên 1/10 | 19.969 | 60,8 | 115,1 | 119,9 | 9 |
| Ngẫu nhiên 1/100 | 1.978 | 62,1 | 116,3 | 474,4 | 0 |
Chỉ giữ warn và error |
2.030 | 820,5 | 1.186,9 | 2.509,0 | 100 |
Theo trace_id 1/100 |
2.082 | 59,7 | 114,0 | 450,6 | 1 |
| Theo trace 1/100 + giữ mọi trace có lỗi | 2.181 | 62,7 | 803,7 | 2.486,8 | 100 |
Kết quả 1: lấy mẫu 1/10 làm sập p99
414,9 xuống 119,9 — sai 71%.
Chạy lại với ba hạt giống khác nhau:
| Sự thật | Sau khi lấy mẫu 1/10 | |
|---|---|---|
| Lần 1 | 414,9 | 119,9 |
| Lần 2 | 416,9 | 120,0 |
| Lần 3 | 402,1 | 120,0 |
Sai đều, và sai về đúng một giá trị. Đó không phải nhiễu, đó là sai hệ thống.
Lý do: p99 của 200.000 dòng là dòng chậm thứ 2.000 — nằm sâu trong nhóm warn. Sau khi lấy mẫu 1/10, tập còn 20.000 dòng và p99 là dòng chậm thứ 200 — vừa đúng ranh giới giữa nhóm bình thường (trần 120 ms) và nhóm chậm. Con số 120 không phải trùng hợp; nó là trần của nhóm phổ biến nhất.
Điều đáng chú ý: p50 và p95 sống sót hoàn hảo (60,8 và 115,1). Lấy mẫu ngẫu nhiên giữ đúng hình dạng của phần giữa phân bố và phá huỷ phần đuôi — đúng cái phần mà người ta lập biểu đồ p99 để nhìn.
Kết quả 2: lọc theo mức làm sai p50 mười ba lần
Cách "tiết kiệm" phổ biến nhất là bỏ hết INFO, chỉ giữ WARN trở lên. Nó giữ đủ 100/100 lỗi, nghe rất hợp lý.
| Sự thật | Chỉ warn+error |
|
|---|---|---|
| Lần 1 | 61,1 | 820,5 |
| Lần 2 | 61,4 | 822,2 |
| Lần 3 | 61,2 | 814,6 |
p50 sai 13,4 lần, và sai đều ở cả ba lần.
Lý do hiển nhiên khi nói ra: bạn vứt hết yêu cầu bình thường đi, nên phân bố còn lại chỉ gồm các yêu cầu chậm. Tính phân vị trên đó là tính trên một phân bố hoàn toàn khác.
Nói lại lần thứ hai vì đây là sai lầm hay gặp nhất trong thực tế: mọi con số phân vị tính từ log đã lọc theo mức đều là số bịa. Không phải hơi lệch — sai một bậc độ lớn.
Kết quả 3: phép đếm cũng không sống sót
Số lỗi thật: 100
Lấy mẫu 1/10 được 7 lỗi, nhân 10 lại = 70
Lệch −30%
Và với 1/100, ba lần đo cho 0, 3, 1 lỗi giữ được trên tổng 100, 116, 100.
Sự kiện hiếm không chịu được lấy mẫu. Với 100 lỗi trên 200.000 dòng, lấy mẫu 1/100 giữ được trung bình một lỗi — và một lần chạy giữ được không lỗi nào.
Kết quả 4: lấy mẫu theo trace giữ phân vị tốt nhất
Thay vì bốc từng dòng, băm trace_id và giữ hoặc bỏ cả cụm:
h = int(hashlib.md5(trace_id.encode()).hexdigest()[:8], 16)
giu = (h % 1000) < rate * 1000
| p50 | p95 | p99 | |
|---|---|---|---|
| Sự thật | 61,1 | 115,1 | 414,9 |
| Ngẫu nhiên 1/100 | 62,1 | 116,3 | 474,4 |
| Theo trace 1/100 | 59,7 | 114,0 | 450,6 |
Gần sự thật nhất trong mọi cách lấy mẫu. Và quan trọng hơn: trace giữ lại thì giữ đủ cả ba dòng, nên bạn đọc được toàn bộ hành trình của một yêu cầu thay vì một mẩu rời rạc.
Đây là cách mọi hệ thống tracing hiện đại làm, và lý do là như vậy: một trace nửa vời không dùng được vào việc gì.
Cấu hình nên dùng
Dòng cuối bảng: theo trace 1/100, cộng giữ mọi trace có lỗi.
- Giữ 2.181 dòng trên 200.000 — 1,1% khối lượng.
- Giữ đủ 100/100 lỗi.
- p50 đúng (62,7 so với 61,1).
Đổi lại: p95 và p99 của nó là con số bịa (803,7 và 2.486,8), vì việc giữ mọi trace lỗi làm nhóm chậm bị đại diện quá mức.
Đó là đánh đổi chấp nhận được, với điều kiện bạn không tính phân vị từ log.
Quy tắc rút ra
| Câu hỏi | Trả lời bằng | Vì sao |
|---|---|---|
| p50 / p95 / p99 là bao nhiêu | Metric | Đếm hết, không lấy mẫu |
| Có bao nhiêu lỗi | Metric | Sự kiện hiếm không chịu được lấy mẫu |
| Tỷ lệ lỗi theo endpoint | Metric | Như trên |
| Yêu cầu này hỏng ở đâu | Log / trace | Cần chi tiết, không cần đầy đủ |
| Vì sao yêu cầu chậm | Trace | Cần thấy từng chặng |
Đếm và tính phân vị bằng metric; lấy mẫu chỉ áp cho log. Metric rẻ tới mức không cần lấy mẫu — bài sau sẽ đo chính xác bao nhiêu.
Và nếu buộc phải lấy mẫu log, hãy lấy mẫu theo trace, không theo dòng, và luôn giữ trọn những trace có lỗi.
Chỗ phép đo này không nói tới
Đây là mô phỏng, không phải hệ thống thật. Phân bố tôi dựng — 99/1/0,05 — chọn cho giống một dịch vụ web thông thường, nhưng hình dạng đuôi thật thường nặng hơn.
Điều đó làm kết quả tệ hơn, không tốt hơn: đuôi càng nặng thì lấy mẫu càng phá nhiều.
Tôi cũng không đo lấy mẫu thích ứng — giữ tỷ lệ cao khi lưu lượng thấp và giảm dần khi tăng — thứ mà OpenTelemetry và các hệ thống thương mại đều có. Nó giải quyết được vấn đề khối lượng, không giải quyết được vấn đề phân vị.
Thử ba mươi giây
Kiểm tra xem biểu đồ phân vị của bạn có tính từ log đã lấy mẫu không:
# 1. Bang dieu khien lay p99 tu dau?
# Neu tu log (Loki, Elasticsearch) va co lay mau -> con so do sai.
# Neu tu metric (Prometheus histogram) -> dung.
# 2. Kiem tra ty le lay mau dang dat
grep -riE 'sampl|sample_rate|SAMPLING' /etc/*/ 2>/dev/null | head -5
# 3. Doi chieu: dem log so voi dem metric trong cung mot khoang
# Hai con so nay PHAI bang nhau neu khong lay mau.
grep -c 'ERROR' /var/log/dich-vu.log
curl -s localhost:9090/api/v1/query --data-urlencode \
'query=sum(increase(http_requests_total{status=~"5.."}[1h]))' | head -c 200
Nếu hai con số ở bước 3 lệch nhau đúng bằng tỷ lệ lấy mẫu, bạn vừa xác nhận rằng mọi thứ đếm từ log đang thiếu — và bảng điều khiển đang báo tình hình tốt hơn thực tế.
Phần sau: thu thập log — agent và sidecar, đo tài nguyên chiếm.