Membongkar Implementasi Klien Prometheus

Mengupas mekanisme pemantauan masa kini.

Mufid
Pujangga Teknologi
9 min readDec 8, 2019

--

Ilustrasi: mengelas besi
Ilustrasi: Daniel Wiadro (Unsplash)

Latar Belakang

Sebagai pengguna lama Statsd (via DataDog/DogStatsD), konsep Prometheus ini agak sedikit baru bagi saya. Saya penasaran, mencoba membandingkan konsepnya dengan yang saya pahami di statsd. Pada artikel kali ini, saya akan memaparkan bagaimana cara klien Prometheus bekerja, dari sudut pandang pemakai statsd. Ya, saya akan fokus hanya kepada kliennya, karena konsep pull metrics di Prometheus membuat implementasi klien lebih kompleks. Kita pun perlu sedikit memahami implementasinya karena akan sedikit banyak berimbas kepada kinerja program.

Berikut adalah kerangka dari artikel ini:

  • Bagaimana klien statsd bekerja? Apa saja konsep-konsep dalam memberikan pengukuran untuk pemantauan? Bagaimana konsep tersebut diimplementasi di statsd dan Prometheus?
  • Bagaimana konsep pull metrics dalam Prometheus?
  • Apa implikasi rancangan pull metrics Prometheus ke jenis pengukuran (metric types)?

Rancangan statsd

statsd adalah agregat pengukuran sederhana. Sebagai agregat, statsd dapat meneruskan hasil pengukuran ke berbagai backend. Misalnya, graphite. Bisa juga ke berkas teks biasa untuk keperluan debugging.

Sebagai agregat yang sederhana, statsd memungkinkan aplikasi untuk mengirimkan hasil pengukuran lewat protokol teks biasa. Oleh karena melalui protokol teks biasa, kita bahkan bisa mengirimkan hasil pengukuran dengan netcat biasa. Pada contoh di atas, setiap kali aplikasi dikunjungi, aplikasi akan mengirimkan paket UDP berisi:

visits:1|c

Sebagai agregat, statsd harus menjamin sistem asli dapat bekerja dengan baik. Jangan sampai ketika kita implementasi statsd, semua sistem jadi jatuh (down) karena klien terlalu sibuk mengirimkan paket-paket pengukuran. Penggunaan UDP berarti stabilitas diserahkan kepada jaringan. Jika jaringan terlalu sibuk, pengukuran bisa dengan mudah di-drop tanpa mekanisme tambahan.

Dapat kita simpulkan, statsd memiliki rancangan berikut:

  • dukungan banyak backend untuk berbagai keperluan
  • menerima hasil pengukuran lewat protokol teks biasa
  • mengutamakan protokol UDP untuk kestabilan

Desain Prometheus

Sekarang, mari kita lihat bagaimana desain Prometheus:

Prometheus cukup mewakili tiga poin mekanisme pengukuran yang telah kita sebut di paragraf sebelumnya. Mari kita bahas satu per satu.

  1. Prometheus mendukung banyak backend disesuaikan dengan kebutuhan. Contohnya: penyimpanan pengukuran dengan jangka waktu lama. Secara asal, Prometheus hanya menyimpan pengukuran beberapa waktu terakhir. Tidak bisa menyimpan 1 tahun terakhir. Jika Anda ingin menyimpan pengukuran lebih lama, Anda dapat menggunakan backend lain seperti Thanos atau Cortex.
  2. Sama seperti statsd, Prometheus memproses pengukuran dengan protokol teks biasa. Prometheus Client harus membuka port HTTP yang menyediakan semua pengukuran yang disajikan oleh aplikasi.
  3. Berbeda dengan statsd dimana klien harus mengirimkan data ke statsd. Prometheus akan mengambil data dari klien untuk kemudian disimpan dan diproses oleh Prometheus. Berbeda dengan statsd yang perlu mengirimkan pengukuran pada setiap kejadian, Prometheus hanya memerlukan ringkasannya saja.

Pada poin nomor (3), ada implikasinya: sebagian kalkulasi dilakukan di klien. Bukan di peladen. Hasilnya, implementasi klien menjadi jauh lebih kompleks. Tadinya, di statsd, kita hanya mengirimkan pengukuran dari setiap kejadian: kunjungan bertambah, penggunaan RAM sekarang 312 MB, ada permintaan web dengan waktu proses 610 milidetik. Klien Prometheus tidak menyimpan setiap kejadian. Klien Prometheus hanya menyimpan ringkasan kejadian pada saat ini.

