Phần trước đo giá của việc nhìn. Phần này đo giá của việc giữ: một mẫu chiếm bao nhiêu byte sau khi nén, giãn chu kỳ thu thập tiết kiệm được bao nhiêu, và — câu hỏi ít ai đặt — giảm mẫu để tiết kiệm chỗ thì bạn mất đúng cái gì.

Lưu chỉ số dài hạn: nén bao nhiêu, giảm mẫu mất gì

Bảng số liệu

Ba Prometheus 2.54.1 chạy song song, cùng thu thập một exporter duy nhất, khác nhau đúng một tham số là chu kỳ thu thập. Chạy 180 giây.

Nén, đo trên bản chu kỳ 1 giây (3 881 303 mẫu):

Dung lượng Byte mỗi mẫu
WAL — nhật ký ghi trước, chưa nén 26,84 MB 7,25
Chunk — đã nén 8,00 MB 2,16

Nén được 3,36 lần.

Đổi chu kỳ thu thập — số chuỗi giống hệt nhau ở cả ba:

Chu kỳ Số chuỗi Số mẫu Tổng thư mục So với 15s
1 giây 21 563 3 881 303 34,87 MB 17,3×
5 giây 21 563 754 668 5,55 MB 2,7×
15 giây 21 563 258 168 2,02 MB 1,0×

Giảm mẫu — một chuỗi 3 600 điểm với ba đợt chậm, đỉnh cao nhất 939,3 ms, gộp 60 điểm thành 1:

Cách gộp Đỉnh còn lại Giữ được
Trung bình (avg) 149,9 ms 16%
Nhỏ nhất (min) 13,7 ms 1%
p95 trong cửa sổ 850,8 ms 91%
Lớn nhất (max) 939,3 ms 100%

Một đợt chậm phải kéo dài bao lâu thì avg cửa sổ 60 giây còn thấy:

Độ dài đợt avg So với nền
1 giây 31,5 ms 1,5×
5 giây 84,6 ms 4,0×
10 giây 146,4 ms 6,9×
30 giây 406,7 ms 19,2×

Điều đáng nhớ

Nói lại theo cách khác:

Một mẫu chỉ tốn 2,16 byte. Đó là một cặp dấu thời gian và giá trị — về lý thuyết 16 byte — được nén xuống còn hơn một phần bảy. Prometheus không lưu giá trị mà lưu độ lệch so với điểm trước, và với chuỗi thu thập đều thì độ lệch của dấu thời gian gần như luôn bằng nhau nên gần như miễn phí.

Giãn chu kỳ thu thập là đòn bẩy gần như tuyến tính. Từ 1 giây sang 15 giây: số mẫu giảm 15,0 lần, dung lượng giảm 17,3 lần. Vì số chuỗi giống hệt nhau ở cả ba bản, toàn bộ chênh lệch đến từ số mẫu — nghĩa là ở quy mô này chi phí của từng mẫu chi phối, chứ không phải chi phí cố định của từng chuỗi.

Nhưng giảm mẫu thì không miễn phí, và cái giá không nằm ở chỗ người ta nghĩ. Gộp 60 điểm thành một bằng trung bình giữ lại đúng 16% chiều cao đỉnh: một đợt chậm 939 ms hiện ra thành 150 ms. Dữ liệu vẫn còn, biểu đồ vẫn có đường, chỉ là đỉnh đã bị san phẳng và không ai biết.

Đợt càng ngắn thì trung bình càng mù. Một đợt chậm kéo dài 1 giây trong cửa sổ 60 giây chỉ làm trung bình nhích lên 1,5 lần nền — nằm trong nhiễu. Phải kéo dài ít nhất 30 giây thì nó mới rõ ràng.

Vì sao

Nén của Prometheus dựa trên hai quan sát về dữ liệu chuỗi thời gian: dấu thời gian tăng đều, và giá trị thường thay đổi ít giữa hai mẫu liền nhau. Nó lưu độ lệch của độ lệch cho dấu thời gian — với chu kỳ đều thì con số đó là 0 và chỉ tốn vài bit — và lưu phần khác biệt nhị phân cho giá trị. Kết quả là 2,16 byte cho một mẫu mà biểu diễn thô cần 16 byte.

Điều đó cũng giải thích vì sao con số này không phải hằng số: chuỗi càng đều thì càng nén tốt. Một bộ đếm tăng chậm nén cực tốt; một chỉ số nhảy ngẫu nhiên thì tệ hơn nhiều.

Còn chuyện giảm mẫu mất đỉnh là số học đơn giản. Trong cửa sổ 60 giây có 10 giây chậm ở 800 ms và 50 giây bình thường ở 20 ms, trung bình là (50 × 20 + 10 × 800) / 60 ≈ 150 ms. Trung bình làm đúng việc của nó — nó tính giá trị đại diện — và giá trị đại diện của một cửa sổ chủ yếu bình thường thì phải gần mức bình thường.

