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', '[email protected]');
await redis.set('user:1:age', '25');
// ✅ Single hash
await redis.hset('user:1', {
name: 'Budi',
email: '[email protected]',
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 [email protected]
Baca juga: