Linux

Cgroup v2: Bedanya memory.max dan memory.high Saat Service Kena OOM

Cgroup v2: Bedanya memory.max dan memory.high Saat Service Kena OOM

memory.max adalah batas keras: begitu pemakaian memori sebuah cgroup menyentuh nilai itu dan tidak bisa diturunkan lagi, OOM killer dijalankan di dalam cgroup tersebut. memory.high adalah batas lunak: melewatinya hanya membuat proses di-throttle dan ditekan reclaim, dan dokumentasi kernel menegaskan batas ini tidak pernah memicu OOM killer. Kedua berkas itu hidup di antarmuka cgroup v2, bersama memory.swap.max dan memory.oom.group, dan bisa dipasang dari unit systemd lewat MemoryMax= serta MemoryHigh=.

Bagi tim yang menjalankan beberapa service di satu VPS kecil, selisih dua direktif ini menentukan apakah satu proses bermasalah akan dibunuh sendirian atau menyeret seluruh host ke kondisi tidak responsif. Artikel ini membedah mekanismenya, cara memasangnya lewat systemd, dan cara membuktikan limit benar-benar bekerja sebelum insiden terjadi.

TL;DR

  • memory.max adalah batas keras: melewatinya memicu OOM killer di dalam cgroup, bukan di host.
  • memory.high tidak membunuh proses apa pun; pemakaian di atasnya di-throttle dan ditekan reclaim.
  • memory.swap.max membatasi swap per cgroup, terpisah dari batas RAM.
  • systemd mengekspos keduanya sebagai MemoryMax= dan MemoryHigh=, dengan MemoryHigh= tersedia sejak systemd versi 231.
  • Bukti limit bekerja ada di memory.current dan memory.events, bukan di asumsi konfigurasi.

Apa perbedaan memory.max dan memory.high?

memory.max adalah batas keras dan mekanisme utama untuk membatasi pemakaian memori sebuah cgroup. Dokumentasi resmi kernel menyatakan bahwa jika pemakaian memori mencapai batas ini dan tidak dapat dikurangi, OOM killer dipanggil di dalam cgroup. Sebaliknya memory.high adalah batas throttle: jika pemakaian melewati batas atas, proses di cgroup tersebut di-throttle dan ditempatkan di bawah tekanan reclaim yang berat, dan melewati batas ini tidak pernah memicu OOM killer.

Perbedaan itu terlihat jelas pada perilaku saat beban naik. Dengan memory.max, aplikasi yang meminta memori melebihi sisa kuota akan menerima kegagalan atau dibunuh. Dengan memory.high, aplikasi tetap hidup tetapi melambat, karena alokasi baru memaksa kernel merebut kembali halaman yang sudah dipakai. Untuk service produksi, kombinasi keduanya biasanya lebih aman daripada salah satu saja: high menahan lonjakan lebih dulu, max menjadi jaring terakhir.

Ada satu detail yang sering terlewat: nilai bawaan kedua berkas adalah "max", artinya tanpa batas. Service yang tidak pernah menyebutkan MemoryMax= dan MemoryHigh= akan memakai memori sebanyak yang tersedia di host, dan kompetisi antar service diselesaikan oleh OOM killer global yang memilih korban berdasarkan skor, bukan berdasarkan niat operator.

Rujukan perintah dan semantik lengkap ada di dokumentasi cgroup v2 di docs.kernel.org. Di sana juga dijelaskan memory.swap.max, yang membatasi swap per cgroup, dan memory.oom.group yang membuat OOM killer membunuh seluruh proses dalam cgroup sebagai satu kesatuan.

Kapan OOM killer benar-benar dipanggil?

OOM killer di dalam cgroup dipanggil ketika pemakaian memori mencapai memory.max dan pemakaian itu tidak bisa diturunkan lagi. Dokumentasi kernel juga mencatat bahwa dalam kondisi tertentu pemakaian bisa melewati batas untuk sementara, dan tidak semua jenis alokasi memicu OOM killer; sebagian mengembalikan kegagalan ke pemanggil alih-alih membunuh proses.

Karena itu, melihat proses mati dengan sinyal 9 bukan bukti otomatis bahwa memory.max bekerja. Indikator yang lebih dapat dipercaya adalah penghitung di memory.events: nilai oom dan oom_kill bertambah setiap kali OOM killer terlibat, sedangkan nilai high menunjukkan berapa kali cgroup melewati batas throttle. Kalau high terus bertambah tetapi oom_kill nol, artinya limit sedang bekerja sesuai rancangan: aplikasi melambat, bukan mati.

