☀️Siang
Database

Redis & Caching Strategy untuk Developer

TOKEN

Panduan lengkap Redis dan caching — data types, pub/sub messaging, caching patterns (cache-aside, write-through), TTL, session store, dan rate limiting untuk aplikasi production

Artikel: Redis Caching Artikel: Redis Caching


1. Pengenalan Redis

Redis (Remote Dictionary Server) adalah database in-memory yang berfungsi sebagai cache, message broker, dan data store berperforma tinggi. Dibuat oleh Salvatore Sanfilippo pada tahun 2009, Redis mampu menangani jutaan operasi per detik dengan latensi sub-milidetik karena seluruh data disimpan di RAM.

Redis bukan sekadar cache — Redis mendukung berbagai tipe data struktural (string, list, set, sorted set, hash), pub/sub messaging, Lua scripting, transaksi, dan bahkan persistence ke disk. Perusahaan besar seperti Twitter, GitHub, Stack Overflow, dan Snapchat menggunakan Redis di production.

Mengapa Redis Begitu Cepat?

Faktor Penjelasan
In-MemorySeluruh data disimpan di RAM — akses data ~100.000x lebih cepat dari disk SSD
Single-ThreadedTidak ada overhead locking/context switching — memanfaatkan event loop efisien
I/O MultiplexingMenggunakan epoll/kqueue untuk menangani ribuan koneksi secara paralel
Data Structure OptimizedStruktur data dioptimalkan secara khusus — bukan general-purpose seperti HashMap
Protocol SederhanaRESP (Redis Serialization Protocol) sangat ringan dan cepat di-parse

Redis vs Alternatif Lain

