ราคาบล็อก
ภาษา

Caching Strategies dengan Redis di Helipod: Panduan Lengkap

Tim Helipod
เผยแพร่เมื่อ อ่าน 6 นาที

บทความนี้เขียนเป็นภาษาอินโดนีเซีย

Aplikasi lambat? Mungkin kamu belum pakai caching dengan benar. Caching yang tepat bisa meningkatkan performa 10-100x lipat.

Artikel ini panduan lengkap caching strategies.

Mengapa Caching Penting?#

Tanpa Caching#

Request → Database Query (500ms) → Response
Request → Database Query (500ms) → Response
Request → Database Query (500ms) → Response

Total: 1500ms untuk 3 request

Dengan Caching#

Request → Cache Hit (1ms) → Response
Request → Cache Hit (1ms) → Response
Request → Cache Hit (1ms) → Response

Total: 3ms untuk 3 request

Speed up: 500x!

1. Cache-Aside Pattern#

Cara Kerja#

1. App cek cache
2. Cache hit → Return data
3. Cache miss → Query database → Simpan ke cache → Return data

Implementasi#

// Cache-aside pattern
async function getUser(userId) {
  const cacheKey = `user:${userId}`;
  
  // 1. Cek cache
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached); // Cache hit
  }
  
  // 2. Cache miss - query database
  const user = await db.users.findById(userId);
  
  // 3. Simpan ke cache dengan TTL
  await redis.set(cacheKey, JSON.stringify(user), { EX: 3600 });
  
  return user;
}

Kapan Menggunakan#

  • ✅ Data yang sering dibaca
  • ✅ Data yang tidak sering berubah
  • ✅ Read-heavy workload

2. Write-Through Pattern#

Cara Kerja#

1. App tulis ke cache DAN database secara bersamaan
2. Data selalu sync antara cache dan database

Implementasi#

// Write-through pattern
async function updateUser(userId, data) {
  const cacheKey = `user:${userId}`;
  
  // 1. Tulis ke database
  await db.users.update(userId, data);
  
  // 2. Tulis ke cache
  await redis.set(cacheKey, JSON.stringify(data), { EX: 3600 });
  
  return data;
}

Kapan Menggunakan#

  • ✅ Data yang sering ditulis dan dibaca
  • ✅ Data yang harus selalu fresh
  • ✅ Write-heavy workload

3. Write-Behind Pattern#

Cara Kerja#

1. App tulis ke cache
2. Cache batch write ke database secara async

Implementasi#

// Write-behind pattern
const writeBuffer = new Map();

async function updateUser(userId, data) {
  const cacheKey = `user:${userId}`;
  
  // 1. Tulis ke cache
  await redis.set(cacheKey, JSON.stringify(data), { EX: 3600 });
  
  // 2. Tambahkan ke buffer
  writeBuffer.set(userId, data);
}

// Batch write ke database setiap 5 detik
setInterval(async () => {
  for (const [userId, data] of writeBuffer) {
    await db.users.update(userId, data);
    writeBuffer.delete(userId);
  }
}, 5000);

Kapan Menggunakan#

  • ✅ Write-heavy workload
  • ✅ Data yang bisa delay sedikit
  • ✅ High-throughput application

4. Read-Through Pattern#

Cara Kerja#

1. App minta data dari cache
2. Cache otomatis query database jika miss

Implementasi#

// Read-through pattern dengan wrapper
class CacheReader {
  constructor(redis, db) {
    this.redis = redis;
    this.db = db;
  }
  
  async get(key, fetchFn, ttl = 3600) {
    // Cek cache
    const cached = await this.redis.get(key);
    if (cached) return JSON.parse(cached);
    
    // Fetch dari source
    const data = await fetchFn();
    
    // Simpan ke cache
    await this.redis.set(key, JSON.stringify(data), { EX: ttl });
    
    return data;
  }
}

// Penggunaan
const cache = new CacheReader(redis, db);
const user = await cache.get(
  `user:${userId}`,
  () => db.users.findById(userId),
  3600
);

5. Cache Invalidation Strategies#

Time-Based (TTL)#

// Data expire setelah waktu tertentu
await redis.set(`cache:${key}`, value, { EX: 3600 }); // 1 jam

Event-Based#

// Invalidate saat data berubah
async function updateUser(userId, data) {
  await db.users.update(userId, data);
  await redis.del(`user:${userId}`); // Invalidate cache
}

Tag-Based#

// Invalidate berdasarkan tag
async function invalidateTag(tag) {
  const keys = await redis.smembers(`tag:${tag}`);
  for (const key of keys) {
    await redis.del(key);
  }
  await redis.del(`tag:${tag}`);
}

