Không phải thứ gì cũng đáng cất giữ như nhau. Hoá đơn tiền điện tháng này bạn giữ vài năm cho quyết toán thuế, nhưng tờ giấy gói bánh mì sáng nay thì vứt ngay — không phải vì cái này quan trọng hơn cái kia ở mọi lúc, mà vì giá trị của chúng phai theo thời gian với tốc độ khác nhau, và cái giữ lâu tình cờ lại là cái nhẹ. Ba nguồn dữ liệu quan sát — chỉ số, log, trace — chia đúng theo lằn đó, và bài này tôi đặt chúng vào một tình huống rất cụ thể để thấy: ba ngày sau sự cố, bạn ngồi viết báo cáo — dữ liệu nào còn sống, và giữ cái nào bao lâu là đáng?
(Các con số tính từ các hằng số đo trong sê-ri, là dung lượng ước tính, không phải perf counter phần cứng.)
Bảng số liệu
Tính ở 1 000 yêu cầu mỗi giây, dùng các con số đo được ở chính sê-ri này: 67 byte mỗi dòng log (phần 31), 135 byte mỗi span và 4 span mỗi yêu cầu (phần 21), 2,16 byte mỗi mẫu với 900 chuỗi thu thập mỗi 15 giây (phần 19).
| Nguồn | Mỗi ngày | 7 ngày | 30 ngày | 90 ngày |
|---|---|---|---|---|
| Chỉ số | 0,01 GB | 0,1 GB | 0,3 GB | 0,9 GB |
| Log | 5,39 GB | 37,7 GB | 161,7 GB | 485,2 GB |
| Trace | 43,45 GB | 304 GB | 1 304 GB | 3 911 GB |
Trace tốn gấp 4 178 lần chỉ số mỗi ngày. Hai cách đặt thời gian giữ:
| Tổng | |
|---|---|
| Giữ cả ba trong 30 ngày | 1 466 GB |
| Chỉ số 90 ngày, log 7 ngày, trace 3 ngày | 169 GB — tiết kiệm 88% |
Và sáu câu hỏi một báo cáo phải trả lời:
| Câu hỏi | Nguồn |
|---|---|
| Sự cố bắt đầu lúc nào | chỉ số |
| Ảnh hưởng bao nhiêu người | chỉ số |
| Chặng dịch vụ nào chậm | trace |
| Yêu cầu cụ thể nào thất bại | log |
| Hàm nào tốn CPU | hồ sơ |
| Đã từng xảy ra chưa | chỉ số — cần dữ liệu vài tháng tuổi |
Bốn điều đáng nhớ
Ba trong sáu câu hỏi cần chỉ số, và chỉ số là nguồn rẻ nhất — 0,01 GB mỗi ngày so với 43,45 GB của trace; sự trùng hợp đó không phải may mắn mà đến từ việc chỉ số lưu trạng thái gộp chứ không lưu từng sự kiện, như phần 31 đã đo. Câu hỏi khó nhất cần dữ liệu cũ nhất: "đã từng xảy ra chưa" là câu quyết định báo cáo này dẫn tới một bản sửa lỗi hay một dòng ghi chú, và nó cần dữ liệu vài tháng tuổi — với 0,9 GB cho 90 ngày, chỉ số là nguồn duy nhất đủ rẻ để giữ lâu như vậy. Đặt thời gian giữ bằng nhau cho ba nguồn là lãng phí 88%: giữ cả ba 30 ngày tốn 1 466 GB, còn giữ chỉ số 90 ngày / log 7 ngày / trace 3 ngày tốn 169 GB — và cách thứ hai còn giữ chỉ số lâu gấp ba lần, vừa rẻ hơn vừa tốt hơn ở đúng chỗ quan trọng. Trace là nguồn đắt nhất nhưng cũng cần ít thời gian nhất: câu hỏi nó trả lời — chặng nào chậm — được trả lời trong lúc điều tra, thường vài giờ đầu; ba ngày sau khi ngồi viết báo cáo, bạn đã biết câu đó rồi.
Vì sao
Khác biệt 4 178 lần đến thẳng từ mô hình dữ liệu (phần 31 đã đo): chỉ số ghi trạng thái, log và trace ghi sự kiện. Một Counter là một số, nó tăng lên chứ không dài ra; dung lượng của nó tỷ lệ với số chuỗi thời gian nhân số lần thu thập — cả hai đều không phụ thuộc việc dịch vụ nhận một hay một triệu yêu cầu — còn log và trace thì mỗi yêu cầu để lại dấu vết riêng. Điều đó dẫn tới một tính chất quan trọng cho lưu dài hạn: chi phí giữ chỉ số không tăng khi lưu lượng tăng; dịch vụ lớn gấp mười lần thì hoá đơn log và trace lớn gấp mười, còn hoá đơn chỉ số gần như không đổi.
Về phía giá trị, ba nguồn có ba khoảng hữu ích khác nhau. Trace hữu ích nhất trong lúc sự cố đang diễn ra, khi bạn cần biết đi tìm ở đâu; log hữu ích ngay sau đó, khi đã biết chỗ và cần chi tiết; chỉ số hữu ích mãi về sau, vì nó là thứ duy nhất cho phép so sánh sự cố này với sự cố tháng trước. Đặt cùng một thời gian giữ cho cả ba là bỏ qua khác biệt đó theo cả hai chiều: giữ trace quá lâu so với lúc nó còn hữu ích, và giữ chỉ số quá ngắn so với lúc nó cần nhất.
Ra thực tế thì đặt ba thời gian giữ khác nhau, theo thứ tự ngược với chi phí — chỉ số lâu nhất, trace ngắn nhất; đây là một trong ít quyết định mà rẻ hơn và tốt hơn đi cùng nhau. Trước khi cắt thời gian giữ, hãy cắt lấy mẫu trace: phần 23 đã đo lấy mẫu đuôi giữ 100% trace lỗi với 5,93% chi phí lưu, áp nó trước rồi mới bàn số ngày. Ghi các chỉ số cấp SLO thành chuỗi riêng và giữ chúng hàng năm — chúng vừa rẻ vừa là thứ cần để trả lời "đã từng xảy ra chưa" và để báo cáo với người ngoài đội. Kiểm thời gian giữ trước khi lên lịch viết báo cáo: nếu trace giữ 3 ngày mà báo cáo viết vào ngày thứ tư, hãy chụp lại các trace quan trọng trong lúc điều tra, đừng đợi. Và đừng ngoại suy con số của tôi — chạy lại phép tính với lưu lượng và số chuỗi thật của bạn; tỷ lệ giữa ba nguồn thì giữ nguyên, giá trị tuyệt đối thì không.
Mẫu số chung
Cái nguyên tắc "dữ liệu khác nhau có tốc độ phai giá trị khác nhau và đường chi phí khác nhau, nên phải phân tầng thời gian giữ thay vì áp một chính sách" không riêng gì quan sát — nó là bài toán vòng đời dữ liệu ở mọi hệ lưu trữ.
Trong thế giới Java, đây đúng là chuyện cấu hình Logback/Log4j2 với RollingPolicy và maxHistory khác nhau theo mức: log DEBUG xoay vòng và xoá sau vài ngày, còn log AUDIT/nghiệp vụ giữ lâu hơn hẳn — cùng ý tưởng "cái rẻ và cần lâu thì giữ, cái nặng và mau hết giá trị thì buông". Và ở tầng cache, TTL của Caffeine/Redis cũng đặt theo tốc độ dữ liệu cũ đi, không phải một con số chung.
Trong Go, các time-series như Prometheus (viết bằng Go) và VictoriaMetrics làm chính xác việc này ở tầng hạ tầng: downsampling — giữ dữ liệu độ phân giải cao trong ít ngày rồi gộp thành mẫu thưa hơn cho dài hạn, để "đã từng xảy ra chưa" vẫn trả lời được mà không giữ từng điểm giây suốt một năm.
Còn ở cơ sở dữ liệu, đây là bài học vận hành trung tâm: phân vùng bảng theo thời gian rồi drop cả partition cũ (rẻ hơn DELETE triệu dòng), đặt TTL khác nhau cho bảng nóng và bảng nguội, và chuyển dữ liệu lạnh xuống tầng rẻ (S3 Glacier, object storage) qua lifecycle policy. Cùng một khung: hot/warm/cold, mỗi tầng một thời gian giữ và một giá lưu.
Mẫu số chung: giá trị của một mẩu dữ liệu và chi phí giữ nó đều biến thiên theo thời gian, thường theo hai đường khác nhau — nên chính sách giữ đúng là phân tầng theo cặp (giá trị-còn-lại, chi phí-lưu) cho từng loại, chứ không một thời-gian-giữ áp cho tất cả. Vì thế câu hỏi đúng không phải "giữ dữ liệu bao lâu" mà là "loại dữ liệu này còn hữu ích tới khi nào, và giữ nó tới đó tốn bao nhiêu" — trả lời riêng cho từng loại thì bạn vừa cắt được hoá đơn vừa giữ đúng thứ cần lâu.
Muốn tự chạy phép tính với con số của mình trong ba mươi giây:
docker run --rm python:3.12-slim python - <<'EOF'
# Doi ba con so nay thanh so cua ban roi chay lai
RPS = 1000 # yeu cau moi giay
SPAN = 4 # span moi yeu cau
CHUOI = 900 # so chuoi thoi gian
CHU_KY = 15 # giay
# Ba hang so nay do duoc trong se-ri, khong can doi
B_LOG, B_SPAN, B_MAU = 67, 135, 2.16
GB = 1073741824
ngay = {
"chi so": CHUOI / CHU_KY * B_MAU * 86400 / GB,
"log": RPS * B_LOG * 86400 / GB,
"trace": RPS * SPAN * B_SPAN * 86400 / GB,
}
print("%-10s %12s %12s %12s" % ("nguon", "moi ngay", "30 ngay", "90 ngay"))
for k, v in ngay.items():
print("%-10s %9.2f GB %9.1f GB %9.1f GB" % (k, v, v*30, v*90))
print()
deu = sum(ngay.values()) * 30
lech = ngay["chi so"]*90 + ngay["log"]*7 + ngay["trace"]*3
print("ca ba giu 30 ngay : %8.0f GB" % deu)
print("chi so 90 / log 7 / trace 3 : %8.0f GB (tiet kiem %.0f%%)" % (lech, 100*(1-lech/deu)))
EOF
Nếu dòng cuối tiết kiệm được hơn tám mươi phần trăm, đó là khoản bạn đang trả để giữ trace lâu hơn lúc nó còn hữu ích.