Memori dan eviksi

maxmemory, kebijakan eviksi, MEMORY USAGE, dan kesadaran biaya memori per key.

Daftar isi

Topik ini membahas sisi memori dari pengelolaan key, yaitu batas maxmemory, delapan kebijakan eviksi (eviction) yang menentukan key mana yang dikorbankan ketika batas tercapai, dan pengukuran biaya memori per key dengan MEMORY USAGE. Pembahasan ditutup dengan percobaan yang memicu penolakan penulisan pada kebijakan noeviction, eviksi nyata pada kebijakan allkeys-lru, serta urutan korban pada kebijakan volatile-ttl. Seluruh contoh berjalan pada server khusus di port 6382 dengan batas memori yang diturunkan di tengah percobaan, sehingga pengujian eviksi tidak mengganggu keyspace pada server lain dan keluarannya dapat direproduksi ulang.

Batas maxmemory dan keputusan eviksi#

Arahan konfigurasi maxmemory menetapkan batas atas penggunaan memori dataset pada satu server, dinyatakan dalam satuan byte. Nilai bawaannya adalah 0, yang berarti server tidak membatasi dataset dan hanya bergantung pada memori fisik yang tersedia. Nilai yang dibandingkan dengan batas ini adalah used_memory pada INFO memory, yaitu akumulasi alokasi untuk data, buffer klien, dan metadata internal. Ukuran proses yang terlihat oleh sistem operasi (used_memory_rss) biasanya lebih besar karena fragmentasi, sehingga perbandingan terhadap maxmemory tetap menggunakan used_memory.

Setiap perintah tulis diuji terhadap batas tersebut sebelum dieksekusi. Apabila penggunaan memori masih di bawah maxmemory, perintah langsung dijalankan. Apabila batas sudah tercapai, kebijakan eviksi yang terpasang menentukan langkah berikutnya, yaitu mengeluarkan sejumlah key untuk mengosongkan ruang atau menolak perintah dengan pesan error. Pengeluaran key inilah yang disebut eviksi, sedangkan kebijakan noeviction memilih menolak penulisan tanpa menghapus satu key pun. Diagram berikut merangkum alur keputusan tersebut untuk setiap perintah tulis.

Perintah tulismemori <maxmemory?Key ditulisTerapkan kebijakan eviksinoevictiontolak, error OOMHapus key korbanyatidakselain noeviction
Alur keputusan eviksi saat penggunaan memori mencapai maxmemory

Lab pada topik ini menjalankan server khusus di port 6382 dengan direktori data di /tmp/lab-evict, tanpa persistensi, batas awal 200 MB, dan kebijakan awal allkeys-lru. Blok berikut menyalakan server lab, termasuk menghentikan sisa server dari sesi sebelumnya bila ada. Baca keluaran PING sebagai tanda server siap menerima perintah.

bash
# hentikan sisa server dari sesi sebelumnya, bila ada
redis-cli -p 6382 shutdown nosave 2>/dev/null || true
mkdir -p /tmp/lab-evict
redis-server --port 6382 --bind 127.0.0.1 --daemonize yes --dir /tmp/lab-evict --save "" --maxmemory 200mb --maxmemory-policy allkeys-lru --protected-mode no --logfile /tmp/lab-evict-server.log
sleep 1
redis-cli -p 6382 PING
text Keluaran
PONG

Baca kedua arahan konfigurasi yang menentukan perilaku percobaan, yaitu batas memori dan kebijakan eviksi awal. Nilai 209715200 setara dengan 200 MB dalam satuan byte.

bash
# baca batas memori yang terpasang saat server dinyalakan
redis-cli -p 6382 CONFIG GET maxmemory
# baca kebijakan eviksi awal
redis-cli -p 6382 CONFIG GET maxmemory-policy
text Keluaran
maxmemory
209715200
maxmemory-policy
allkeys-lru

Kebijakan eviksi yang tersedia#

Kebijakan eviksi terbagi menjadi dua kelompok berdasarkan cakupan kandidatnya. Kelompok allkeys memperlakukan seluruh key di keyspace sebagai kandidat, sedangkan kelompok volatile hanya memperlakukan key yang memiliki masa hidup. Di luar keduanya, noeviction tidak mengeluarkan key apa pun dan menolak setiap perintah tulis ketika memori penuh. Tabel berikut merangkum kedelapan kebijakan beserta aturan pemilihan korbannya.