Perbandingan arsitektur Prometheus dan statsd

Implementasi Klien

Untuk lebih memahami bagaimana klien Prometheus bekerja, saya telah membuat repository contoh. Repository ini berisi aplikasi contoh (bernama HitApp) dan Prometheus itu sendiri. Repository ini, secara default, akan menjalankan dua HitApp untuk mensimulasikan layanan dengan ketersediaan tinggi. HitApp sendiri adalah aplikasi yang hanya menghitung jumlah hit dan lamanya memproses request. Anda dapat mencoba mengkloning repository contoh dan menjalankan program di sana sesuai instruksi. Konfigurasi asal portnya adalah sebagai berikut:

  • HitApp instance 1 (A): 5000 (web), 8080 (prometheus)
  • HitApp instance 2 (B): 5100 (web), 8081 (prometheus)
  • Prometheus: 9090
Tampilan laman pengukuran aplikasi untuk ditarik oleh Prometheus
Kondisi asal saat program baru saja dimulai. Ini adalah laman statistik yang dilihat oleh Prometheus. Prometheus akan secara periodik menarik laman ini dan menyimpannya.

Ketika kita membuka port metrics untuk disedot oleh Prometheus, kita akan melihat metrics awal seperti di gambar samping. Terlihat di sana ada empat pengukuran dengan empat tipe pengukuran:

  • hitapp_processing_time_hg menunjukkan lamanya memproses sebuah permintaan web. Pengukuran ini bertipe histogram. Dipisah berdasarkan bucket.
  • hitapp_processing_time_sm menunjukkan lamanya proses memproses sebuah permintaan web juga. Sama seperti hitapp_processing_time_hg. Bedanya: pengukuran ini bertipe summary. Dipisahkan berdasarkan persentil.
  • hitapp_requests menunjukkan jumlah permintaan web yang masuk sejak instance hidup. Setiap kali ada sebuah request yang masuk, jumlah akan bertambah satu. Pengukuran ini bertipe counter.
  • hitapp_requests_concurrent menunjukkan jumlah permintaan web yang sedang ditangani secara bersamaan. Pengukuran ini bertipe gauge.

Mari kita lihat bagaimana implementasi masing-masing tipe pengukuran ini. Untuk penjelasan selanjutnya, saya akan mengambil implementasi dari repositori klien Java yang resmi dari Prometheus (prometheus/client_java di Github). Ini karena klien Java memiliki implementasi yang paling komprehensif.

Gauge

Gauge mengukur nilai saat ini yang bisa naik atau turun. Misalnya: Jumlah memori (dalam byte) yang terpakai, CPU core yang diutilisasi, kecepatan internet saat ini. Dalam kasus HitApp, tipe pengukuran Gauge kita gunakan untuk memantau jumlah koneksi konkuren. Untuk menyajikan data Gauge ini, cukup buka laman dasbor Prometheus, kemudian mengetikkan nama pengukurannya (hitapp_requests_concurrent).

Nilai tersebut ditampilkan berdasarkan metrik Prometheus yang diambil pada saat itu. Anda dapat mencocokannya dengan melihat nilai akhir, kemudian membuka localhost:8081 atau localhost:8080.

Implementasi Gauge sendiri cukup sederhana. Kita hanya perlu memastikan proses inc dan dec dilakukan secara atomik. Pada banyak kepustakaan, ini bisa dicapai dengan menggunakan mutex. Akan tetapi, pada pengumpulan metrik untuk proses yang banyak, mutex bisa tidak efisien. Pada prometheus/client_java, proses inc dan dec dilakukan lewat concurrency DoubleAdder.

Counter

Counter merupakan tipe pengukuran yang jumlahnya terus meningkat sejak aplikasi dihidupkan. Setiap ada data masuk, kita menambah jumlahnya. Data berikut bisa masuk ke dalam counter:

  • jumlah pengunjung mall sejak mall pertama kali didirikan
  • jumlah barang yang diimport sejak Indonesia merdeka
  • jumlah kucing yang pernah masuk ke rumah Anda sejak membeli rumah

Pada HitApp, kita menghitung jumlah request yang terjadi sejak aplikasi dihidupkan. Pada dasbor Prometheus, kita bisa melihat nilainya dari waktu ke waktu dengan cukup mengetikkan nama pengukurannya:

