Kardinalitas Prometheus: Recording Rules yang Menyelamatkan TSDB Anda

Foto oleh kevinmarsh on flickr
Kardinalitas adalah jumlah time series unik yang dihasilkan satu nama metrik setelah menghitung setiap kombinasi nilai label. Ini penting karena Prometheus menyimpan setiap series aktif di memori, sehingga metrik dengan label tak terbatas seperti user ID atau path request mentah bisa diam-diam tumbuh menjadi jutaan series dan menghabiskan RAM server.
Recording rule menjalankan ekspresi PromQL pada jadwal tetap dan menyimpan hasil agregasinya sebagai kumpulan series baru yang jauh lebih kecil. Dashboard dan alert kemudian membaca series murah yang sudah pra-agregasi itu, bukan menghitung ulang query mahal berkardinalitas tinggi dari data mentah setiap kali, sehingga menurunkan biaya query maupun jumlah series efektif bagi konsumen di hilir.
Tidak, keduanya menyelesaikan masalah yang berbeda. Recording rules berjalan pada data yang sudah di-scrape dan disimpan, sehingga series mentah berkardinalitas tinggi tetap membebani ingestion, WAL, dan memori head block sebelum roll-up terjadi. Relabeling dengan metric_relabel_configs berjalan saat waktu scrape, sebelum sampel ditulis, sehingga itulah alat yang tepat untuk menghentikan label yang benar-benar tak terbatas sebelum sampai ke storage.
Buka halaman tsdb-status bawaan pada server Prometheus Anda, yang mengurutkan nama metrik dan pasangan label berdasarkan berapa banyak series yang mereka sebabkan. Padukan dengan metrik prometheus_rule_evaluation_duration_seconds untuk melihat apakah recording rules Anda yang sudah ada mulai tertinggal dari interval evaluasinya, tanda dini bahwa kardinalitas sudah melampaui strategi agregasi Anda saat ini.
Konvensi resminya adalah level titik dua metric titik dua operations, di mana level mendaftarkan label agregasi yang tersisa, metric menjaga nama metrik asli tetap tidak berubah, dan operations mendaftarkan fungsi yang diterapkan dengan yang terbaru di depan. Ini membuat nama rule menjelaskan dirinya sendiri sehingga siapa pun yang membacanya langsung tahu metrik mentah mana asalnya dan apa yang telah dilakukan padanya.

