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 chỉ số và chi phí của chúng

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
Counter.Inc() 2,0 / 2,0 / 1,9 42
Histogram.Observe() 11 bucket 10,5 / 10,5 / 10,4 373
Histogram.Observe() 30 bucket 13,0 / 13,0 / 13,0 884
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 CounterHistogram 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.
  • path chưa chuẩn hoá/api/v1/don-hang/12345 sinh 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ùng error_code thay 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.