KebijakanKandidat eviksiAturan pemilihan korban
noevictiontidak adapenulisan ditolak dengan error
allkeys-lruseluruh keykey yang paling lama tidak diakses
allkeys-lfuseluruh keykey dengan frekuensi akses terendah
allkeys-randomseluruh keykey acak
volatile-lrukey bermasa hidupkey yang paling lama tidak diakses
volatile-lfukey bermasa hidupkey dengan frekuensi akses terendah
volatile-randomkey bermasa hidupkey acak
volatile-ttlkey bermasa hidupkey dengan sisa masa hidup terpendek

LRU adalah singkatan dari least recently used, yaitu key yang paling lama tidak diakses menjadi kandidat utama, sedangkan LFU, least frequently used, memilih key dengan frekuensi akses paling rendah. Keduanya tidak dihitung secara presisi atas seluruh keyspace karena biayanya mahal. Redis menerapkan versi aproksimasi dengan pengambilan sampel (sampling), yaitu mengambil sejumlah key secara acak sebanyak maxmemory-samples, lalu mengeluarkan kandidat terbaik dari sampel tersebut. Nilai bawaan jumlah sampel dapat dibaca pada blok berikut.

bash
# ganti kebijakan eviksi saat server berjalan
redis-cli -p 6382 CONFIG SET maxmemory-policy allkeys-lfu
# pastikan kebijakan baru sudah aktif
redis-cli -p 6382 CONFIG GET maxmemory-policy
# kembalikan kebijakan awal
redis-cli -p 6382 CONFIG SET maxmemory-policy allkeys-lru
# baca nilai bawaan jumlah sampel eviksi
redis-cli -p 6382 CONFIG GET maxmemory-samples
text Keluaran
OK
maxmemory-policy
allkeys-lfu
OK
maxmemory-samples
5

Biaya memori per key dengan MEMORY USAGE#

Perintah MEMORY USAGE melaporkan estimasi byte yang digunakan untuk menyimpan satu key, mencakup nilai, nama key, penanda tipe, dan entri tabel hash yang menampungnya. Struktur data agregat berukuran besar menerima opsi SAMPLES, yaitu mengambil sampel sebagian elemen agar estimasi tetap cepat. Perintah ini hanya membaca metadata alokasi, sehingga aman dijalankan pada keyspace produksi tanpa membebani server.

Estimasi tersebut mengungkap biaya tetap yang melekat pada setiap key. Sebuah aplikasi yang menyimpan jutaan key kecil membayar overhead yang sama untuk setiap key, jauh sebelum isi nilainya diperhitungkan. Blok berikut menyimpan tiga key dengan ukuran nilai yang berbeda, lalu mengukur biaya memori masing-masing.

bash
# simpan key dengan nilai sepanjang tiga byte
redis-cli -p 6382 SET biaya:key-pendek "abc"
# simpan key dengan nilai sepanjang seribu byte
redis-cli -p 6382 SET biaya:key-panjang "$(head -c 1000 /dev/zero | tr '\0' 'x')"
# simpan key dengan nilai berupa bilangan bulat kecil
redis-cli -p 6382 SET biaya:jumlah 42
# ukur biaya memori masing-masing key
redis-cli -p 6382 MEMORY USAGE biaya:key-pendek
redis-cli -p 6382 MEMORY USAGE biaya:key-panjang
redis-cli -p 6382 MEMORY USAGE biaya:jumlah
text Keluaran
OK
OK
OK
72
1088
56

Key pendek menempati 72 byte untuk nilai berisi tiga karakter, sehingga 69 byte di antaranya adalah overhead. Nilai seribu byte menempati 1088 byte, yang berarti overhead menambah sekitar sembilan persen biaya key tersebut. Angka 56 pada biaya:jumlah justru paling kecil karena bilangan bulat kecil disimpan sebagai objek bersama (shared object) yang digunakan bergantian oleh seluruh key, sehingga yang dihitung hanyalah penanda dan entri tabel hashnya.