Karena Prometheus secara periodik mengambil nilai ini, sebenarnya kita memiliki dua dimensi data: waktu dan jumlah permintaan. Dengan dua dimensi ini, kita bisa mengetahui jumlah per satuan waktu. Misalnya, berapa jumlah permintaan setiap menitnya? Cukup lihat perbedaan nilai saat ini dengan nilai 1 menit yang lalu. Di Prometheus, kita bisa menghitung ini secara otomatis dengan kueri increase:

increase akan menghasilkan nilai perbedaan jumlah pada setiap interval waktunya. Gunakan increase(…[1m]) untuk melihat banyaknya request per menitnya. Atau, increase(…[5m]) untuk melihat banyaknya permintaan per 5 menit. Jika Anda ingin mengetahui banyaknya permintaan per detiknya, gunakan kueri rate atau irate.

Terlihat sangat bervariasi. Ada masa dimana program mendapatkan 500 permintaan per menit. Ada masa dimana program mendapatkan 100 permintaan per menit. Akan tetapi, program konsisten mendapatkan sekitar 600 permintaan per menit pada jam 00:00 GMT (atau sekitar jam 07:00 pagi Waktu Indonesia Barat).

Sama seperti Gauge, implementasi Counter cukup sederhana. Lebih mudah lagi — Counter hanya menambah, tidak akan berkurang. Pada banyak kepustakaan, penguncian nilai lewat mutex bisa dilakukan. Implementasi di prometheus/client_java sendiri menggunakan DoubleAdder.

Mari tebak-tebakan: apa yang terjadi apabila aplikasi di-restart? Tepat sekali: counter akan kembali mulai dari nol. Menariknya, dengan ekspresi Prometheus seperti increase, ini sudah ditangani secara otomatis. Prometheus akan menyesuaikan perhitungan di masa counternya dimulai ulang sehingga hasilnya tetap akurat.

Menurut Saya, Counter ini adalah tipe pengukuran yang paling kompleks. Ini karena kesederhanaan penggunaannya: cukup gunakan increase. Jika Anda masih penasaran bagaimana rancangan rekayasanya dibuat, ada presentasi yang cukup menarik langsung dari pembuat Prometheus itu sendiri, spesifik hanya membahas tentang tipe pengukuran Counter!

Summary dan Histogram

Kedua tipe ini memiliki kegunaan yang sama: untuk mengetahui distribusi dari kejadian. Pada kasus sesungguhnya, Anda hanya perlu memilih salah satu untuk setiap pengukuran: Summary atau Histogram. Kita menggunakan dua tipe pengukuran yang berbeda hanya untuk pembelajaran saja dan mengetahui perbedaannya.

Dalam kasus HitApp, Summary dan Histogram digunakan untuk mengukur lama memproses permintaan. Perbedaan dari Summary dan Histogram dapat langsung terlihat pada gambar terkait. Histogram membagi jumlah kejadian menjadi beberapa buket. le berarti less than or equal to. Sebagai contoh, hitapp_processing_time_hg_bucket{le=”0.005",} 525.0 berarti ada 525 permintaan dengan durasi di bawah atau sama dengan 0,005 detik. hitapp_processing_time_hg_bucket{le=”0.25",} 29984.0 berarti ada 29.884 permintaan dengan durasi di bawah atau sama dengan 0,25 detik sejak aplikasi dihidupkan. Tidak ada permintaan dengan lama proses di atas 7,5 detik karena nilai le=”7,5" sama dengan nilai le=”10.0", yaitu di 119536.

Untuk mendapatkan nilai kuantilnya, kita bisa menghitung dengan menjumlahkan frekuensi setiap buket, dan mendapatkan indeksnya. Misalnya, jika kita menghitung p99, berarti kita perlu mencari indeks 0,99 * count -> 0,99 * 119536= 118340. Artinya p99 berkisar antara 5 detik (le=5.0 berjumlah 107482) dan 7,5 detik (le=7.5 berjumlah 119536).

p99 untuk hitapp_processing_time_hg

Mirip dengan pelajaran SMA, ya? Tentu saja kita tak perlu pusing-pusing menghitung kuantil secara manual seperti SMA dulu. Perhitungan kuantil dari Histogram bisa dilakukan dengan mudah dengan ekspresi PromQL. Hasilnya dapat Anda lihat pada gambar di atas.