// Saat update
async function updateUser(userId, data) {
  await db.users.update(userId, data);
  await redis.del(`user:${userId}`);
  await redis.sadd(`tag:user:${userId % 10}`, `user:${userId}`);
}

6. Cache Patterns untuk Use Cases#

Session Storage#

// Session dengan auto-expire
async function createSession(userId) {
  const sessionId = crypto.randomUUID();
  const session = {
    userId,
    createdAt: Date.now(),
    expiresAt: Date.now() + 86400000 // 24 jam
  };
  
  await redis.set(`session:${sessionId}`, JSON.stringify(session), { EX: 86400 });
  
  return sessionId;
}

async function getSession(sessionId) {
  const session = await redis.get(`session:${sessionId}`);
  return session ? JSON.parse(session) : null;
}

API Rate Limiting#

// Rate limit dengan sliding window
async function checkRateLimit(userId, limit = 100, window = 60) {
  const key = `ratelimit:${userId}`;
  const now = Date.now();
  const windowStart = now - window * 1000;
  
  // Hapus request lama
  await redis.zremrangebyscore(key, 0, windowStart);
  
  // Hitung request saat ini
  const count = await redis.zcard(key);
  
  if (count >= limit) {
    return false; // Rate limited
  }
  
  // Tambah request baru
  await redis.zadd(key, now, `${now}`);
  await redis.expire(key, window);
  
  return true;
}

Leaderboard#

// Leaderboard dengan sorted set
async function updateScore(userId, score) {
  await redis.zadd('leaderboard', score, userId);
}

async function getTop10() {
  return await redis.zrange('leaderboard', 0, 9, { REV: true });
}

async function getUserRank(userId) {
  const rank = await redis.zrevrank('leaderboard', userId);
  return rank !== null ? rank + 1 : null;
}

Shopping Cart#

// Cart dengan hash
async function addToCart(userId, productId, quantity) {
  await redis.hset(`cart:${userId}`, productId, quantity);
}

async function getCart(userId) {
  const cart = await redis.hgetall(`cart:${userId}`);
  return Object.entries(cart).map(([productId, quantity]) => ({
    productId,
    quantity: parseInt(quantity)
  }));
}

async function clearCart(userId) {
  await redis.del(`cart:${userId}`);
}

7. Performance Optimization#

Pipeline untuk Batch Operations#

// ❌ Slow - multiple round trips
for (const key of keys) {
  await redis.get(key);
}

// ✅ Fast - single round trip
const pipeline = redis.pipeline();
for (const key of keys) {
  pipeline.get(key);
}
const results = await pipeline.exec();

Connection Pooling#

// Gunakan connection pooling
import { Redis } from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  maxRetriesPerRequest: 3,
  enableReadyCheck: true,
  lazyConnect: true,
});

Memory Optimization#

// Gunakan hash untuk data yangRelated
// ❌ Multiple keys
await redis.set('user:1:name', 'Budi');
await redis.set('user:1:email', 'budi@example.com');
await redis.set('user:1:age', '25');

// ✅ Single hash
await redis.hset('user:1', {
  name: 'Budi',
  email: 'budi@example.com',
  age: '25'
});

8. Monitoring dan Debugging#

Metrics#

// Monitor cache hit rate
async function getCacheStats() {
  const info = await redis.info('stats');
  const hits = parseInt(info.match(/keyspace_hits:(\d+)/)[1]);
  const misses = parseInt(info.match(/keyspace_misses:(\d+)/)[1]);
  
  return {
    hits,
    misses,
    hitRate: hits / (hits + misses)
  };
}

Debug Logging#

// Log cache operations
redis.on('error', (err) => {
  console.error('Redis error:', err);
});

redis.on('connect', () => {
  console.log('Redis connected');
});

Checklist Caching#

  • Pilih caching pattern yang tepat
  • Setup TTL untuk data
  • Implementasi cache invalidation
  • Setup monitoring
  • Optimize memory usage
  • Test cache hit rate

Kesimpulan#

Caching yang tepat bisa meningkatkan performa aplikasi secara signifikan. Dengan Redis + Helipod, kamu bisa:

  • ✅ Implementasi berbagai caching patterns
  • ✅ Optimize performa aplikasi
  • ✅ Monitor cache usage
  • ✅ Scale sesuai kebutuhan

Butuh bantuan? Hubungi support@helipod.io

Baca juga:

Blog

บทความที่เกี่ยวข้อง

ดูทั้งหมด

พร้อมลองใช้ Helipod หรือยัง

ดีพลอยแอปของคุณได้เลยตอนนี้ ฟรี ไม่ต้องใช้บัตรเครดิต
WhatsApp