Remote Read Meets Streaming

Bài tóm tắt Prometheus 2019 3. Quy mô lớn Độ khó: Trung bình

Prometheus · 2019 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Bài của dự án mã nguồn mở (tác giả là maintainer của Prometheus và Thanos), giải thích một thay đổi giao thức trong Prometheus 2.13.0. Benchmark chạy trên laptop với dữ liệu sinh giả.

Bối cảnh

  • Prometheus có remote API từ bản 1.x: write (Prometheus đẩy sample ra hệ khác, cách phổ biến để lưu dài hạn) và read (hệ khác đọc thẳng dữ liệu trong TSDB của Prometheus, không qua PromQL). Remote read có từ 3/2017, dùng để: nâng cấp giữa hai định dạng lưu trữ (Prometheus đọc từ Prometheus), Prometheus đọc từ kho dài hạn, hoặc hệ khác như Thanos đọc từ Prometheus.
  • Remote read cũ có hai vấn đề: không stream trong một request, và trả sample thô (float64 + int64) thay vì chunk nén mà TSDB vốn lưu.
  • Hệ quả: server phải gom toàn bộ kết quả chưa nén trong bộ nhớ, marshal thành một message protobuf khổng lồ, nén, gửi; client cũng phải nhận đủ mới giải mã được. Một request 8 giờ khớp 10.000 series có thể tốn tới 2,5 GB bộ nhớ ở mỗi bên.
  • Query cho dashboard thường chỉ trả khoảng 20 series nhưng là kết quả gộp từ hàng nghìn series bên dưới; với Thanos, request remote read hay rất nặng.

Thay đổi chính

  • Bên trong Prometheus vốn đã đọc theo kiểu iterator (lấy từng series, rồi từng sample, theo nhu cầu), không cần danh sách dựng sẵn. Remote read mới dùng lại đúng mẫu đó qua mạng.
  • Protobuf không tự phân tách message, nên giao thức được mở rộng thành chuỗi frame nhỏ (chế độ STREAMED_XOR_CHUNKS; chế độ cũ là SAMPLES): server làm từng series, gửi một frame khi đủ khoảng 1 MB, dùng lại vùng nhớ cho series sau.
  • Frame chứa chính các chunk nén XOR như trong TSDB, nên server không phải giải nén ra sample thô; client như Thanos dùng lại được định dạng này thì cũng không phải nén lại.
  • Tương thích hai chiều: client mới gửi thêm trường accepted_response_types; server cũ bỏ qua và trả kiểu cũ, server mới không thấy trường này thì trả kiểu cũ cho client cũ.

Kết quả (benchmark của tác giả, 10.000 series)

  • Bộ nhớ: Prometheus khoảng 50 MB mỗi request, gần như không đổi bất kể khoảng thời gian và số series; sidecar Thanos gần như không tốn thêm.
  • CPU giảm khoảng một nửa; latency 8 giờ dữ liệu từ khoảng 34,7 s xuống 8,2 s (khoảng 2,5 lần nhanh hơn), và dữ liệu bắt đầu tới client ngay.
  • Cờ giới hạn số sample không còn tác dụng với chế độ mới; giới hạn số request đồng thời vẫn giữ.

Bài học cho SWE

  • Bộ nhớ không đổi theo request làm capacity planning dễ hơn nhiều: chỉ cần giới hạn số request đồng thời, không phải đoán request to nhất (Load shedding, rate limit và backpressure). Bộ nhớ là tài nguyên không nén được, vượt là chết.
  • Trả kết quả lớn thì stream thành frame nhỏ thay vì một message khổng lồ; giữ định dạng lưu trữ gốc qua mạng để tránh giải nén rồi nén lại.
  • Hạ tầng quan sát cũng phải được thiết kế cho tải nặng: một query gộp trông nhỏ có thể chạm hàng nghìn series bên dưới (Observability và monitoring).
  • Mở rộng giao thức bằng một trường tuỳ chọn mà phía cũ bỏ qua là cách nâng cấp không phải cập nhật hai bên cùng lúc.

Liên quan