Pada perhitungan Summary, klien yang melakukan perhitungan kuantil. Berhubung klien yang melakukannya, tentu saja klien performanya akan menjadi lebih lambat. Anda perlu mengetahui implementasi dari kepustakaan Anda. Pada klien resmi Java, kuantil dilakukan dengan metode CKMS. Sangat efisien. Pada klien discourse/prometheus_exporter, perhitungan dilakukan dengan metode naive: mengurutkan datanya terlebih dahulu, kemudian mendapatkan nilai indeks terkaitnya.

Untuk perhitungan rata-rata, keduanya sama. Keduanya memiliki count dan sum untuk menghitung mean atau rata-rata. Rata-rata dapat dihitung dengan membagi sum dengan count.

Kapan perlu menggunakan Summary? Kapan perlu menggunakan Histogram? Jika Anda membutuhkan buket, gunakan Summary. Contohnya, jika Anda ingin menghitung Apdex dengan T=0,5 detik, Anda hanya mempedulikan berapa banyak durasi proses permintaan yang lebih lambat dari 0,5 detik. Anda tidak membutuhkan berapa persentilnya. Jika Anda membutuhkan nilai kuantil spesifik dan klien Anda mendukung perhitungan kuantil yang efisien, gunakan Summary.

Jika tidak memungkinkan menggunakan Summary dan Anda ingin tetap menghitung kuantil dengan Histogram, Anda bisa menggunakan recording rules. Prometheus akan melakukan perhitungannya dan menyimpannya ke dalam tembolok.

Perbandingan Implementasi Klien Prometheus

Tabel perbandingan 4 kepustakaan Prometheus
Perbandingan kepustakaan antara klien Prometheus per November 2019

Karena perhitungan yang dilakukan di klien, implementasi bisa berbeda-beda antar bahasa dan kepustakaan. Hasilnya, tidak semua fitur Prometheus bisa dinikmati di semua kepustakaan. Performanya antar kepustakaan pun bisa berbeda-beda. Berbeda dengan statsd: klien hanya mengirimkan pengukuran dari setiap kejadian. Tidak perlu memproses datanya.

Saya membandingkan empat kepustakaan klien Prometheus. Hampir semua klien mendukung tipe pengukuran Counter, Gauge, dan Histogram. Tidak mengherankan karena cara memperoleh nilai pengukurannya mudah. Hanya perlu menambah nilai ke masing-masing hasil pengukuran sebelumnya. Perbedaan terlihat pada kepustakaan masing-masing bahasa karena dukungan antarmuka concurrency yang dimiliki. NodeJS menjamin setiap perintah dilakukan secara atomik karena NodeJS single-threaded. Di klien resmi Ruby, menambahkan nilai baru ke hasil pengukuran dilakukan dengan mutex biasa.

Perbedaan yang lebih kontras lagi terlihat bagaimana masing-masing kepustakaan memproses pengukuran bertipe summary. Ini karena summary langsung menampilkan hasil akhir kuantilnya — berbeda dengan histogram yang mana kuantil dikalkulasi di Prometheus, meski akurasinya boleh lebih rendah.

  • Perhitungan kuantil kepustakaan resmi prometheus/client_java (Java) menggunakan metode CKMS.
  • Perhitungan kuantil siimon/prom-client (NodeJS) menggunakan metode T-Digest.
  • Kepustakaan resmi prometheus/client_ruby (Ruby) tidak memiliki perhitungan kuantil. Hanya mean (rata-rata) saja.
  • Kepustakaan discourse/prometheus_exporter memiliki kalkulasi kuantil. Akan tetapi perhitungannya terlalu naive dan lambat. Kepustakaan tersebut mengurutkan satuan kejadian kemudian mengambil kuantilnya.

Kesimpulan

Prometheus sangat menarik. Jika Anda menggunakan Kubernetes, akan sangat mudah untuk mengintegrasikan Prometheus ke dalam sistem pemantauan Anda. Prometheus adalah mekanisme pemantauan layanan masa kini. Akan tetapi, karena perlu ada sedikit kecerdasan di klien Prometheus, Anda perlu memeriksa implementasi klien pada kepustakaan dan bahasa yang Anda pakai. Pastikan kebutuhan Anda memang sesuai dengan dukungan kepustakaan yang Anda pergunakan.

P.S. Jika teman-teman menyukai artikel semacam ini, silakan subscribe ke newsletter kita dan dapatkan notifikasi artikel terbaru langsung di inbox kamu!

--

--