Menguji noeviction, allkeys-lru, dan volatile-ttl#

Percobaan berikut menurunkan batas menjadi 60 MB melalui CONFIG SET agar batas dapat dicapai dalam hitungan detik, lalu menulis key berukuran satu MB secara berulang. Ukuran satu MB dipilih agar setiap key menonjol di samping overhead dan batas terlampaui hanya dengan puluhan key. Kebijakan diganti di antara percobaan pada keyspace yang sama, sehingga perbedaan keputusan sepenuhnya berasal dari perbedaan kebijakan.

bash
# kosongkan keyspace lab agar pengujian berangkat dari keadaan bersih
redis-cli -p 6382 FLUSHDB
# kecilkan batas memori menjadi 60 MB agar percobaan cepat
redis-cli -p 6382 CONFIG SET maxmemory 60mb
# pasang kebijakan noeviction sehingga penulisan ditolak saat penuh
redis-cli -p 6382 CONFIG SET maxmemory-policy noeviction
# tulis key berukuran satu MB sampai server menolak penulisan
NILAI=$(head -c 1048576 /dev/zero | tr '\0' 'a')
i=1
while [ "$(printf '%s' "$NILAI" | redis-cli -p 6382 -x SET "isi:x-$i")" = "OK" ]; do
  i=$((i+1))
done
# ulangi penulisan yang sama untuk menangkap pesan errornya
printf '%s' "$NILAI" | redis-cli -p 6382 -x SET "isi:x-$i"
# key yang telah tersimpan tetap utuh karena tidak ada yang dikorbankan
redis-cli -p 6382 EXISTS isi:x-1
text Keluaran
OK
OK
OK
OOM command not allowed when used memory > 'maxmemory'.

1

Pengisian berakhir ketika server menolak penulisan berikutnya dengan pesan OOM, singkatan dari out of memory. Jumlah key yang berhasil tersimpan bergantung pada ruang yang tersisa di bawah batas 60 MB pada saat pengujian, sehingga angkanya tidak dinyatakan sebagai nilai tetap. Kebijakan noeviction tidak menghapus satu key pun, sehingga key pertama, isi:x-1, tetap ada dan dapat dibaca normal. Perintah pembacaan seperti GET, EXISTS, dan DBSIZE tidak pernah ditolak karena tidak menambah penggunaan memori.

Ganti kebijakan menjadi allkeys-lru, lalu tulis key berukuran satu MB yang sama pada keyspace yang penuh tersebut. Perhatikan perubahan keputusan server pada penulisan yang persis sama.

bash
# ganti kebijakan menjadi allkeys-lru
redis-cli -p 6382 CONFIG SET maxmemory-policy allkeys-lru
# catat jumlah key sebelum penulisan baru
N=$(redis-cli -p 6382 DBSIZE)
# penulisan kembali diterima karena key lama dikorbankan
printf '%s' "$NILAI" | redis-cli -p 6382 -x SET isi:baru
# key baru ada di keyspace
redis-cli -p 6382 EXISTS isi:baru
# jumlah key tidak bertambah karena penulisan baru membayar dengan key lama
test "$(redis-cli -p 6382 DBSIZE)" -le "$N" && echo "jumlah key tidak bertambah"
text Keluaran
OK
OK
1
jumlah key tidak bertambah

Perintah SET kini dijawab OK karena server mengosongkan ruang lebih dahulu dengan mengeluarkan key lama, baru kemudian menyimpan key baru. EXISTS melaporkan 1 untuk key baru, sedangkan jumlah key tidak bertambah karena setiap penulisan baru pada keyspace yang penuh membayar dengan key lama yang dikorbankan. Korban yang dipilih merupakan hasil aproksimasi LRU, sehingga identitasnya dapat berbeda antar eksekusi dan memang tidak dinyatakan pada keluaran.

Kebijakan volatile-ttl memperlihatkan urutan korban yang lebih rinci. Kosongkan keyspace, buat tiga key bermasa hidup berukuran satu MB dengan sisa waktu 300, 200, dan 100 detik, lalu ulangi pengisian key tanpa masa hidup sampai server menolak.