Untuk host dengan beberapa unit systemd yang berbagi RAM, ada baiknya juga memahami bahwa OOM killer global tetap bisa memilih korban di luar cgroup yang salah. Batas per unit mengurangi kemungkinan itu, tetapi tidak menghapusnya, terutama bila total MemoryMax semua unit lebih besar dari RAM fisik ditambah swap.

Bagaimana cara memasang limit memori lewat systemd?

Pasang lewat direktif di berkas drop-in, bukan dengan mengedit unit bawaan paket. Buat berkas di /etc/systemd/system/nama.service.d/limit.conf, isi dengan MemoryMax=, MemoryHigh=, dan MemorySwapMax=, lalu jalankan daemon-reload dan restart unit tersebut. Pola drop-in menjaga konfigurasi tetap bertahan saat paket diperbarui.

Contoh isi yang membatasi service pada 768 MB dengan throttle di 640 MB:

[Service]
MemoryHigh=640M
MemoryMax=768M
MemorySwapMax=0
TasksMax=256

Direktif MemoryMax=, MemoryHigh=, MemorySwapMax=, TasksMax=, CPUQuota=, dan CPUWeight= semuanya didokumentasikan di halaman systemd.resource-control. Halaman itu juga mencatat bahwa MemoryHigh= dan StartupMemoryHigh= tersedia sejak systemd versi 231, jadi host dengan systemd jauh lebih lama tidak akan mengenali direktif tersebut dan hanya akan mengabaikannya tanpa error yang jelas.

Setelah limit dipasang, verifikasi dari dua sisi. Dari systemd, jalankan systemctl show nama.service -p MemoryMax -p MemoryHigh. Dari kernel, baca nilai efektif di /sys/fs/cgroup/system.slice/nama.service/memory.max dan memory.high. Kalau dua sumber itu tidak cocok, biasanya ada drop-in lain yang menimpa nilai, dan urutan pembacaan berkas drop-in mengikuti nama berkasnya.

Berapa nilai yang masuk akal untuk VPS RAM kecil?

Belum ada angka tunggal yang benar untuk semua service, karena kebutuhan memori bergantung pada runtime dan beban. Yang bisa dipertanggungjawabkan adalah metode: ukur pemakaian puncak dulu, lalu pasang MemoryHigh= sedikit di atas puncak normal dan MemoryMax= sekitar 20 sampai 30 persen di atasnya. Angka 20 sampai 30 persen itu pilihan kebijakan, bukan temuan penelitian, dan sebaiknya dicatat sebagai keputusan tim.

Di host kecil, langkah yang lebih penting daripada memilih angka adalah memastikan jumlah seluruh batas tidak melebihi kapasitas. Kalau tiga service masing-masing dipasangi MemoryMax=1G pada host 2 GB tanpa swap, batas itu tidak menjamin apa pun saat ketiganya naik bersamaan: host tetap akan kehabisan memori dan OOM killer global yang memutuskan.

Tabel berikut merangkum tiga berkas yang paling sering dipakai, berdasarkan semantik di dokumentasi kernel:

BerkasPerilaku saat terlampauiCocok untuk
memory.maxOOM killer dipanggil di dalam cgroupBatas keras per service, jaring terakhir
memory.highThrottle dan reclaim berat, tanpa OOMMenahan lonjakan agar aplikasi melambat, bukan mati
memory.swap.maxMembatasi swap yang boleh dipakai cgroupMencegah satu service menghabiskan swap host

Bagaimana membuktikan limit bekerja tanpa menunggu insiden?

Uji dengan beban buatan di unit yang terpisah, bukan di service produksi. Buat unit sementara dengan MemoryMax=64M, jalankan proses yang mengalokasikan memori bertahap, lalu amati memory.current dan memory.events di direktori cgroup-nya. Kalau oom_kill bertambah tepat saat memory.current menyentuh memory.max, mekanismenya terbukti; kalau proses mati tanpa kenaikan oom_kill, kemungkinan besar yang membunuh adalah OOM killer global atau batas lain di lapisan atas.

Untuk audit menyeluruh, systemd menyediakan alat bantu: systemd-analyze punya subperintah security yang menghitung exposure level sebuah unit pada skala 0.0 sampai 10.0, dan subperintah itu membaca langsung direktif sandbox yang aktif pada unit tersebut. Skor rendah bukan jaminan bebas masalah, tetapi skor tinggi hampir selalu menandakan direktif pembatas belum dipasang.

