Phần 4 kết luận rằng đếm phải bằng chỉ số chứ không bằng log. Bài này đo xem chỉ số rẻ tới đâu, và chỗ nó đột nhiên không rẻ nữa.
Bốn kiểu
Đo bằng thư viện client chính thức của Prometheus cho Go, ba lần mỗi kiểu:
| Kiểu | ns mỗi lần ghi | Byte khi phơi ra | Gộp được giữa nhiều bản sao? |
|---|---|---|---|
Gauge.Set() |
1,5 / 1,5 / 1,5 | 31 | có |
Counter.Inc() |
2,0 / 2,0 / 1,9 | 42 | có |
Histogram.Observe() 11 bucket |
10,5 / 10,5 / 10,4 | 373 | có |
Histogram.Observe() 30 bucket |
13,0 / 13,0 / 13,0 | 884 | có |
Summary.Observe() 3 phân vị |
226,4 / 226,4 / 228,9 | 130 | không |
Counter và Gauge miễn phí
1,5 và 2,0 nano giây. Để so sánh, phần 47 của sê-ri trước đo một lời gọi hàm thường tốn 0,77 ns — nghĩa là tăng một bộ đếm tốn khoảng hai lời gọi hàm.
Nghĩa là không có lý do hiệu năng nào để không đặt bộ đếm. Nếu bạn đang phân vân "chỗ này có nên đếm không", câu trả lời là có; 2 ns không xuất hiện trong bất kỳ hồ sơ nào.
Đặt cạnh chi phí log từ hai phần trước:
| ns mỗi sự kiện | |
|---|---|
Counter.Inc() |
2,0 |
| Ghi một dòng log JSON (phần 2) | 548 |
| + thu thập nó (phần 5) | 1.730 |
| Tổng cho một dòng log | 2.278 |
Chỉ số rẻ hơn log 1.139 lần cho cùng một sự kiện.
Đó là lý do kỹ thuật đằng sau kết luận của phần 4: đếm bằng chỉ số vì nó rẻ tới mức không cần lấy mẫu — và vì không lấy mẫu nên con số của nó đúng.
Summary đắt gấp 22 lần Histogram
226,4 ns so với 10,5 ns.
Lý do: Histogram chỉ tăng vài bộ đếm — tìm bucket rồi cộng một. Summary duy trì một bộ ước lượng phân vị theo luồng với cửa sổ trượt, nên mỗi lần quan sát phải chèn vào cấu trúc dữ liệu và loại bỏ mẫu cũ.
Điều thú vị: Summary phơi ra nhỏ hơn — 130 byte so với 373 byte của Histogram, vì nó chỉ xuất ba con số phân vị thay vì mười một bucket.
Nhưng cột cuối cùng mới là lý do thật:
Phân vị của Summary không gộp được. Nếu bạn có 10 bản sao của dịch vụ, mỗi bản sao báo p99 riêng, thì không có phép toán nào cho ra p99 của toàn hệ thống. Trung bình của mười p99 không phải p99. Lớn nhất trong mười p99 cũng không phải p99.
Histogram gộp được, vì bucket chỉ là bộ đếm và cộng bộ đếm lại là hợp lệ. Bạn tính phân vị sau khi gộp, ở phía Prometheus.
Đó là lý do thực dụng để chọn Histogram gần như luôn luôn — không phải vì nó nhanh hơn 22 lần, mà vì con số nó cho là con số dùng được. Phần 13 sẽ đo cụ thể phân vị sai thành ra thế nào.
Cái đắt thật: số giá trị nhãn
Một HistogramVec với một nhãn, đổi số giá trị mà nhãn đó nhận:
| Số giá trị nhãn | Byte mỗi lần quét |
|---|---|
| 1 | 757 |
| 10 | 7.294 |
| 100 | 73.917 |
| 1.000 | 752.749 (735 KB) |
Khoảng 750 byte mỗi giá trị nhãn, tăng tuyến tính.
Với chu kỳ quét 15 giây, một chỉ số duy nhất có 1.000 giá trị nhãn sinh ra:
752.749 byte × 4 lần/phút × 60 × 24 = 4,3 GB mỗi ngày
Cho một chỉ số.
Nói lại lần thứ hai vì đây là điều quan trọng nhất của bài: chọn kiểu chỉ số gần như không ảnh hưởng gì; chọn nhãn thì ảnh hưởng tất cả. Khoảng cách giữa Counter và Histogram là 5 lần; khoảng cách giữa 1 và 1.000 giá trị nhãn là 1.000 lần.
Ba nhãn hay gây tai nạn nhất:
user_id,request_id,session_id— vô hạn giá trị. Không bao giờ đặt làm nhãn.pathchưa chuẩn hoá —/api/v1/don-hang/12345sinh một chuỗi thời gian cho mỗi đơn hàng. Phải rút về/api/v1/don-hang/:id.error_message— chuỗi tự do, thường chứa cả giá trị. Dùngerror_codethay thế.
Phần 11 sẽ đo cụ thể chuyện bùng nổ chiều này ở quy mô lớn hơn.
Chọn kiểu nào
| Bạn muốn biết | Kiểu | Vì sao |
|---|---|---|
| Có bao nhiêu lần | Counter |
Chỉ tăng, và rate() cho ra tốc độ |
| Hiện đang là bao nhiêu | Gauge |
Lên xuống được |
| Phân bố của một đại lượng | Histogram |
Gộp được, tính phân vị ở phía máy chủ |
| Phân vị chính xác của một bản sao | Summary |
Hiếm khi là thứ bạn cần |
Một nhầm lẫn hay gặp: dùng Gauge cho thứ đáng lẽ là Counter. Nếu ứng dụng khởi động lại, Counter được Prometheus nhận ra là đã reset và rate() xử lý đúng; Gauge thì không có khái niệm reset, và biểu đồ sẽ có một cú rơi giả.
Chỗ phép đo này không nói tới
Tôi đo thư viện Go. Client của Python, Java, Node đắt hơn đáng kể — nhưng theo cùng thứ tự tương đối, và ở mức vài chục tới vài trăm nano giây thì kết luận "chỉ số rẻ hơn log ba bậc độ lớn" vẫn giữ nguyên.
Tôi cũng không đo native histogram — kiểu histogram mới của Prometheus dùng bucket theo cấp số nhân và giảm mạnh kích thước phơi ra. Nó vẫn đang thử nghiệm ở thời điểm đo, và đáng một bài riêng khi ổn định.
Con số 750 byte mỗi giá trị nhãn phụ thuộc vào độ dài tên nhãn và tên chỉ số của tôi. Tên dài hơn thì tốn hơn — đó cũng là một lý do thực dụng để đặt tên chỉ số ngắn.
Thử ba mươi giây
Xem điểm quét của bạn to cỡ nào và chỉ số nào chiếm chỗ:
url=http://localhost:9090/metrics # doi thanh diem /metrics cua ban
echo "kich thuoc: $(curl -s $url | wc -c) byte, $(curl -s $url | grep -vc '^#') dong"
echo "--- 10 chi so nhieu chuoi thoi gian nhat ---"
curl -s $url | grep -v '^#' | sed 's/{.*//' | sort | uniq -c | sort -rn | head -10
echo "--- nhan nao co nhieu gia tri nhat ---"
curl -s $url | grep -oE '[a-z_]+="[^"]*"' | cut -d= -f1 | sort | uniq -c | sort -rn | head -5
Dòng đầu nhân với số lần quét mỗi ngày cho ra lượng dữ liệu bạn đang sinh. Nếu một chỉ số duy nhất chiếm hàng nghìn dòng ở lệnh thứ hai, hãy xem lại nhãn của nó — đó gần như luôn là một trường lẽ ra phải nằm trong log, không nằm trong chỉ số.
Phần sau: Prometheus và mô hình kéo — đo chi phí của một lần thu thập.