Sê-ri đã đo giá của việc sinh chỉ số và giá của việc tính phân vị. Còn một khoản chưa ai tính: giá của việc nhìn. Mỗi bảng điều khiển đang mở là một chuỗi truy vấn lặp lại đều đặn, và không con số nào của khoản đó hiện lên màn hình người đang nhìn. Bài này dựng một Prometheus với hơn hai mươi nghìn chuỗi và đo chính xác chuyện đó.

Một bảng điều khiển nặng tính tiền ở đâu

Bảng số liệu

Prometheus 2.54.1, một exporter sinh 21 563 chuỗi, thu thập mỗi giây trong 150 giây — tổng 3,23 triệu mẫu. Mỗi lần thu thập tải về 1,85 MB và tốn 44 ms. Khoảng xem của bảng điều khiển là 140 giây.

Bốn kiểu panel thường gặp, ở bước 1 giây:

Panel Số đường vẽ Số điểm Thời gian
sum(rate(...)) 1 141 260 ms
sum by (service) (rate(...)) 5 705 259 ms
topk(10, rate(...)) 381 1 410 264 ms
rate(...) không gộp 21 000 2 961 000 488 ms

Cùng panel không gộp, đổi bước:

Bước Số điểm JSON Thời gian
1s 2 961 000 97,54 MB 461 ms
5s 609 000 22,30 MB 201 ms
15s 210 000 9,54 MB 147 ms
60s 63 000 4,83 MB 95 ms

Cả bảng điều khiển, bước 15 giây, làm tươi mỗi 10 giây:

Số panel Thời gian Một người xem Mười người xem
5 242 ms 2% một nhân 24%
10 367 ms 4% 37%
20 726 ms 7% 73%
40 1 226 ms 12% 123%

Điều đáng nhớ

Nói lại theo cách khác, vì hai kết quả đầu đều ngược với trực giác thông thường:

Gộp bằng sum by không làm Prometheus nhẹ đi. Panel trả về một đường mất 260 ms; panel trả về 21 000 đường mất 488 ms — chỉ gấp 1,88 lần, dù số điểm trả về gấp hai mươi nghìn lần. Lý do đơn giản: cả bốn panel đều phải đọc đủ 21 000 chuỗi để tính rate. Gộp chỉ cắt bớt phần trả về, không cắt phần đọc.

Cái sum by cứu là trình duyệt, không phải máy chủ. 97,54 MB JSON cho một panel duy nhất ở bước 1 giây — Grafana phải tải, phân tích và vẽ 2,96 triệu điểm lên một khung hình rộng vài trăm pixel. Đó mới là chỗ bảng điều khiển đứng hình.

Bước là đòn bẩy lớn nhất, nhưng lớn không đều. Từ 1 giây lên 60 giây, JSON giảm 20,2 lần trong khi thời gian truy vấn chỉ giảm 4,85 lần. Phần đọc 3,23 triệu mẫu không đổi; chỉ phần trả về đổi.

Và chi phí nhân với số người đang mở. Bốn mươi panel làm tươi mỗi 10 giây tốn 12% một nhân — chấp nhận được cho một người. Mười người cùng mở là 123%, tức hơn một nhân trọn vẹn, dành riêng cho việc vẽ lại những biểu đồ mà phần lớn thời gian không ai nhìn.

Vì sao

Một truy vấn rate(http_requests_total[30s]) trên khoảng 140 giây bắt Prometheus làm ba việc: tìm các chuỗi khớp bộ chọn, đọc mẫu của chúng trong khoảng thời gian, rồi tính rate tại mỗi mốc bước. Việc thứ hai chiếm phần lớn thời gian và không phụ thuộc vào bước: dù bạn xin một điểm hay ba nghìn điểm, số mẫu phải đọc từ ổ đĩa là như nhau.

Đó là lý do đường cong thời gian thoải hơn đường cong dung lượng. Bước điều khiển số lần tính, còn bộ chọn và khoảng xem điều khiển số mẫu phải đọc. Muốn giảm phần đọc thì phải thu hẹp bộ chọn — thêm nhãn vào truy vấn — hoặc rút ngắn khoảng xem.

sum by (service) không thu hẹp bộ chọn. Nó vẫn khớp cả 21 000 chuỗi rồi cộng chúng lại ở cuối. Ngược lại, rate(http_requests_total{service="api"}[30s]) chỉ chạm một phần năm số chuỗi, và đó mới là cách làm truy vấn rẻ đi thật.

Còn 44 ms cho mỗi lần thu thập là khoản phí nền: 4,4% một nhân chỉ để nạp dữ liệu, trước khi bất kỳ ai mở bất kỳ bảng điều khiển nào.

