Bali, Indonesia
Insights

Jebakan Direct Reclaim di Cgroups V2: Mengukur Lonjakan Tail Latency Akibat Throttling memory.high

October 14, 2026
0:00
Banyak tim infrastruktur bermigrasi ke cgroups v2 dengan satu harapan besar: membuang mimpi buruk OOM killer mendadak yang selama bertahun-tahun menghantui cgroups v1. Fitur memory.high digadang-gadang sebagai penyelamat karena menawarkan mekanisme throttling bertahap alih-alih eksekusi mati proses lewat SIGKILL. Premisnya terdengar elegan: biarkan kernel memperlambat proses secara terkontrol ketika konsumsi memori melewati ambang batas aman, memberi waktu bagi beban kerja untuk mereda. Namun di lingkungan produksi dengan beban transaksi tinggi, mekanisme proteksi ini kerap berubah menjadi bumerang. Sebuah investigasi telemetri yang sempat dibedah oleh Netdata menyoroti anomali di mana beban CPU tampak rendah, penggunaan memori belum menyentuh batas absolut, namun p99 tail latency sistem melonjak ratusan milidetik secara acak. Masalah ini bukan disebabkan oleh garbage collector pause atau kemacetan jaringan, melainkan jebakan direct reclaim yang dieksekusi secara sinkron oleh kernel Linux tepat di jalur kritis eksekusi aplikasi.
Untuk memahami mengapa tail latency hancur, kita harus membedah apa yang terjadi di level kernel saat sebuah proses melewati ambang memory.high. Dalam kondisi normal, reklamasi memori ditangani di latar belakang secara asinkron oleh thread kernel kswapd. Proses aplikasi hanya meminta alokasi halaman (page allocation), kernel mengambilnya dari free list, dan aplikasi langsung melanjutkan tugasnya tanpa jeda. Namun, begitu cgroup melewati batas memory.high, kernel mengeksekusi fungsi mem_cgroup_handle_over_high(). Di titik ini, strategi kernel bergeser dari asinkron menjadi pemaksaan sinkron. Thread aplikasi yang sedang memanggil alokasi memori—bisa berupa worker thread HTTP, koneksi database, atau runtime goroutine—dipaksa berhenti menjalankan logika bisnisnya. Thread tersebut dibajak oleh kernel untuk melakukan tugas sanitasi: mencari halaman memori yang tidak aktif (inactive file/anon pages), melakukan pembilasan dirty pages ke disk, dan membebaskannya secara mandiri sebelum alokasi diizinkan kembali. Tidak berhenti di situ, kernel kemudian menghitung penalti penundaan (penalty delay) proporsional berdasarkan seberapa jauh batas memory.high dilanggar. Thread tersebut dipaksa tidur via schedule_timeout_killable(). Jika Anda menjalankan arsitektur berbasis event-loop atau thread pool yang ramping, satu thread yang terjebak direct reclaim selama 15 milidetik sudah cukup untuk menumpuk antrean ribuan in-flight requests di belakangnya.
Alasan mengapa jebakan ini sangat berbahaya adalah karena metrik pemantauan konvensional sering kali buta terhadap fenomena tersebut. Menatap grafik RSS (Resident Set Size) atau persentase pemakaian RAM di dashboard tidak akan menunjukkan masalah apa pun—memori tampak stabil tepat di batas yang Anda konfigurasikan. Penggunaan CPU bahkan bisa terlihat menurun drastis karena worker threads terjebak dalam status uninterruptible sleep (D state) menunggu I/O disk saat mengusir page cache. Satu-satunya instrumen observabilitas yang mampu mengungkap degradasi ini secara presisi adalah Pressure Stall Information (PSI). Metrik PSI merekam fraksi waktu dinding (wall-clock time) di mana thread sistem kehilangan siklus produktif murni akibat kelaparan sumber daya memori:
BASH
# Memeriksa pressure memori langsung dari cgroup target
cat /sys/fs/cgroup/system.slice/my-service.service/memory.pressure
some avg10=18.42 avg60=7.11 avg300=2.04 total=1582910
full avg10=4.12 avg60=1.20 avg300=0.31 total=421098
Metrik some menunjukkan persentase waktu di mana setidaknya satu thread terhenti menunggu reklamasi memori, sedangkan full menandakan semua thread non-idle dalam cgroup tersebut lumpuh total. Angka some avg10=18.42 adalah alarm keras: hampir seperlima dari kapasitas komputasi proses terbuang sia-sia hanya untuk mengurusi pembersihan memori secara sinkron. Untuk memverifikasi apakah penalti berasal dari direct reclaim atau aktivitas background biasa, bedah berkas memory.stat pada cgroup yang bersangkutan:
BASH
grep -E 'pgscan_direct|pgscan_kswapd|pgsteal_direct' /sys/fs/cgroup/system.slice/my-service.service/memory.stat
Jika nilai pgscan_direct meningkat pesat seiring dengan melonjaknya latensi, sistem Anda sedang mengalami pendarahan performa internal. Halaman memori tidak lagi dipersiapkan oleh kswapd, melainkan dirampas secara paksa oleh thread aplikasi Anda sendiri.
Kesalahan konfigurasi paling umum di lapangan adalah menetapkan memory.high terlalu dekat dengan batas absolut memory.max, atau menetapkannya tanpa memberi ruang bagi memory.low. Cgroups v2 dirancang untuk bekerja optimal menggunakan gradasi hierarki tiga lapis. memory.low berfungsi sebagai batas perlindungan terbaik (best-effort protection). Memori di bawah batas ini tidak akan direklamasi oleh kernel kecuali seluruh sistem kehabisan memori secara global. Di atas memory.low, kswapd harus dibiarkan bekerja membersihkan halaman memori yang dingin secara bertahap. Ambang batas memory.high seharusnya diposisikan dengan headroom yang cukup longgar di bawah memory.max—umumnya sekitar 15% hingga 20% dari total alokasi, bergantung pada kecepatan alokasi (allocation rate) aplikasi Anda. Jarak ini memberi ruang penyangga krusial agar lonjakan alokasi mendadak (burst allocation) tidak langsung menabrak dinding penalti schedule_timeout. Jika aplikasi Anda memiliki pola kerja berbasis in-memory cache agresif yang dipadukan dengan mutasi data masif, bergantung sepenuhnya pada memory.high tanpa menyetel dirty writeback ratio di tingkat OS justru memperparah keadaan. Begitu direct reclaim menyentuh halaman memori kotor, thread aplikasi akan tertahan latensi I/O storage untuk page flush. Menjaga agar latensi p99 tetap berada di batas sub-milidetik menuntut kita memandang alokasi memori bukan sekadar angka kapasitas statis, melainkan dinamika aliran halaman yang harus dijaga agar selalu asinkron.