
Giới thiệu
Khi Prometheus đã tạo ra một loạt khối, chúng ta cần thường xuyên bảo trì các khối đó để sử dụng đĩa hiệu quả và duy trì hiệu suất của các truy vấn.
Trong bài này, chúng ta sẽ xem xét 2 chủ đề, nén và lưu giữ, xảy ra ở chế độ nền khi Prometheus đang chạy.
Nếu bạn chưa đọc các phần trước, bây giờ là thời điểm thích hợp để xem phần 1 và phần 4 để hiểu rõ hơn về bài đăng này.
Compaction
Nén bao gồm việc ghi một khối mới từ một hoặc nhiều khối hiện có (gọi là khối nguồn hoặc khối cha), và cuối cùng, các khối nguồn sẽ bị xóa và khối nén mới sẽ được sử dụng thay cho các khối nguồn đó.
Nhưng tại sao chúng ta cần nén?
- Như chúng ta đã thấy ở phần 4, bất kỳ thao tác xóa nào đối với dữ liệu đều được lưu trữ dưới dạng tombstone trong một tệp riêng biệt trong khi dữ liệu vẫn còn trên đĩa. Vì vậy, khi tombstone chạm đến hơn một số % của series, chúng ta cần xóa dữ liệu đó khỏi đĩa.
- Với tỷ lệ churn đủ thấp, hầu hết dữ liệu trong chỉ mục ở các khối liền kề (tính theo thời gian) sẽ giống nhau. Vì vậy, bằng cách nén (gộp) các khối liền kề đó, chúng ta có thể loại bỏ phần lớn dữ liệu trùng lặp trong index và do đó tiết kiệm dung lượng đĩa.
- Khi truy vấn đạt đến >1 khối, chúng ta phải hợp nhất kết quả nhận được từ các khối riêng lẻ và điều này có thể gây ra một chút chi phí. Bằng cách hợp nhất các khối liền kề, chúng ta có thể ngăn chặn được chi phí này.
- Nếu có các khối chồng chéo (chồng chéo theo thời gian), việc truy vấn chúng đòi hỏi phải loại bỏ samples trùng lặp giữa các khối, tốn kém hơn đáng kể so với việc chỉ ghép chunks từ các khối khác nhau. Việc hợp nhất các khối chồng chéo này sẽ tránh được việc loại bỏ trùng lặp.
Dưới đây là hai bước để thực hiện nén đơn. Mỗi phút, chúng tôi khởi tạo một chu trình nén, trong đó chúng tôi kiểm tra bước 1 và chỉ chuyển sang bước 2 nếu bước 1 không rỗng. Chu trình nén chạy các bước này theo vòng lặp và kết thúc khi bước 1 rỗng.
Bước 1: Kế hoạch (Plan)
“Kế hoạch” là danh sách các khối cần được nén lại với nhau, được chọn dựa trên các điều kiện bên dưới theo thứ tự ưu tiên (từ cao đến thấp). Điều kiện đầu tiên được thỏa mãn sẽ tạo ra một kế hoạch, do đó chỉ có 1 điều kiện cho mỗi kế hoạch. Khi không có điều kiện nào đáp ứng, kế hoạch sẽ rỗng.
Điều kiện 1: Các khối trùng lặp
Như đã thấy ở trên, các khối chồng chéo có thể làm chậm truy vấn. Hơn nữa, bản thân Prometheus không tạo ra các khối chồng chéo, điều này chỉ có thể xảy ra nếu bạn chèn một số dữ liệu vào Prometheus. Vì vậy, ưu tiên hàng đầu là loại bỏ sự chồng chéo và đưa trạng thái trở lại trạng thái mà Prometheus sẽ tạo ra.
Kế hoạch có thể bao gồm >2 khối. Hãy xem ví dụ này:
|---1---|
|---2---|
|---3---|
|---4---|Mặc dù chỉ có 2 khối cho mỗi lần chồng lấn, nhưng nếu bạn nhìn kỹ, khi chúng ta nén một lần chồng lấn, giả sử là 1 và 3, cuối cùng chúng sẽ chồng lấn với 2. Vì vậy, thay vì trải qua nhiều chu kỳ để sửa tất cả các lần chồng lấn được liên kết, lần chạy đầu tiên sẽ chọn [1 2 3 4] theo kế hoạch và giảm số lần nén.
Một ví dụ khác tạo ra một kế hoạch duy nhất [1 2 3]
|-----1-----|
|--2--|
|----3----| Lưu ý rằng hỗ trợ khối chồng chéo không được bật theo mặc định trong Prometheus, nó sẽ báo lỗi khi khởi động nếu bạn có các khối chồng chéo, trừ khi được bật thông qua --storage.tsdb.allow-overlapping-blocks.
Điều kiện 2: Khoảng thời gian đặt trước
Trong trường hợp này, chúng ta chọn >1 khối để hợp nhất nhằm lấp đầy một số khoảng thời gian được đặt trước. Trong Prometheus, theo mặc định, khoảng thời gian là [2h 6h 18h 54h 162h 486h], tức là bắt đầu từ 2 giờ với bội số của 3.
Hãy lấy một ví dụ về 6hphạm vi. Chúng tôi chia thời gian Unix thành các nhóm như sau 0-6h, 6h-12h, 12h-18h ...và nếu >1 khối rơi vào bất kỳ nhóm nào, điều đó sẽ tạo thành một kế hoạch và chúng tôi sẽ nén chúng lại với nhau để tạo thành một khối có độ dài tối đa là 6 giờ.
Chúng tôi cũng lưu ý không nén các khối mới nhất chưa trải rộng toàn bộ nhóm lại với nhau. Ví dụ, 2 khối mới nhất trong phạm vi 2h sẽ không được nén lại với nhau vì chúng (1) mới (2) không trải rộng trong phạm vi 6h cộng lại. Vì Prometheus tạo ra các khối 2h, khi chúng ta có >=3 khối, các khối rơi vào cùng một nhóm sẽ được nén lại với nhau.
Tương tự, chúng tôi kiểm tra tất cả các phạm vi để xem có nhóm thời gian nào có >1 khối rơi vào hay không. Khi kết thúc chu kỳ nén, sẽ không có nhóm thời gian nào có >1 khối rơi vào tất cả các phạm vi.
Trong Prometheus, kích thước tối đa của một khối có thể là 31d(tức là 744h) hoặc 1/10 thời gian lưu giữ, tùy theo kích thước nào thấp hơn.
Điều kiện 3: Tỉ lệ tombstones bao phủ trong series
Cuối cùng, nếu bất kỳ khối nào có tombstones chạm tới >5% tổng số series trong khối, chúng tôi sẽ chọn khối đó để nén, trong đó dữ liệu được tombstones chỉ ra sẽ bị xóa khỏi đĩa (bằng cách tạo một khối mới không có samples nào được tombstones bao phủ). Điều này tạo ra một kế hoạch chỉ có 1 khối.
Bước 2: Quá trình nén
Như đã thấy trong phần 4, persistent blocks là bất biến. Để thực hiện bất kỳ thay đổi nào, chúng ta phải viết một khối mới. Tương tự, trong quá trình nén, chúng ta viết một khối hoàn toàn mới, ngay cả khi đó là quá trình nén của một khối duy nhất. Bước nén chỉ nhận danh sách các khối để nén lại thành một khối duy nhất và không biết gì về logic được sử dụng để tạo ra kế hoạch này.
Logic nén đã phát triển theo thời gian với nhiều kỹ thuật quản lý bộ nhớ khác nhau và khả năng hợp nhất dữ liệu nhanh hơn. Ở cấp độ cao hơn, nén thực hiện hợp nhất N series từ khối nguồn trong khi lặp lại từng series một theo cách được sắp xếp (thứ tự xuất hiện của chúng trong chỉ mục cũng vậy).
Trong khi series được loại bỏ trùng lặp trong index, khi các khối không chồng chéo, các chunks sẽ được nối lại với nhau từ các khối nguồn. Nếu các khối chồng chéo, chỉ các chunks chồng chéo được giải nén, các samples được loại bỏ trùng lặp (tức là chỉ giữ lại 1 sample để khớp với dấu thời gian) và được nén lại thành >=1 chunk trong khi vẫn giữ kích thước tối đa của chunk là 120 sample.
Nếu có tombstones trong bất kỳ khối nào, các chunks của series đó sẽ được viết lại để loại trừ các khoảng thời gian được đề cập trong tombstones. Khối cuối cùng sẽ không có tombstones nào.
Mỗi khối được nén đều được gán một mức nén, cho biết khối đó được tạo ra như thế nào, tức là số lần khối được nén để có được khối này. Mức nén này max(level of source blocks) + 1 dành cho khối mới.
Nếu tất cả các samples của một series bị xóa, series đó sẽ bị bỏ qua hoàn toàn khỏi khối mới. Nếu khối có 0 samples (tức là khối trống), thì không có khối nào được ghi vào đĩa trong khi các khối nguồn bị xóa.
Lưu ý rằng bản thân quá trình nén không xóa các khối nguồn mà chỉ đánh dấu chúng là có thể xóa (trong meta.json). Việc tải các khối mới và xóa các khối nguồn được TSDB xử lý riêng sau khi chu kỳ nén kết thúc.
Head compaction
Đây là một loại nén đặc biệt trong đó nguồn là Head block và quá trình nén sẽ giữ một phần của Head block thành persistent blocks trong khi loại bỏ bất kỳ dữ liệu nào được trỏ đến bởi tombstone.
Phần 1 minh họa và giải thích thời điểm thực hiện nén Head. Khối Head triển khai cùng giao diện với trình đọc persistent block, do đó chúng tôi sử dụng cùng mã nén để nén khối Head thành một persistent block.
Khối được tạo ra từ Head block có mức độ nén là 1.
Retention
TSDB cho phép thiết lập chính sách lưu trữ để giới hạn lượng dữ liệu bạn lưu trữ. Có 2 chính sách: lưu trữ theo thời gian và lưu trữ theo kích thước. Bạn có thể thiết lập một trong hai chính sách này hoặc cả hai. Khi bạn thiết lập cả hai chính sách, nó sẽ ở OR giữa hai chính sách, tức là chính sách nào thỏa mãn sẽ kích hoạt việc xóa dữ liệu liên quan.
Time based retention
Ở đây đề cập đến khoảng thời gian dữ liệu trong TSDB nên kéo dài bao lâu. Đây là khoảng thời gian tương đối được tính theo thời gian tối đa của persistent block mới nhất (và không liên quan đến khối Head). Một khối sẽ bị xóa khi nó vượt quá hoàn toàn khoảng thời gian lưu giữ, chứ không phải khi một phần của khối vượt quá thời gian lưu giữ.
Ví dụ, nếu thời gian lưu giữ là 15d, ngay khi khoảng cách giữa thời gian tối đa của khối cũ nhất và thời gian tối đa của khối mới nhất vượt quá 15d, khối cũ nhất sẽ bị xóa.
Size based retention
Trong phần này, bạn đề cập đến kích thước tối đa của TSDB trên đĩa. Kích thước này bao gồm WAL, checkpoint, m-mapped chunks và persistent blocks. Mặc dù chúng tôi đếm tất cả các khối này để quyết định việc xóa, WAL, checkpoint và m-mapped chunks vẫn cần thiết cho hoạt động bình thường của TSDB. Vì vậy, ngay cả khi chúng cộng lại vượt quá giới hạn lưu giữ kích thước, chỉ các khối mới bị xóa. Vì vậy, TSDB có thể chiếm nhiều hơn kích thước tối đa được chỉ định nếu bạn đặt kích thước quá thấp.
Việc lưu giữ dựa trên kích thước nghiêm ngặt hơn so với việc lưu giữ dựa trên thời gian. Ngay khi toàn bộ không gian được sử dụng lớn hơn ít nhất 1 byte so với kích thước tối đa, khối cũ nhất sẽ bị xóa.
Mã tham khảo
tsdb/compact.go có mã để tạo kế hoạch và nén các khối.
storage/merge.go có mã để nối/gộp các khối từ các khối khác nhau (cho cả các chunks chồng chéo và không chồng chéo).
tsdb/db.go có mã để khởi tạo chu kỳ nén mỗi phút và gọi bước 1 & bước 2 trên các khối và nén Head block. Nó cũng có mã cho cả hai loại giữ lại.

