Pada industri perkeretaapian seperti Union Pacific (UP), rel baja memuai ekstrem saat terpapar suhu tinggi dan berisiko melengkung (sun kink). UP mengantisipasinya dengan memantau panas dan mengecat rel menjadi putih sebelum ambang batas kritis tercapai. Dalam arsitektur sistem terdistribusi, Redis mengalami kondisi serupa yang disebut thermal throttling cache: beban throughput tinggi memicu fragmentasi, memori menembus batas maxmemory, dan siklus CPU habis untuk alokasi ulang sinkron serta stop-the-world eviction.

Artikel ini membahas konfigurasi dan strategi proaktif untuk memitigasi degradasi latensi Redis sebelum memicu Out Of Memory (OOM) atau lonjakan tajam pada tail latency p99.

1. Gejala Thermal Throttling pada Redis Cluster

Saat Redis mendekati batas kapasitas tanpa mitigasi awal, dua fenomena degradasi performa terjadi secara bersamaan:

  • Sinkronisasi Eviction Blocking: Redis mengeksekusi perintah pada single-threaded event loop. Jika memori mencapai maxmemory, thread utama dipaksa mencari key kadaluarsa atau melakukan kalkulasi eviksi sebelum mengeksekusi operasi baca/tulis baru, sehingga used_cpu_sys naik tajam.
  • Fragmentasi Memori (High mem_fragmentation_ratio): Alokator seperti jemalloc sering mengalokasikan ukuran blok statis. Operasi mutasi key berukuran variatif menyebabkan fragmentasi tinggi (> 1.5). Sistem operasi mulai melakukan swapping ke disk yang melipatgandakan latensi baca.
  • Kaskade Failover: Node cluster yang terlambat mengirimkan detak jantung PING/PONG akibat thread terblokir akan dianggap mati oleh sentry/master lain, memicu rebalance dan failover yang tidak perlu saat trafik puncak.

2. Proactive Eviction Policy dan Early Invalidation Dinamis

Mengandalkan eviksi reaktif bawaan Redis saat memori 100% penuh adalah kegagalan desain. Dua strategi berikut memotong beban kerja eviksi thread utama:

Perbandingan: volatile-lru vs allkeys-lfu

Gunakan konfigurasi yang tepat sesuai pola beban:

  • volatile-lru: Membatasi eviksi hanya pada key yang memiliki TTL berbasis waktu akses terakhir. Risiko: jika data non-TTL bertambah, Redis tetap menabrak error OOM OOM command not allowed.
  • allkeys-lfu: Memantau Least Frequently Used menggunakan counter frekuensi probabilistik (Morris counter) untuk seluruh namespace. Pilihan terbaik untuk cache murni guna memastikan key bernilai rendah dibuang lebih awal.

Terapkan via konfigurasi:

maxmemory 4gb
maxmemory-policy allkeys-lfu
maxmemory-samples 10

Dynamic TTL Invalidation dengan Jitter

Mencegah thundering herd dan beban eviksi terkonsentrasi pada detik yang sama dengan menerapkan delta random (jitter) pada layer aplikasi:

import math
import random
import time

def calculate_dynamic_ttl(base_ttl: int, current_memory_ratio: float) -> int:
    """
    Memperpendek TTL saat kapasitas memori Redis meningkat (Early Invalidation).
    Analog dengan mengecat rel lebih awal saat sensor suhu rel mendeteksi panas naik.
    """
    # Jitter 10% untuk mencegah key expired bersamaan
    jitter = random.randint(-int(base_ttl * 0.1), int(base_ttl * 0.1))
    
    # Jika penggunaan memori > 80%, kompres TTL secara proporsional
    if current_memory_ratio > 0.8:
        compression_factor = 1.0 - (current_memory_ratio - 0.8) * 3
        compression_factor = max(0.2, compression_factor) # Batas bawah 20%
        return int((base_ttl * compression_factor) + jitter)
        
    return base_ttl + jitter

3. Tuning Active Defragmentation dan Maxmemory Samples

Secara default, Redis mengorbankan ketepatan algoritma eviksi demi throughput pemrosesan. Nilai default maxmemory-samples 5 sering membuang key yang salah (false eviction). Naikkan sampel ke 10 untuk akurasi mendekati true-LRU/LFU dengan overhead CPU yang terukur.

Aktifkan defragmentasi memori latar belakang untuk alokator jemalloc tanpa mengorbankan latensi p99:

# Aktifkan active defrag
activedefrag yes

# Mulai defrag jika fragmentasi mencapai minimal 100MB
active-defrag-ignore-bytes 100mb

# Mulai defrag jika rasio fragmentasi > 1.25 (25% memori terbuang)
active-defrag-threshold-lower 25

# Defrag agresif maksimal jika rasio > 1.50
active-defrag-threshold-upper 50

# Alokasi CPU minimum untuk defrag agar latensi thread utama aman
active-defrag-cycle-min 5

# Alokasi CPU maksimum saat batas upper tercapai
active-defrag-cycle-max 25

# Jumlah komputasi per siklus tick jemalloc
active-defrag-max-scan-fields 1000

4. Simulasi Benchmark Beban Tinggi dan Observabilitas Latency p99

Gunakan skrip Python dengan koneksi pool asinkron untuk menyimulasikan lonjakan beban dan mengukur tail latency p99 saat proses eviksi berjalan.

import asyncio
import numpy as np
import redis.asyncio as redis
import time

async def stress_worker(r: redis.Redis, worker_id: int, iterations: int, latencies: list):
    for i in range(iterations):
        key = f"sensor_rail_{worker_id}_{i % 500}"
        val = "x" * 1024  # Payload 1KB
        start = time.perf_counter()
        
        # Pipeline write + read
        async with r.pipeline(transaction=False) as pipe:
            pipe.set(key, val, ex=60)
            pipe.get(key)
            await pipe.execute()
            
        latency = (time.perf_counter() - start) * 1000  # ms
        latencies.append(latency)
        await asyncio.sleep(0.001)

async def main():
    pool = redis.ConnectionPool(host="127.0.0.1", port=6379, max_connections=50)
    r = redis.Redis(connection_pool=pool)
    latencies = []
    
    print("Memulai benchmark beban eviksi...")
    tasks = [stress_worker(r, w, 200, latencies) for w in range(20)]
    await asyncio.gather(*tasks)
    await pool.disconnect()
    
    # Hitung metrik latency percentiles
    p50 = np.percentile(latencies, 50)
    p95 = np.percentile(latencies, 95)
    p99 = np.percentile(latencies, 99)
    
    print(f"p50 Latency : {p50:.2f} ms")
    print(f"p95 Latency : {p95:.2f} ms")
    print(f"p99 Latency : {p99:.2f} ms")

if __name__ == "__main__":
    asyncio.run(main())

Metrik Kunci Observabilitas

Pantau metrik berikut secara berkala menggunakan perintah CLI:

  • redis-cli --latency-dist: Menampilkan sebaran distribusi latensi secara berkala langsung dari soket engine.
  • redis-cli info stats | grep evicted_keys: Periksa laju key yang dibuang per detik. Lonjakan mendadak mengindikasikan maxmemory terlalu rendah atau dynamic TTL tidak berjalan efektif.
  • redis-cli latency latest: Memeriksa subsistem Redis mana (seperti eviction-del atau defrag-cycle) yang menyebabkan spike latency lebih dari 10 milidetik.