Aspek Redis Memcached Varnish
Data TypesBeragam (string, list, set, hash, sorted set)Hanya stringHTTP objects
Persistence✅ RDB + AOF✅ Disk-backed
Pub/Sub
Clustering✅ Built-inClient-side
Scripting✅ LuaVCL
Cocok untukCache, session, queue, analyticsSimple key-value cacheHTTP cache/reverse proxy
Diagram: Posisi Redis dalam Arsitektur Aplikasi
flowchart TB
    Client["📱 Client"] -->|"Request"| App["🖥️ App Server"]
    App -->|"Check Cache"| Redis["⚡ Redis\n(In-Memory Cache)"]
    Redis -->|"Cache Hit ✅"| App
    Redis -->|"Cache Miss ❌"| DB["("#quot;🗄️ Database\n(PostgreSQL/MySQL")#quot;)"]
    DB -->|"Data"| Redis
    Redis -->|"Store + Return"| App
    App -->|"Response"| Client

    style Client fill:#2a0f1e,stroke:#f472b6
    style App fill:#0f1d3a,stroke:#60a5fa,stroke-width:2px
    style Redis fill:#2a1f0a,stroke:#f59e0b,stroke-width:2px
    style DB fill:#0f2a1f,stroke:#3ecf8e,stroke-width:1.5px


2. Instalasi & Setup Redis

Docker (Rekomendasi untuk Development)

Diagram
🗄️ Database⚡ Redis🖥️ App🗄️ Database⚡ Redis🖥️ AppREAD Pathalt[Cache Hit][Cache Miss]WRITE PathInvalidate cacheGET keyReturn cached dataSELECT dataReturn dataSET key data (TTL)Return dataUPDATE dataDELETE key
JavaScript — Cache-Aside
const Redis = require('ioredis');
const redis = new Redis();
const db = require('./database'); // Database connection

// ==========================================
// CACHE-ASIDE PATTERN
// ==========================================

async function getProductById(productId) {
  const cacheKey = `product:${productId}`;

  // Langkah 1: Cek cache
  const cached = await redis.get(cacheKey);
  if (cached) {
    console.log('✅ Cache HIT');
    return JSON.parse(cached);
  }

  // Langkah 2: Cache MISS — query database
  console.log('❌ Cache MISS — querying database');
  const product = await db.query(
    'SELECT * FROM products WHERE id = ?', [productId]
  );

  if (!product) return null;

  // Langkah 3: Simpan ke cache dengan TTL 1 jam
  await redis.setex(cacheKey, 3600, JSON.stringify(product));

  return product;
}

// ==========================================
// CACHE INVALIDATION
// ==========================================

async function updateProduct(productId, newData) {
  // Update database dulu
  await db.query(
    'UPDATE products SET name = ?, price = ? WHERE id = ?',
    [newData.name, newData.price, productId]
  );

  // Hapus cache (bukan update!) — biar lazy reload
  await redis.del(`product:${productId}`);

  // Atau: update cache langsung (write-through ringan)
  const updated = { id: productId, ...newData };
  await redis.setex(`product:${productId}`, 3600, JSON.stringify(updated));
}

Pattern 2: Write-Through

Setiap kali data ditulis ke database, data juga sekaligus ditulis ke cache. Cache selalu konsisten dengan database.

JavaScript — Write-Through
// ==========================================
// WRITE-THROUGH PATTERN
// ==========================================

async function createProduct(productData) {
  // Langkah 1: Tulis ke database
  const result = await db.query(
    'INSERT INTO products (name, price, stock) VALUES (?, ?, ?)',
    [productData.name, productData.price, productData.stock]
  );

  const newProduct = {
    id: result.insertId,
    ...productData,
    created_at: new Date().toISOString()
  };

  // Langkah 2: Tulis ke cache SEKALIGUS
  const cacheKey = `product:${newProduct.id}`;
  await redis.setex(cacheKey, 3600, JSON.stringify(newProduct));

  // Langkah 3: Tambahkan ke list produk terbaru
  await redis.lpush('products:latest', JSON.stringify(newProduct));
  await redis.ltrim('products:latest', 0, 49); // Simpan 50 terbaru

  return newProduct;
}

// Kelebihan: Cache selalu up-to-date
// Kekurangan: Write latency lebih tinggi (harus tulis ke 2 tempat)
// Cocok untuk: Data yang sering dibaca dan jarang diubah

Pattern 3: Write-Behind (Write-Back)

JavaScript — Write-Behind
// ==========================================
// WRITE-BEHIND PATTERN
// Data ditulis ke cache dulu, database di-update belakangan
// ==========================================

const writeQueue = [];

async function updatePageView(pageId) {
  // Langkah 1: Tulis ke Redis (sangat cepat)
  await redis.incr(`page:${pageId}:views`);

  // Langkah 2: Tambahkan ke queue untuk update database nanti
  writeQueue.push({
    type: 'page_view',
    pageId: pageId,
    timestamp: Date.now()
  });
}

// Background worker — flush ke database setiap 10 detik
setInterval(async () => {
  if (writeQueue.length === 0) return;

  const batch = writeQueue.splice(0, writeQueue.length);
  console.log(`Flushing ${batch.length} writes ke database...`);

  try {
    // Batch update ke database
    for (const item of batch) {
      await db.query(
        'UPDATE pages SET views = views + 1 WHERE id = ?',
        [item.pageId]
      );
    }
  } catch (err) {
    console.error('Flush error:', err);
    // Kembalikan ke queue untuk retry
    writeQueue.unshift(...batch);
  }
}, 10000);

// Cocok untuk: Counter, analytics, logging — data yang toleran kehilangan
// ⚠️ Risiko: Data hilang jika Redis crash sebelum flush

Perbandingan Caching Patterns

Pattern Read Speed Write Speed Konsistensi Kompleksitas Cocok untuk
Cache-Aside🟢 Cepat (jika hit)🟡 Normal⚠️ Bisa staleRendahUmum, product catalog
Write-Through🟢 Selalu cepat🟡 Lebih lambat✅ KonsistenSedangData penting, profil user
Write-Behind🟢 Selalu cepat🟢 Sangat cepat⚠️ DelayTinggiCounter, analytics, log


6. TTL & Key Expiration

TTL (Time To Live) adalah mekanisme untuk mengatur berapa lama sebuah key akan hidup di Redis sebelum otomatis dihapus. Ini sangat penting untuk mencegah cache menjadi stale dan mengontrol penggunaan memori.

Operasi TTL

Redis CLI
# Set dengan expiry
SET session:abc123 '{"user_id":1}' EX 3600    # 1 jam
SET otp:0812345 "938271" EX 300                 # 5 menit
SET temp:upload:file1 "processing" EX 1800      # 30 menit

# Set expiry pada key yang sudah ada
EXPIRE session:abc123 7200      # Ubah ke 2 jam
EXPIREAT session:abc123 1687766400  # Expire pada timestamp tertentu

# Cek sisa TTL
TTL session:abc123              # 3599 (detik tersisa)
PTTL session:abc123             # 3599234 (milidetik tersisa)

# Hasil -1 = key ada tanpa expiry
# Hasil -2 = key tidak ditemukan

# Hapus expiry (buat persisten)
PERSIST session:abc123          # TTL dihapus, key hidup selamanya

# Set kalau belum ada (opsional)
SETNX lock:resource1 "worker-1" # Hanya set jika key belum ada

Strategi TTL untuk Berbagai Kasus

Kasus Penggunaan TTL yang Disarankan Alasan
Session user30 menit — 24 jamAuto-logout setelah idle
OTP / Token verifikasi3 — 5 menitKeamanan — OTP harus cepat expired
Product cache1 — 6 jamUpdate tidak perlu real-time
API rate limit counter1 — 60 detik (window)Counter di-reset per window
Cache populer / trending5 — 15 menitData berubah cepat
Distributed lock10 — 30 detikCegah deadlock jika worker crash


7. Session Store dengan Redis

Menyimpan session di Redis (bukan di memory server atau database) adalah praktik standar untuk aplikasi production. Keuntungannya: persistent (tidak hilang saat server restart), shared (bisa diakses dari banyak server), dan cepat (in-memory).

Implementasi Session Store dengan Express.js

JavaScript — server.js
// Instalasi:
// npm install express express-session connect-redis ioredis

const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const Redis = require('ioredis');

const app = express();

// Koneksi Redis
const redisClient = new Redis({
  host: 'localhost',
  port: 6379,
  password: 'rahasia123',
  db: 1  // Gunakan db terpisah untuk session
});

// Konfigurasi session dengan Redis store
app.use(session({
  store: new RedisStore({
    client: redisClient,
    prefix: 'sess:',           // Prefix key di Redis
    ttl: 86400                 // Session TTL: 24 jam (detik)
  }),
  secret: 'rahasia-session-***@example.com'
    });
    req.session.userId = user.id;
    req.session.role = user.role;

    res.json({ message: 'Login berhasil', user: { id: user.id, nama: user.nama } });
  } else {
    res.status(401).json({ error: 'Email atau password salah' });
  }
});