Foto oleh kevinmarsh on flickr
Setiap operator Prometheus pada akhirnya menghadapi tembok yang sama: dashboard yang tadinya langsung tampil mulai timeout, grafik memori server naik lurus terus, dan pada suatu malam prosesnya di-OOM-kill. Penyebab utamanya hampir selalu kardinalitas, bukan volume trafik mentah. Satu nama metrik dengan segelintir label bisa diam-diam meledak menjadi jutaan time series yang berbeda, dan Prometheus harus menyimpan setiap series itu di memori selama series tersebut masih aktif.
Tulisan ini adalah panduan praktis tentang bagaimana kardinalitas benar-benar menghabiskan memori, cara mengukurnya sebelum Anda mulai menerka-nerka, serta dua alat yang saling melengkapi untuk menjaganya di produksi: recording rules yang melakukan pra-agregasi query yang mahal, dan relabeling yang menghentikan label tak terbatas sebelum sampai ke storage. Di akhir tulisan ini Anda akan punya checklist yang bisa langsung dijalankan pada deployment Prometheus Anda minggu ini.
Kardinalitas adalah jumlah time series unik yang dihasilkan satu nama metrik setelah memperhitungkan setiap kombinasi nilai label. Sebuah counter bernama http_requests_total dengan label method dan status mungkin hanya menghasilkan belasan series. Namun tambahkan path request mentah atau identifier pelanggan sebagai label, dan counter yang sama itu bisa menghasilkan satu series baru untuk setiap path unik atau setiap pelanggan unik, selamanya. Setiap series aktif duduk di head block dalam memori, dan berbagai tulisan teknis tentang internal Prometheus menyebut biayanya sekitar satu hingga tiga kibibyte RAM per series, sehingga server yang menyimpan beberapa juta series aktif butuh puluhan gigabyte hanya untuk tetap hidup. Begitu melewati beberapa juta series, latensi query, waktu evaluasi rule, dan waktu replay WAL saat restart semuanya ikut memburuk bersamaan.
Sebuah metrik tidak perlu terlihat berbahaya untuk benar-benar berbahaya. Dua label yang tampak biasa saja, masing-masing dengan 1.000 nilai, akan bergabung secara perkalian menjadi satu juta series begitu keduanya muncul pada metrik yang sama. Risiko kardinalitas adalah hasil perkalian jumlah nilai label, bukan penjumlahan.
Menerka-nerka metrik mana yang jadi masalah membuang waktu dan sering berujung memangkas label yang sebenarnya bukan sumber masalah. Prometheus sudah menyediakan halaman diagnostik bawaan di endpoint slash tsdb-status pada server Anda yang mengurutkan nama metrik dan pasangan label berdasarkan berapa banyak series yang mereka sebabkan. Padukan ini dengan metrik prometheus_rule_evaluation_duration_seconds, yang memberi tahu Anda saat recording rules Anda sendiri mulai tertinggal dari interval evaluasinya, sinyal dini yang kuat bahwa pertumbuhan kardinalitas sudah melampaui strategi agregasi Anda.
| Sinyal diagnostik | Di mana menemukannya | Apa yang ditunjukkannya |
|---|---|---|
| Jumlah total series aktif | Bagian Head Stats pada halaman tsdb-status | Tekanan memori keseluruhan di seluruh server, dan seberapa dekat Anda dengan zona bahaya |
| Pasangan label diurutkan berdasarkan jumlah series | Tabel kardinalitas label pada halaman tsdb-status yang sama | Label mana pada metrik mana yang paling bertanggung jawab atas sebagian besar series Anda, sehingga Anda tahu apa yang harus diperbaiki lebih dulu |
| prometheus_rule_evaluation_duration_seconds | Endpoint slash metrics milik Prometheus sendiri, di-scrape seperti target lainnya | Apakah recording rules Anda masih mengikuti interval evaluasi yang dikonfigurasi atau diam-diam mulai tertinggal |
Recording rule menjalankan sebuah ekspresi PromQL pada jadwal tetap dan menyimpan hasilnya sebagai time series baru sepenuhnya. Trik yang membuatnya efektif melawan kardinalitas adalah agregasi: daripada menampilkan detail per pod, per path, per status pada setiap query, Anda menghitung series yang sudah diagregasi sekali per interval evaluasi, lalu setiap dashboard dan alert membaca series murah yang sudah pra-agregasi itu, bukan menghitung ulang query mahal dari data mentah setiap kali. Konvensi penamaan resminya adalah level titik dua metric titik dua operations, di mana level menggambarkan label agregasi yang tersisa, metric menjaga nama metrik asli tetap tidak berubah, dan operations mendaftarkan fungsi yang diterapkan dengan yang terbaru di depan. Menjaga nama metrik tetap utuh dilakukan dengan sengaja: siapa pun yang membaca job_path titik dua http_request_duration_seconds titik dua p99_5m langsung tahu itu berasal dari http_request_duration_seconds.
# groups/http-slo.rules.yml
groups:
- name: http_slo_aggregations
interval: 30s
rules:
# level:metric:operations naming convention
- record: instance_path:http_requests:rate5m
expr: |
sum without (pod, replica) (
rate(http_requests_total[5m])
)
- record: job_path:http_request_duration_seconds:p99_5m
expr: |
histogram_quantile(
0.99,
sum without (pod, replica) (
rate(http_request_duration_seconds_bucket[5m])
)
)
- record: job:http_errors:ratio_rate5m
expr: |
sum without (pod, replica) (rate(http_requests_total{status=~"5.."}[5m]))
/
sum without (pod, replica) (rate(http_requests_total[5m]))
Selalu pasangkan agregasi dengan klausa without eksplisit yang mendaftarkan label yang Anda buang, bukan mendaftarkan label yang Anda pertahankan. Dengan begitu label baru yang ditambahkan ke metrik sumber di kemudian hari otomatis ikut terjaga. Dan jangan pernah merata-ratakan sebuah rasio atau merata-ratakan rata-rata antar instance, itu tidak valid secara statistik; agregasikan pembilang dan penyebut secara terpisah dulu, baru kemudian bagi.
Recording rules berjalan pada data yang sudah di-scrape dan disimpan Prometheus, artinya series mentah berkardinalitas tinggi tetap membebani ingestion, WAL, dan memori head block sebelum proses roll-up itu terjadi. Jika sebuah label benar-benar tak terbatas dan Anda tidak pernah membutuhkan detail mentahnya, perbaikan yang lebih baik adalah menghentikannya saat waktu scrape menggunakan metric_relabel_configs. Ini berjalan setelah target di-scrape tapi sebelum sampel ditulis ke TSDB, sehingga Anda bisa membuang seluruh metrik yang tidak lagi diperlukan, atau menulis ulang nilai label menjadi sekumpulan bucket kecil yang terbatas.
scrape_configs:
- job_name: checkout-service
metric_relabel_configs:
# drop the raw histogram once the recording rule covers it
- source_labels: [__name__]
regex: "http_request_duration_seconds_bucket"
action: drop
# collapse unbounded path label into a bounded set
- source_labels: [path]
regex: "/orders/[0-9]+"
target_label: path
replacement: "/orders/:id"
# cap total series this target can expose per scrape
sample_limit: 10000
Recording rules bukan solusi ajaib, dan penting untuk jujur tentang batasannya sebelum Anda mengandalkannya sebagai satu-satunya pertahanan. Karena beroperasi pada data yang sudah di-scrape dan ditulis ke TSDB, recording rules mengurangi biaya query dan dashboarding, tapi tidak melakukan apa pun untuk biaya ingestion dari series mentah. Agregasi streaming saat ingest, pada tool yang memang dibuat untuk tujuan itu, menghindari masalah ini dengan tidak pernah membiarkan series mentah berkardinalitas tinggi sampai ke storage jangka panjang sama sekali.
Jika kombinasinya tepat, hasilnya dramatis: dashboard yang tadinya butuh beberapa detik untuk merender dari histogram mentah kini tampil dalam hitungan milidetik dari series yang sudah pra-agregasi, dan Prometheus produksi berskala menengah yang sehat biasanya menetap di antara 100.000 hingga 2.000.000 total series aktif, jauh di bawah rentang di mana operator mulai melaporkan ketidakstabilan.
Mulailah dengan membuka halaman tsdb-status di server Anda sendiri dan catat lima pasangan label teratas berdasarkan jumlah series, daftar itulah urutan prioritas sesungguhnya, bukan tebakan. Untuk setiap masalah, tentukan apakah detail tersebut adalah sesuatu yang akan di-query manusia pada resolusi penuh saat insiden, yang berarti recording rule yang mengagregasinya adalah alat yang tepat, atau apakah itu benar-benar tak terbatas dan tidak pernah dibutuhkan pada resolusi penuh, yang berarti relabeling untuk membuangnya saat scrape adalah alat yang tepat. Kebanyakan perbaikan di produksi akhirnya memakai keduanya: relabel label yang benar-benar tak terbatas untuk menghentikan pendarahan, lalu tambahkan recording rules untuk agregasi yang paling sering di-query oleh dashboard dan alert Anda.
Sebelum mengirim grup recording rule baru ke produksi, validasi dengan perintah rule test milik promtool terhadap file fixture kecil. Ini menangkap kesalahan sintaks dan agregasi yang jelas-jelas salah dalam hitungan detik, jauh sebelum rule yang rusak diam-diam mengirimkan angka yang salah ke sistem alerting on-call Anda.
Masalah kardinalitas jarang terasa dramatis sampai saatnya tiba: memori yang merambat naik pelan-pelan selama berminggu-minggu, lalu OOM kill di saat yang paling buruk. Mengukur dulu dengan halaman tsdb-status, lalu menerapkan relabeling untuk menghentikan label tak terbatas di pintu masuk dan recording rules untuk membuat query yang benar-benar Anda jalankan jadi murah, mengubah rambatan pelan itu menjadi anggaran yang terkelola dan terpantau, bukan insiden yang berulang.