Đây là cùng một cơ chế đã thấy ở phần 12 khi trung bình che mất đuôi phân bố, chỉ khác là lần này nó che theo trục thời gian thay vì theo trục phân vị. Và nó nguy hiểm hơn, vì đây là phép biến đổi không đảo ngược được: khi dữ liệu độ phân giải cao đã bị xoá, không cách nào lấy lại đỉnh.

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

  • Ước lượng dung lượng bằng 2,16 byte mỗi mẫu, và nhân ra. 21 563 chuỗi thu thập mỗi 15 giây là 1 438 mẫu mỗi giây, tức 5,18 triệu mỗi giờ và khoảng 256 MB mỗi ngày. Con số đó còn dễ chịu — cho tới khi số chuỗi tăng gấp mười, và lúc đó là 2,5 GB mỗi ngày cho đúng một chỉ số.
  • Giãn chu kỳ trước khi nghĩ tới giảm mẫu. Đổi từ 15 giây sang 30 giây cắt một nửa dung lượng và không mất gì ngoài độ phân giải thời gian — trong khi giảm mẫu bằng trung bình cắt được nhiều hơn nhưng xoá mất thông tin về đỉnh.
  • Nếu giảm mẫu, hãy giữ max song song với avg. Giữ cả avg, maxmin cho ra 180 điểm thay vì 3 600 — vẫn giảm 20 lần thay vì 60 lần, và đó là cái giá hợp lý để không mù trước sự cố ngắn. Các hệ lưu trữ dài hạn quen thuộc đều làm đúng như vậy, và giờ bạn biết vì sao.
  • Đừng nhìn WAL rồi tính ngân sách đĩa. WAL là 26,84 MB trong khi dữ liệu thật là 8,00 MB — chênh 3,36 lần. Đây là lần thứ hai trong sê-ri này thư mục ghi-trước suýt làm hỏng một kết luận về dung lượng.
  • Khi đọc biểu đồ dữ liệu cũ, hãy nhớ nó có thể đã bị giảm mẫu. Một biểu đồ sáu tháng trông phẳng lặng không có nghĩa là sáu tháng qua không có sự cố nào — nó có thể nghĩa là mọi sự cố đều ngắn hơn cửa sổ gộp.

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

Ba bản Prometheus của tôi chạy quá ngắn để so nén một cách công bằng. Prometheus chỉ ghi một chunk xuống đĩa khi chunk đầy 120 mẫu, nên sau 180 giây chỉ bản chu kỳ 1 giây có chunk thật; hai bản còn lại có chunks = 0,00 MB và toàn bộ dung lượng của chúng là WAL. Vì thế cột "tổng thư mục" trong bảng so WAL với WAL chứ không so dữ liệu đã nén. Tỷ lệ 17,3 lần vẫn phản ánh đúng chiều hướng vì cả ba đều ở cùng trạng thái, nhưng con số nén 2,16 byte mỗi mẫu thì chỉ đo được trên bản 1 giây. Muốn so đúng, phải chạy bản 15 giây ít nhất 30 phút.

Tỷ lệ nén phụ thuộc dữ liệu. Exporter của tôi tăng bộ đếm bằng số ngẫu nhiên trong khoảng 0–50 mỗi lần, tức tương đối đều. Chỉ số thật có cái đều hơn (bộ đếm request) và cái nhảy loạn hơn (độ trễ, độ sâu hàng đợi). Con số 2,16 nằm ở khoảng giữa chứ không phải một trần hay sàn.

Phần giảm mẫu là tính toán trên dữ liệu tôi sinh ra, không phải một hệ lưu trữ dài hạn thật. Tôi không chạy Thanos hay Mimir; tôi lấy một chuỗi và tự gộp nó bằng bốn hàm khác nhau. Điều đó đủ để chứng minh trung bình xoá đỉnh — đó là số học, không phụ thuộc công cụ — nhưng nó không nói gì về chi phí và hành vi thật của các hệ đó.

Đỉnh trong dữ liệu thử là do tôi đặt vào: ba đợt, mỗi đợt 10 giây, cao gấp khoảng 40 lần nền. Chọn đợt dài hơn hoặc thấp hơn thì tỷ lệ "giữ được 16%" sẽ khác. Cái tôi tin là cơ chế và bảng độ dài đợt ở cuối — nó cho thấy chính xác ngưỡng mà trung bình bắt đầu nhìn thấy.

Thử ba mươi giây

docker run --rm python:3.12-slim python - <<'EOF'
import random, statistics
random.seed(5)
lat = [random.lognormvariate(-3.9, 0.25) for _ in range(3600)]   # nen ~20 ms
for s in (600, 1800, 3000):                                      # ba dot cham
    for i in range(s, s+10): lat[i] = random.lognormvariate(-0.25, 0.10)

def gop(f, w=60): return [f(lat[i:i+w]) for i in range(0, len(lat), w)]
dinh = max(lat)
print("dinh that            %8.1f ms" % (dinh*1000))
for ten, f in (("gop bang avg", statistics.fmean), ("gop bang max", max)):
    print("%-20s %8.1f ms   giu duoc %3.0f%%"
          % (ten, max(gop(f))*1000, 100*max(gop(f))/dinh))
EOF

Hai dòng cuối là hai biểu đồ mà bạn sẽ nhìn sáu tháng sau. Một trong hai vẫn còn sự cố; cái kia thì không, và không có gì trên màn hình cho biết nó đã từng có.