// Logout — hapus session
app.post('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) return res.status(500).json({ error: 'Gagal logout' });
    res.clearCookie('sessionId');
    res.json({ message: 'Logout berhasil' });
  });
});

app.listen(3000, () => console.log('Server jalan di port 3000'));

Monitoring Session di Redis

Redis CLI
# Lihat semua session keys
KEYS sess:*

# Ambil data session
GET sess:abc123def456
# Output: {"cookie":{...},"userId":1,"role":"user"}

# Cek TTL session
TTL sess:abc123def456
# Output: 82345 (sisa detik)

# Hitung jumlah session aktif
DBSIZE   # (jika db khusus session)

# Hapus session tertentu (force logout)
DEL sess:abc123def456

# Hapus semua session (force logout semua user)
FLUSHDB  # ⚠️ Hati-hati — hapus semua data di db saat ini


8. Rate Limiting

Rate limiting adalah teknik untuk membatasi jumlah request yang boleh dilakukan oleh satu client dalam periode waktu tertentu. Ini sangat penting untuk melindungi API dari abuse, brute force, dan DDoS. Redis sangat cocok untuk rate limiting karena operasinya atomic dan cepat.

Algoritma: Fixed Window Counter

JavaScript — Rate Limiter
const Redis = require('ioredis');
const redis = new Redis();

// ==========================================
// RATE LIMITER: Fixed Window Counter
// ==========================================

async function rateLimitFixedWindow(identifier, maxRequests, windowSeconds) {
  const key = `ratelimit:${identifier}:${Math.floor(Date.now() / 1000 / windowSeconds)}`;

  // Atomic increment
  const current = await redis.incr(key);

  // Set expiry hanya pada pertama kali (current === 1)
  if (current === 1) {
    await redis.expire(key, windowSeconds);
  }

  const remaining = Math.max(0, maxRequests - current);
  const ttl = await redis.ttl(key);

  return {
    allowed: current <= maxRequests,
    current: current,
    remaining: remaining,
    resetIn: ttl,
    limit: maxRequests
  };
}

// Contoh penggunaan
async function handleRequest(clientIp) {
  // Maksimal 100 request per 15 menit per IP
  const result = await rateLimitFixedWindow(clientIp, 100, 900);

  if (!result.allowed) {
    return {
      status: 429,
      headers: {
        'X-RateLimit-Limit': result.limit,
        'X-RateLimit-Remaining': 0,
        'Retry-After': result.resetIn
      },
      body: { error: 'Terlalu banyak request. Coba lagi nanti.' }
    };
  }

  // Request diizinkan
  return { status: 200, headers: { 'X-RateLimit-Remaining': result.remaining } };
}

