Kuasai Redis dari konsep hingga produksi. Pelajari semua tipe data, use case real-world, dan bangun API lengkap untuk session management dan caching dengan NestJS.

Redis ada di mana-mana dalam sistem backend modern. Ia mendukung session management di GitHub, caching di Twitter, real-time leaderboards di platform gaming, dan pub/sub messaging di Slack. Namun banyak developer memperlakukannya sebagai "hanya cache"—sebuah kesempatan yang terlewatkan.
Redis adalah in-memory data structure store yang dapat berfungsi sebagai database, cache, message broker, dan streaming engine. Memahami tipe data dan kapan menggunakan masing-masing mengubah cara Anda merancang sistem. Dalam artikel ini, kita akan mengeksplorasi fundamental Redis, menyelami setiap tipe data dengan use case real-world, dan membangun API NestJS siap produksi yang mendemonstrasikan session management, caching, rate limiting, dan fitur real-time.
Database tradisional menyimpan data di disk. Bahkan dengan SSD, disk I/O jauh lebih lambat dari RAM:
Ketika Anda membutuhkan response time sub-milidetik—session lookups, cache hits, real-time analytics—database berbasis disk tidak bisa bersaing.
Sebelum Redis, developer membangun caching layers dengan Memcached atau solusi custom. Ini bekerja untuk simple key-value storage tetapi gagal untuk:
Redis menyelesaikan ini dengan menyediakan rich data structures dengan atomic operations, semuanya in-memory.
Redis menyimpan semua data di RAM, membuat reads dan writes sangat cepat. Data dapat dipersist ke disk menggunakan:
Redis menggunakan single-threaded event loop untuk eksekusi command. Ini menghilangkan race conditions dan membuat operations atomic secara default. Meskipun terdengar membatasi, Redis dapat menangani jutaan operations per detik karena:
Setiap Redis command adalah atomic. Ini crucial untuk:
Tipe data paling sederhana. Menyimpan text, numbers, atau binary data hingga 512MB.
Common Commands:
SET user:1000:name "John Doe"
GET user:1000:name
INCR page:views
INCRBY user:1000:credits 100
SETEX session:abc123 3600 "user_data" # Expires dalam 1 jamReal-World Use Cases:
Session Storage
Caching API Responses
Rate Limiting
Feature Flags
Contoh: Rate Limiting
# Izinkan 100 requests per menit
SET rate:user:1000 0 EX 60 NX
INCR rate:user:1000
# Jika result > 100, reject requestSimpan field-value pairs di bawah single key. Perfect untuk merepresentasikan objects.
Common Commands:
HSET user:1000 name "John" email "john@example.com" age 30
HGET user:1000 name
HGETALL user:1000
HINCRBY user:1000 age 1
HMGET user:1000 name emailReal-World Use Cases:
User Profiles
Product Catalogs
Configuration Management
Shopping Carts
Mengapa Gunakan Hashes Daripada Strings?
# ❌ String approach - harus serialize/deserialize entire object
SET user:1000 '{"name":"John","email":"john@example.com","age":30}'
# ✅ Hash approach - update individual fields
HSET user:1000 age 31 # Hanya update age fieldOrdered collections dari strings. Diimplementasikan sebagai linked lists.
Common Commands:
LPUSH queue:tasks "task1" "task2"
RPUSH queue:tasks "task3"
LPOP queue:tasks
RPOP queue:tasks
LRANGE queue:tasks 0 -1
LTRIM queue:tasks 0 99 # Simpan hanya 100 items pertamaReal-World Use Cases:
Task Queues
Activity Feeds
Message Queues
Undo/Redo Stacks
Contoh: Reliable Queue Pattern
# Pindahkan task dari queue ke processing list secara atomic
BRPOPLPUSH queue:tasks queue:processing 0
# Setelah processing, hapus dari processing list
LREM queue:processing 1 "task_data"Unordered collections dari unique strings. Fast membership testing.
Common Commands:
SADD tags:post:1 "redis" "database" "caching"
SMEMBERS tags:post:1
SISMEMBER tags:post:1 "redis"
SINTER tags:post:1 tags:post:2 # Intersection
SUNION tags:post:1 tags:post:2 # Union
SCARD tags:post:1 # Count membersReal-World Use Cases:
Tagging Systems
Unique Visitor Tracking
Social Graphs
Access Control
Contoh: Friend Recommendations
# Temukan friends of friends yang belum menjadi friends
SADD friends:user:1 "user:2" "user:3"
SADD friends:user:2 "user:1" "user:4" "user:5"
# Dapatkan friends user:2, exclude user:1 dan existing friends mereka
SDIFF friends:user:2 friends:user:1
# Result: user:4, user:5 (potential friend suggestions)Sets dengan score untuk setiap member. Members diurutkan berdasarkan score.
Common Commands:
ZADD leaderboard 100 "player1" 200 "player2" 150 "player3"
ZRANGE leaderboard 0 -1 WITHSCORES
ZREVRANGE leaderboard 0 9 # Top 10
ZINCRBY leaderboard 50 "player1"
ZRANK leaderboard "player1"
ZCOUNT leaderboard 100 200Real-World Use Cases:
Leaderboards
Priority Queues
Time-Series Data
Auto-Complete
Contoh: Real-Time Leaderboard
# Tambahkan player score
ZADD game:leaderboard 1500 "player:123"
# Dapatkan player rank (0-indexed)
ZREVRANK game:leaderboard "player:123"
# Dapatkan top 10 players
ZREVRANGE game:leaderboard 0 9 WITHSCORES
# Dapatkan players dalam score range
ZRANGEBYSCORE game:leaderboard 1000 2000Bukan tipe data terpisah, tetapi string operations di bit level. Sangat memory efficient.
Common Commands:
SETBIT user:1000:login:2024-02-21 0 1 # User logged in
GETBIT user:1000:login:2024-02-21 0
BITCOUNT user:1000:login:2024-02-21 # Hitung login days
BITOP AND result key1 key2 # Bitwise operationsReal-World Use Cases:
User Activity Tracking
Real-Time Analytics
A/B Testing
Contoh: Daily Active Users
# Tandai user 1000 sebagai active di day 0
SETBIT dau:2024-02-21 1000 1
# Hitung total active users
BITCOUNT dau:2024-02-21
# Temukan users active di kedua hari
BITOP AND dau:both dau:2024-02-21 dau:2024-02-22
BITCOUNT dau:bothProbabilistic data structure untuk menghitung unique items. Menggunakan fixed 12KB memory terlepas dari cardinality.
Common Commands:
PFADD unique:visitors:2024-02-21 "user1" "user2" "user3"
PFCOUNT unique:visitors:2024-02-21
PFMERGE unique:visitors:week day1 day2 day3Real-World Use Cases:
Unique Visitor Counting
Unique Search Queries
Cardinality Estimation
Mengapa Gunakan HyperLogLog?
# ❌ Set approach - memory tumbuh dengan unique items
SADD visitors:2024-02-21 "user1" "user2" ... # Bisa jutaan
# ✅ HyperLogLog - fixed 12KB memory
PFADD visitors:2024-02-21 "user1" "user2" ... # Selalu 12KBSimpan dan query geographic coordinates.
Common Commands:
GEOADD locations 13.361389 38.115556 "Palermo"
GEOADD locations 15.087269 37.502669 "Catania"
GEODIST locations "Palermo" "Catania" km
GEORADIUS locations 15 37 200 km WITHDIST
GEOSEARCH locations FROMLONLAT 15 37 BYRADIUS 100 kmReal-World Use Cases:
Location-Based Services
Delivery Routing
Geofencing
Append-only log data structure untuk event streaming dan message queues.
Common Commands:
XADD events * action "login" user "1000"
XREAD COUNT 10 STREAMS events 0
XGROUP CREATE events processors 0
XREADGROUP GROUP processors consumer1 COUNT 1 STREAMS events >
XACK events processors <message-id>Real-World Use Cases:
Event Sourcing
Message Queues
Real-Time Analytics
Activity Feeds
Sekarang mari kita bangun API siap produksi yang mendemonstrasikan Redis dalam aksi. Kita akan membuat platform blog dengan:
npm i -g @nestjs/cli
nest new redis-blog-api
cd redis-blog-api
npm install ioredis @nestjs/throttler class-validator class-transformerimport { Module, Global } from '@nestjs/common';
import { RedisService } from './redis.service';
@Global()
@Module({
providers: [RedisService],
exports: [RedisService],
})
export class RedisModule {}import { Injectable, OnModuleDestroy } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisService implements OnModuleDestroy {
private readonly client: Redis;
constructor() {
this.client = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379,
password: process.env.REDIS_PASSWORD,
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
});
this.client.on('error', (err) => {
console.error('Redis Client Error', err);
});
this.client.on('connect', () => {
console.log('Redis Client Connected');
});
}
getClient(): Redis {
return this.client;
}
async onModuleDestroy() {
await this.client.quit();
}
// String operations
async set(key: string, value: string, ttl?: number): Promise<void> {
if (ttl) {
await this.client.setex(key, ttl, value);
} else {
await this.client.set(key, value);
}
}
async get(key: string): Promise<string | null> {
return this.client.get(key);
}
async del(key: string): Promise<number> {
return this.client.del(key);
}
async incr(key: string): Promise<number> {
return this.client.incr(key);
}
// Hash operations
async hset(key: string, field: string, value: string): Promise<number> {
return this.client.hset(key, field, value);
}
async hgetall(key: string): Promise<Record<string, string>> {
return this.client.hgetall(key);
}
async hget(key: string, field: string): Promise<string | null> {
return this.client.hget(key, field);
}
// Sorted set operations
async zadd(key: string, score: number, member: string): Promise<number> {
return this.client.zadd(key, score, member);
}
async zincrby(key: string, increment: number, member: string): Promise<string> {
return this.client.zincrby(key, increment, member);
}
async zrevrange(
key: string,
start: number,
stop: number,
withScores?: boolean,
): Promise<string[]> {
if (withScores) {
return this.client.zrevrange(key, start, stop, 'WITHSCORES');
}
return this.client.zrevrange(key, start, stop);
}
// Set operations
async sadd(key: string, ...members: string[]): Promise<number> {
return this.client.sadd(key, ...members);
}
async smembers(key: string): Promise<string[]> {
return this.client.smembers(key);
}
async sismember(key: string, member: string): Promise<number> {
return this.client.sismember(key, member);
}
}import { Injectable } from '@nestjs/common';
import { RedisService } from '../redis/redis.service';
import { randomBytes } from 'crypto';
interface SessionData {
userId: string;
email: string;
createdAt: number;
}
@Injectable()
export class SessionService {
private readonly SESSION_PREFIX = 'session:';
private readonly SESSION_TTL = 86400; // 24 jam
constructor(private readonly redis: RedisService) {}
async createSession(userId: string, email: string): Promise<string> {
const sessionId = randomBytes(32).toString('hex');
const sessionKey = `${this.SESSION_PREFIX}${sessionId}`;
const sessionData: SessionData = {
userId,
email,
createdAt: Date.now(),
};
await this.redis.set(
sessionKey,
JSON.stringify(sessionData),
this.SESSION_TTL,
);
return sessionId;
}
async getSession(sessionId: string): Promise<SessionData | null> {
const sessionKey = `${this.SESSION_PREFIX}${sessionId}`;
const data = await this.redis.get(sessionKey);
if (!data) {
return null;
}
return JSON.parse(data);
}
async refreshSession(sessionId: string): Promise<boolean> {
const sessionKey = `${this.SESSION_PREFIX}${sessionId}`;
const data = await this.redis.get(sessionKey);
if (!data) {
return false;
}
await this.redis.set(sessionKey, data, this.SESSION_TTL);
return true;
}
async destroySession(sessionId: string): Promise<void> {
const sessionKey = `${this.SESSION_PREFIX}${sessionId}`;
await this.redis.del(sessionKey);
}
async getUserSessions(userId: string): Promise<string[]> {
const pattern = `${this.SESSION_PREFIX}*`;
const client = this.redis.getClient();
const keys = await client.keys(pattern);
const sessions: string[] = [];
for (const key of keys) {
const data = await this.redis.get(key);
if (data) {
const session: SessionData = JSON.parse(data);
if (session.userId === userId) {
sessions.push(key.replace(this.SESSION_PREFIX, ''));
}
}
}
return sessions;
}
}import {
Injectable,
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { RedisService } from '../../redis/redis.service';
@Injectable()
export class RateLimitGuard implements CanActivate {
private readonly RATE_LIMIT_PREFIX = 'rate_limit:';
private readonly MAX_REQUESTS = 100;
private readonly WINDOW_SIZE = 60; // 60 detik
constructor(private readonly redis: RedisService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const userId = request.user?.userId || request.ip;
const key = `${this.RATE_LIMIT_PREFIX}${userId}`;
const client = this.redis.getClient();
const current = await client.incr(key);
if (current === 1) {
await client.expire(key, this.WINDOW_SIZE);
}
if (current > this.MAX_REQUESTS) {
throw new HttpException(
'Terlalu banyak requests. Silakan coba lagi nanti.',
HttpStatus.TOO_MANY_REQUESTS,
);
}
// Tambahkan rate limit info ke response headers
const ttl = await client.ttl(key);
request.res.setHeader('X-RateLimit-Limit', this.MAX_REQUESTS);
request.res.setHeader('X-RateLimit-Remaining', this.MAX_REQUESTS - current);
request.res.setHeader('X-RateLimit-Reset', Date.now() + ttl * 1000);
return true;
}
}import { Injectable } from '@nestjs/common';
import { RedisService } from '../redis/redis.service';
interface Post {
id: string;
title: string;
content: string;
authorId: string;
createdAt: Date;
views: number;
}
@Injectable()
export class PostCacheService {
private readonly POST_CACHE_PREFIX = 'post:';
private readonly POST_LIST_KEY = 'posts:all';
private readonly TRENDING_KEY = 'posts:trending';
private readonly CACHE_TTL = 3600; // 1 jam
constructor(private readonly redis: RedisService) {}
async cachePost(post: Post): Promise<void> {
const key = `${this.POST_CACHE_PREFIX}${post.id}`;
await this.redis.set(key, JSON.stringify(post), this.CACHE_TTL);
}
async getPost(postId: string): Promise<Post | null> {
const key = `${this.POST_CACHE_PREFIX}${postId}`;
const data = await this.redis.get(key);
if (!data) {
return null;
}
return JSON.parse(data);
}
async invalidatePost(postId: string): Promise<void> {
const key = `${this.POST_CACHE_PREFIX}${postId}`;
await this.redis.del(key);
}
async incrementViews(postId: string): Promise<number> {
const viewKey = `${this.POST_CACHE_PREFIX}${postId}:views`;
const views = await this.redis.incr(viewKey);
// Update trending score (views dalam 24 jam terakhir)
await this.redis.zincrby(this.TRENDING_KEY, 1, postId);
return views;
}
async getViews(postId: string): Promise<number> {
const viewKey = `${this.POST_CACHE_PREFIX}${postId}:views`;
const views = await this.redis.get(viewKey);
return views ? parseInt(views) : 0;
}
async getTrendingPosts(limit: number = 10): Promise<string[]> {
return this.redis.zrevrange(this.TRENDING_KEY, 0, limit - 1);
}
async cachePostList(posts: Post[]): Promise<void> {
await this.redis.set(
this.POST_LIST_KEY,
JSON.stringify(posts),
this.CACHE_TTL,
);
}
async getPostList(): Promise<Post[] | null> {
const data = await this.redis.get(this.POST_LIST_KEY);
if (!data) {
return null;
}
return JSON.parse(data);
}
async invalidatePostList(): Promise<void> {
await this.redis.del(this.POST_LIST_KEY);
}
async addToUserPosts(userId: string, postId: string): Promise<void> {
const key = `user:${userId}:posts`;
await this.redis.sadd(key, postId);
}
async getUserPosts(userId: string): Promise<string[]> {
const key = `user:${userId}:posts`;
return this.redis.smembers(key);
}
}import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
UseGuards,
Request,
} from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostCacheService } from './cache.service';
import { RateLimitGuard } from '../common/guards/rate-limit.guard';
import { AuthGuard } from '../common/guards/auth.guard';
@Controller('posts')
@UseGuards(RateLimitGuard)
export class PostsController {
constructor(
private readonly postsService: PostsService,
private readonly cacheService: PostCacheService,
) {}
@Get()
async findAll() {
// Coba cache dulu
const cached = await this.cacheService.getPostList();
if (cached) {
return { source: 'cache', data: cached };
}
// Cache miss - fetch dari database
const posts = await this.postsService.findAll();
await this.cacheService.cachePostList(posts);
return { source: 'database', data: posts };
}
@Get('trending')
async getTrending() {
const postIds = await this.cacheService.getTrendingPosts(10);
const posts = await Promise.all(
postIds.map(async (id) => {
const cached = await this.cacheService.getPost(id);
if (cached) return cached;
return this.postsService.findOne(id);
}),
);
return posts.filter(Boolean);
}
@Get(':id')
async findOne(@Param('id') id: string) {
// Coba cache dulu
const cached = await this.cacheService.getPost(id);
if (cached) {
// Increment views secara asynchronous
this.cacheService.incrementViews(id);
return { source: 'cache', data: cached };
}
// Cache miss - fetch dari database
const post = await this.postsService.findOne(id);
if (post) {
await this.cacheService.cachePost(post);
await this.cacheService.incrementViews(id);
}
return { source: 'database', data: post };
}
@Post()
@UseGuards(AuthGuard)
async create(@Body() createPostDto: any, @Request() req) {
const post = await this.postsService.create({
...createPostDto,
authorId: req.user.userId,
});
// Cache post baru
await this.cacheService.cachePost(post);
// Tambahkan ke user's posts
await this.cacheService.addToUserPosts(req.user.userId, post.id);
// Invalidate post list cache
await this.cacheService.invalidatePostList();
return post;
}
@Put(':id')
@UseGuards(AuthGuard)
async update(
@Param('id') id: string,
@Body() updatePostDto: any,
@Request() req,
) {
const post = await this.postsService.update(id, updatePostDto);
// Invalidate cache
await this.cacheService.invalidatePost(id);
await this.cacheService.invalidatePostList();
return post;
}
@Delete(':id')
@UseGuards(AuthGuard)
async remove(@Param('id') id: string) {
await this.postsService.remove(id);
// Invalidate cache
await this.cacheService.invalidatePost(id);
await this.cacheService.invalidatePostList();
return { message: 'Post berhasil dihapus' };
}
@Get(':id/views')
async getViews(@Param('id') id: string) {
const views = await this.cacheService.getViews(id);
return { postId: id, views };
}
}import { Controller, Post, Delete, Body, Headers, HttpException, HttpStatus } from '@nestjs/common';
import { SessionService } from './session.service';
import { UsersService } from '../users/users.service';
@Controller('auth')
export class AuthController {
constructor(
private readonly sessionService: SessionService,
private readonly usersService: UsersService,
) {}
@Post('login')
async login(@Body() loginDto: { email: string; password: string }) {
// Validasi credentials (simplified)
const user = await this.usersService.validateUser(
loginDto.email,
loginDto.password,
);
if (!user) {
throw new HttpException('Credentials tidak valid', HttpStatus.UNAUTHORIZED);
}
// Buat session
const sessionId = await this.sessionService.createSession(
user.id,
user.email,
);
return {
sessionId,
user: {
id: user.id,
email: user.email,
},
};
}
@Post('logout')
async logout(@Headers('authorization') auth: string) {
const sessionId = auth?.replace('Bearer ', '');
if (!sessionId) {
throw new HttpException('Tidak ada session', HttpStatus.BAD_REQUEST);
}
await this.sessionService.destroySession(sessionId);
return { message: 'Berhasil logout' };
}
@Post('refresh')
async refresh(@Headers('authorization') auth: string) {
const sessionId = auth?.replace('Bearer ', '');
if (!sessionId) {
throw new HttpException('Tidak ada session', HttpStatus.BAD_REQUEST);
}
const refreshed = await this.sessionService.refreshSession(sessionId);
if (!refreshed) {
throw new HttpException('Session tidak valid', HttpStatus.UNAUTHORIZED);
}
return { message: 'Session di-refresh' };
}
}# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# Application
PORT=3000
NODE_ENV=developmentversion: '3.8'
services:
redis:
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
- redis_data:/data
command: redis-server --appendonly yes
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
redis-commander:
image: rediscommander/redis-commander:latest
environment:
- REDIS_HOSTS=local:redis:6379
ports:
- '8081:8081'
depends_on:
- redis
volumes:
redis_data:# Start Redis
docker-compose up -d
# Install dependencies
npm install
# Run application
npm run start:dev# Login
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'
# Response: {"sessionId":"abc123...","user":{...}}
# Buat post (dengan session)
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer abc123..." \
-d '{"title":"Redis Guide","content":"Belajar Redis..."}'
# Dapatkan semua posts (cached)
curl http://localhost:3000/posts
# Dapatkan single post (cached + increment views)
curl http://localhost:3000/posts/1
# Dapatkan trending posts
curl http://localhost:3000/posts/trending
# Dapatkan post views
curl http://localhost:3000/posts/1/views
# Test rate limiting (buat 101 requests)
for i in {1..101}; do
curl http://localhost:3000/posts
done
# Setelah 100 requests: 429 Too Many RequestsKeys tanpa expiration dapat menyebabkan memory leaks.
// ❌ Salah - key hidup selamanya
await redis.set('session:abc123', sessionData);
// ✅ Benar - key expires secara otomatis
await redis.setex('session:abc123', 3600, sessionData);KEYS memblokir Redis saat scanning semua keys. Gunakan SCAN sebagai gantinya.
// ❌ Salah - blocks Redis
const keys = await redis.keys('user:*');
// ✅ Benar - non-blocking iteration
const stream = redis.scanStream({ match: 'user:*', count: 100 });
stream.on('data', (keys) => {
// Process keys
});Redis dioptimalkan untuk small values. Large values (>1MB) merusak performance.
// ❌ Salah - menyimpan 10MB JSON
await redis.set('data', JSON.stringify(hugeObject));
// ✅ Benar - gunakan compression atau split data
const compressed = gzip(JSON.stringify(hugeObject));
await redis.set('data', compressed);
// Atau split ke chunks
await redis.hset('data', 'chunk1', part1);
await redis.hset('data', 'chunk2', part2);Redis connections bisa fail. Implementasikan retry logic dan error handling.
// ✅ Proper error handling
const redis = new Redis({
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
maxRetriesPerRequest: 3,
});
redis.on('error', (err) => {
logger.error('Redis error:', err);
// Alert monitoring system
});Multiple requests dapat menyebabkan cache stampede.
// ❌ Salah - cache stampede mungkin terjadi
const cached = await redis.get('posts');
if (!cached) {
const posts = await db.getPosts(); // Multiple requests hit DB
await redis.set('posts', JSON.stringify(posts));
}
// ✅ Benar - gunakan locking
const lockKey = 'lock:posts';
const lock = await redis.set(lockKey, '1', 'EX', 10, 'NX');
if (lock) {
try {
const posts = await db.getPosts();
await redis.set('posts', JSON.stringify(posts), 'EX', 3600);
} finally {
await redis.del(lockKey);
}
} else {
// Wait dan retry
await sleep(100);
return getCachedPosts();
}Redis menyimpan semuanya di RAM. Monitor memory dan set limits.
# Set max memory
CONFIG SET maxmemory 2gb
# Set eviction policy
CONFIG SET maxmemory-policy allkeys-lru
# Monitor memory
INFO memoryPilih tipe data yang tepat untuk use case Anda:
Pre-populate cache untuk frequently accessed data.
async warmCache() {
const popularPosts = await db.getPopularPosts(100);
for (const post of popularPosts) {
await redis.setex(
`post:${post.id}`,
3600,
JSON.stringify(post)
);
}
}Kurangi network round trips dengan pipelining.
// ❌ Lambat - multiple round trips
for (const post of posts) {
await redis.set(`post:${post.id}`, JSON.stringify(post));
}
// ✅ Cepat - single round trip
const pipeline = redis.pipeline();
for (const post of posts) {
pipeline.set(`post:${post.id}`, JSON.stringify(post));
}
await pipeline.exec();Aplikasi harus bekerja meskipun Redis down.
async getPost(id: string) {
try {
const cached = await redis.get(`post:${id}`);
if (cached) return JSON.parse(cached);
} catch (err) {
logger.warn('Redis unavailable, falling back to DB');
}
// Fallback ke database
return db.getPost(id);
}Track Redis performance:
async getMetrics() {
const info = await redis.info();
return {
hitRate: calculateHitRate(info),
memoryUsed: parseMemory(info),
evictedKeys: parseEvicted(info),
};
}Redis bukan pengganti traditional databases. Gunakan untuk:
Tapi tidak untuk:
Redis menyimpan semuanya di RAM. Jika dataset Anda lebih besar dari available memory, pertimbangkan:
Redis tidak mendukung joins atau complex queries. Untuk relational data, gunakan:
Jika Anda memerlukan ACID guarantees, audit logs, atau strict consistency, gunakan traditional databases.
Redis adalah tool yang powerful ketika digunakan dengan benar. Memahami tipe data dan use case mereka memungkinkan Anda membangun sistem high-performance dan scalable. Contoh NestJS mendemonstrasikan pola real-world:
Mulai dengan use case sederhana seperti caching dan session storage. Seiring Anda mendapatkan confidence, eksplorasi pola advanced seperti pub/sub, streams, dan geospatial queries. Simplicity dan performance Redis membuatnya indispensable dalam arsitektur modern.
Langkah selanjutnya:
Redis bukan hanya cache—ini adalah data structure server yang dapat mentransformasi performance dan capabilities aplikasi Anda.