Nghĩa là gì trong thực tế

  • Đặt bước theo độ rộng biểu đồ, đừng đặt cứng. Grafana có biến $__interval tính bước theo số pixel; dùng nó thay vì gõ 15s hay tệ hơn là 1s. Một biểu đồ rộng 800 pixel không thể hiển thị nhiều hơn 800 điểm, nên mọi điểm sau đó là băng thông vứt đi.
  • Đừng vẽ panel không gộp trên chỉ số có nhiều chuỗi. Nếu bạn thật sự cần thấy từng chuỗi, hãy dùng topk và giới hạn khoảng xem. Một panel 21 000 đường không đọc được bằng mắt, nó chỉ làm trình duyệt chậm.
  • Lọc bằng nhãn trong chính truy vấn, không lọc bằng cách gộp. {service="api"} rẻ hơn sum by (service) rất nhiều, dù cả hai đều cho ra một đường.
  • Xem lại chu kỳ làm tươi trước khi xem lại truy vấn. Đổi từ 10 giây sang 60 giây cắt ngay sáu lần chi phí mà không đổi một ký tự nào trong truy vấn — và với dữ liệu thu thập mỗi 15 giây, làm tươi mỗi 10 giây vốn đã là vẽ lại cùng một thứ.
  • Nhớ nhân với số người xem. Một bảng điều khiển treo trên màn hình lớn trong phòng làm việc là một người xem chạy 24 giờ mỗi ngày.

Chỗ tôi không kết luận được

Tôi không chạy Grafana. Tôi mô phỏng các truy vấn mà một bảng điều khiển sinh ra và đo chi phí ở phía Prometheus. Phần Grafana tự tiêu tốn — phân tích JSON, dựng biểu đồ, giữ trạng thái phiên — không nằm trong bảng nào ở trên, và với con số 97,54 MB thì phần đó nhiều khả năng lớn hơn phần tôi đo được.

Khoảng xem của tôi chỉ 140 giây. Bảng điều khiển thật thường xem 6 hoặc 24 giờ. Số mẫu phải đọc tăng tuyến tính theo khoảng xem, nên thời gian truy vấn sẽ lớn hơn nhiều — nhưng tỷ lệ giữa các dòng trong bảng thì tôi tin là giữ nguyên, vì cơ chế không đổi.

Dữ liệu của tôi nằm trọn trong bộ nhớ. 3,23 triệu mẫu vừa được nạp nên chúng còn trong khối head của Prometheus, chưa nén xuống đĩa. Truy vấn trên dữ liệu cũ đã ghi thành khối sẽ có thêm chi phí giải nén. Đó là lý do tôi gọi các con số này là cận dưới.

Một phép đo phải vứt đi trọn vẹn, và cách nó lộ ra. Lần đo đầu tiên tôi dựng exporter với 400 chuỗi. Kết quả: mọi panel dưới 7 ms, cả bảng 40 panel mất 26 ms, và chi phí làm tươi làm tròn thành 0% một nhân. Nếu dừng ở đó, bài này sẽ kết luận rằng bảng điều khiển gần như miễn phí.

Điều khiến tôi không tin con số đó không phải là một sai số nào — mọi phép đo đều đúng. Vấn đề là tôi đã dựng một thí nghiệm ở quy mô không thể cho thấy điều gì: 400 chuỗi là quy mô của một dịch vụ đồ chơi, và ở đó mọi thứ đều rẻ. Dựng lại ở 21 000 chuỗi — vẫn là một hệ thống nhỏ theo tiêu chuẩn thực tế — thì cùng những truy vấn ấy tốn từ 95 tới 488 ms.

Bài học tôi mang sang các phần sau: một phép đo cho ra "không tốn gì" phải bị nghi ngờ y như một phép đo cho ra con số vượt giới hạn vật lý. Cả hai đều thường là dấu hiệu của thí nghiệm sai quy mô, không phải của một sự thật thú vị.

Thử ba mươi giây

# Doi <prom> thanh dia chi Prometheus cua ban
P=http://localhost:9090
Q='rate(http_requests_total[5m])'
END=$(date +%s); START=$((END-21600))          # 6 gio

for STEP in 15 60 300; do
  SZ=$(curl -s -G "$P/api/v1/query_range" \
        --data-urlencode "query=$Q" \
        --data-urlencode "start=$START" --data-urlencode "end=$END" \
        --data-urlencode "step=${STEP}s" | wc -c)
  printf "  step %4ss -> %8.2f MB\n" "$STEP" "$(echo "$SZ/1048576" | bc -l)"
done

Ba dòng, cùng một truy vấn, cùng một khoảng xem. Nếu dòng đầu tính bằng chục megabyte, đó là số byte mà mỗi người mở bảng điều khiển đang tải về, mỗi lần làm tươi.