bash
# kosongkan keyspace untuk skenario kebijakan volatile-ttl
redis-cli -p 6382 FLUSHDB
# pasang kebijakan volatile-ttl
redis-cli -p 6382 CONFIG SET maxmemory-policy volatile-ttl
# buat tiga key bermasa hidup berukuran satu MB
NILAI=$(head -c 1048576 /dev/zero | tr '\0' 'a')
printf '%s' "$NILAI" | redis-cli -p 6382 -X NILAI SET "isi:sementara-300" NILAI EX 300
printf '%s' "$NILAI" | redis-cli -p 6382 -X NILAI SET "isi:sementara-200" NILAI EX 200
printf '%s' "$NILAI" | redis-cli -p 6382 -X NILAI SET "isi:sementara-100" NILAI EX 100
# tulis key tanpa masa hidup sampai server menolak penulisan
i=1
while [ "$(printf '%s' "$NILAI" | redis-cli -p 6382 -x SET "isi:x-$i")" = "OK" ]; do
  i=$((i+1))
done
# ketiga key bermasa hidup sudah dikorbankan
redis-cli -p 6382 EXISTS isi:sementara-300
redis-cli -p 6382 EXISTS isi:sementara-200
redis-cli -p 6382 EXISTS isi:sementara-100
text Keluaran
OK
OK
OK
OK
OK
0
0
0

EXISTS melaporkan 0 untuk ketiga key bermasa hidup, yang berarti server mengeluarkannya saat mencari ruang, dimulai dari sisa masa hidup terpendek. Setelah kandidat bermasa hidup habis, penulisan kembali ditolak sebagaimana noeviction karena tidak ada korban lain yang sah menurut kebijakan ini. Pola tersebut berlaku umum pada kelompok volatile.

Percobaan pada topik ini selesai, sehingga kebijakan terakhir diperiksa kembali dan server khusus dimatikan tanpa menyimpan data ke disk.

bash
# pastikan kebijakan terakhir yang terpasang pada server lab
redis-cli -p 6382 CONFIG GET maxmemory-policy
# matikan server khusus topik ini tanpa menyimpan data ke disk
redis-cli -p 6382 shutdown nosave
text Keluaran
maxmemory-policy
volatile-ttl

Kesalahan umum#

Kesalahan pertama adalah membandingkan maxmemory dengan used_memory_rss. Nilai yang dibandingkan server terhadap batasnya adalah used_memory, sedangkan used_memory_rss memuat tambahan fragmentasi penyimpan alokasi, sehingga RSS yang mendekati maxmemory bukan berarti dataset melampauinya. Gunakan used_memory sebagai acuan batas dan sediakan ruang pada memori fisik untuk selisih fragmentasi.

Kesalahan kedua adalah mengandalkan key tertentu untuk selamat dari eviksi. Pemilihan korban pada LRU dan LFU bersifat aproksimasi melalui sampel acak, sehingga key yang menurut perkiraan paling lama tidak disentuh belum tentu yang dikorbankan. Rancanglah setiap key pada keyspace yang sama sebagai key yang sah untuk dikorbankan, dan simpan data yang tidak boleh hilang pada penyimpanan yang tahan lama.

Kesalahan ketiga adalah memperlakukan eviksi sebagai pengganti masa hidup. Eviksi hanya bekerja ketika memori penuh, sedangkan key tanpa masa hidup dapat menetap berbulan-bulan pada server yang memorinya longgar. Data yang keabsahannya berbatas waktu tetap memerlukan masa hidup, sedangkan eviksi berfungsi sebagai pengaman terakhir yang menjaga penggunaan memori di bawah batas.

Ringkasan#

  • maxmemory membatasi used_memory dataset, dan setiap perintah tulis diuji terhadap batas tersebut sebelum dieksekusi.
  • Kebijakan eviksi terbagi atas kelompok allkeys, volatile, dan noeviction, dengan algoritma LRU, LFU, random, atau TTL yang diterapkan secara aproksimasi melalui maxmemory-samples.
  • MEMORY USAGE mengukur biaya byte per key, termasuk overhead tetap yang membebani keyspace berisi banyak key kecil.
  • Kelompok volatile hanya mengeluarkan key bermasa hidup, sehingga tanpa key semacam itu server menolak penulisan sebagaimana noeviction.

Referensi#