Satu kebiasaan yang layak diadopsi: simpan keluaran systemctl show dan isi memory.events ke dalam log berkala. Ketika service melambat tanpa pesan error, data itu yang membedakan antara masalah memori, masalah CPU, dan masalah I/O tanpa perlu menebak.

Apa efek memory.swap.max=0 pada service?

memory.swap.max=0 berarti cgroup tersebut tidak diizinkan memakai swap sama sekali. Konsekuensinya bukan "lebih cepat", melainkan "lebih cepat terlihat": begitu RAM yang dialokasikan habis, tidak ada ruang cadangan di disk, sehingga pemakaian langsung menyentuh memory.max dan OOM killer di dalam cgroup terlibat. Untuk service yang punya batas waktu respons ketat, keputusan ini kadang disengaja; untuk service yang boleh melambat, nilai bukan nol biasanya lebih ramah.

Kebalikannya juga punya jebakan. Membiarkan swap tanpa batas pada host kecil membuat satu proses bisa mendorong halaman proses lain ke disk, dan gejala yang muncul bukan OOM melainkan kelambatan menyeluruh yang sulit dilacak. Dokumentasi kernel menyediakan memory.swap.max justru untuk memisahkan dua kegagalan itu: batas RAM berbicara soal kematian proses, batas swap berbicara soal seberapa jauh host boleh memperlambat diri sendiri.

Kesalahan umum saat memasang limit memori

Pola kegagalan yang paling sering muncul bukan pada nilai angkanya, melainkan pada cara limit dipasang dan diverifikasi.

  • Memasang MemoryMax= di unit bawaan paket, lalu kehilangan konfigurasi setiap kali paket diperbarui. Drop-in di direktori .d menyelesaikan ini.
  • Menjumlahkan batas semua unit melebihi RAM fisik ditambah swap, sehingga batas per unit kehilangan makna saat beban bersamaan naik.
  • Mengandalkan MemoryHigh= di host dengan systemd lama, padahal direktif itu baru dikenali sejak versi 231.
  • Menyimpulkan penyebab kematian proses dari sinyal, tanpa membaca memory.events untuk memastikan OOM killer memang terlibat.
  • Menguji limit di service produksi, alih-alih di unit sementara dengan nilai kecil yang mudah diamati.

Semua butir di atas bisa diperiksa dengan perintah yang sama: baca nilai efektif dari systemd, baca nilai efektif dari kernel, lalu cocokkan. Konfigurasi yang tidak diverifikasi dari kedua sisi hanyalah niat, bukan batas yang berlaku.

FAQ

Apakah memory.high bisa membunuh proses?

Tidak. Dokumentasi kernel menyatakan melewati memory.high tidak pernah memicu OOM killer; proses hanya di-throttle dan ditekan reclaim. Pembunuhan proses terjadi pada memory.max, atau oleh OOM killer global di host ketika memori benar-benar habis.

Apakah nilai bawaan memory.max tanpa batas?

Ya, nilai bawaannya "max" untuk cgroup non-root, yang berarti tanpa batas keras. Service yang tidak menyebut MemoryMax= akan memakai memori sebanyak yang tersedia, dan kompetisi antar service diselesaikan OOM killer global, bukan oleh kebijakan operator.

Mengapa direktif MemoryHigh= diabaikan di host lama?

Karena direktif itu baru tersedia sejak systemd versi 231. Pada versi lebih lama, direktif yang tidak dikenal tidak selalu menghasilkan error yang jelas, sehingga konfigurasi terlihat terpasang padahal tidak berpengaruh. Periksa dengan systemctl --version sebelum mengandalkannya.

Apakah memory.max menjamin service tidak mengganggu host?

Tidak sepenuhnya. memory.max membatasi satu cgroup, tetapi host masih bisa kehabisan memori bila jumlah seluruh batas melebihi kapasitas, atau bila proses di luar unit tersebut memakai memori tanpa batas. Batas per unit mengurangi risiko, bukan menghapusnya.

Di mana melihat jumlah proses yang dibunuh karena OOM?

Di berkas memory.events dalam direktori cgroup unit terkait. Nilai oom_kill bertambah setiap kali OOM killer membunuh proses di cgroup itu, sedangkan nilai oom mencatat kejadian OOM yang melibatkan cgroup tersebut.

💬 Komentar (0)

Belum ada komentar. Jadilah yang pertama! 💬

Komentar yang memuat tautan, bahasa asing, atau aksara non-Latin ditahan dulu buat ditinjau.