Algoritma: Sliding Window (Lebih Akurat)

JavaScript — Sliding Window
// ==========================================
// RATE LIMITER: Sliding Window dengan Sorted Set
// Lebih akurat — tidak ada "burst" di batas window
// ==========================================

async function rateLimitSlidingWindow(identifier, maxRequests, windowSeconds) {
  const key = `ratelimit:sliding:${identifier}`;
  const now = Date.now();
  const windowStart = now - (windowSeconds * 1000);

  // Gunang Redis pipeline untuk efisiensi
  const pipeline = redis.pipeline();

  // 1. Hapus request lama yang sudah di luar window
  pipeline.zremrangebyscore(key, 0, windowStart);

  // 2. Hitung request dalam window saat ini
  pipeline.zcard(key);

  // 3. Tambah request baru
  pipeline.zadd(key, now, `${now}:${Math.random()}`);

  // 4. Set expiry pada key
  pipeline.expire(key, windowSeconds);

  const results = await pipeline.exec();
  const requestCount = results[1][1]; // Hasil zcard

  const allowed = requestCount < maxRequests;
  const remaining = Math.max(0, maxRequests - requestCount - 1);

  // Hapus request yang baru ditambah jika tidak diizinkan
  if (!allowed) {
    await redis.zrem(key, `${now}:${Math.random()}`);
  }

  return { allowed, current: requestCount + (allowed ? 1 : 0), remaining };
}

// Contoh: API endpoint dengan rate limiting
const express = require('express');
const app = express();

app.use(async (req, res, next) => {
  const clientIp = req.ip || req.connection.remoteAddress;
  const result = await rateLimitSlidingWindow(clientIp, 100, 900);

  res.set('X-RateLimit-Limit', '100');
  res.set('X-RateLimit-Remaining', result.remaining);

  if (!result.allowed) {
    return res.status(429).json({ error: 'Rate limit exceeded' });
  }

  next();
});

app.get('/api/data', (req, res) => {
  res.json({ data: 'success' });
});

app.listen(3000);
💡 Tips Rate Limiting
  • Per-IP — Untuk API publik, limit per IP address
  • Per-User — Untuk API terautentikasi, limit per user ID
  • Per-Endpoint — Endpoint berat (export, search) perlu limit lebih ketat
  • Tiered — User premium dapat limit lebih tinggi dari user gratis
  • Selalu kirim header X-RateLimit-Remaining dan Retry-After agar client tahu sisa kuota mereka


9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Redis & Caching Strategy:

Pertanyaan 1: Mengapa Redis sangat cepat dalam memproses data?

a) Karena menggunakan GPU untuk komputasi
b) Karena seluruh data disimpan di RAM (in-memory) dan menggunakan struktur data yang dioptimalkan
c) Karena menggunakan multi-threading paralel
d) Karena mengompresi semua data sebelum penyimpanan

Pertanyaan 2: Dalam pattern Cache-Aside, apa yang terjadi ketika terjadi cache miss?

a) Client menerima error 404
b) Aplikasi mengembalikan data dari database dan menyimpannya ke cache untuk request berikutnya
c) Cache otomatis mengisi dirinya sendiri dari database
d) Request dialihkan ke server lain

Pertanyaan 3: Apa fungsi dari TTL (Time To Live) di Redis?

a) Mengenkripsi data di Redis
b) Mengatur prioritas akses data
c) Mengatur berapa lama key akan hidup sebelum otomatis dihapus
d) Mengompresi data untuk menghemat memori

Pertanyaan 4: Mengapa session lebih baik disimpan di Redis daripada di memory server atau database?

a) Karena Redis lebih murah dari database
b) Karena Redis tidak memerlukan koneksi jaringan
c) Karena session di Redis shared antar server, cepat diakses, dan tidak hilang saat restart
d) Karena Redis secara otomatis mengenkripsi session

Pertanyaan 5: Apa kelebihan algoritma Sliding Window dibanding Fixed Window untuk rate limiting?

a) Sliding Window menggunakan lebih sedikit memori
b) Sliding Window lebih akurat dan tidak mengalami masalah "burst" di batas window
c) Sliding Window tidak memerlukan Redis
d) Sliding Window mendukung unlimited requests
🔍 Zoom
100%
🎨 Tema