Software Engineering

System Design untuk Developer: Panduan Lengkap

Scalability, load balancing, database design, caching strategies, message queue, microservices, dan pattern arsitektur distributed system

1. Pengenalan System Design

System Design adalah proses mendefinisikan arsitektur, komponen, module, interface, dan data flow dari suatu sistem untuk memenuhi kebutuhan spesifik. Ini adalah skill krusial yang membedakan developer junior dari senior.

Bayangkan kamu diminta mendesain sistem seperti Twitter, Uber, atau Netflix. Tidak cukup hanya bisa coding β€” kamu harus memahami bagaimana sistem menangani jutaan pengguna, terabyte data, dan latency rendah.

Mengapa System Design Penting?

Alasan Penjelasan
Interview Teknis80% interview senior/staff di FAANG menguji system design
Karir Level SeniorNaik ke senior/staff membutuhkan kemampuan desain sistem
Produk Skala BesarSistem yang tidak scalable akan gagal saat user bertambah
Kolaborasi TimMemahami keseluruhan sistem membantu komunikasi antar tim
Decision MakingMembuat keputusan teknis yang tepat untuk trade-off
Diagram: High-Level System Architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Client  │────▢│     CDN      │────▢│       Load Balancer         β”‚
β”‚ (Browser/β”‚     β”‚ (Static      β”‚     β”‚    (Nginx/HAProxy/ALB)      β”‚
β”‚  Mobile) β”‚     β”‚  Assets)     β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚
                                                β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€  App Server  β”‚
                                    β”‚           β”‚ β”‚   (Node.js)  β”‚
                                    β”‚           β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚           β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β”‚           └──  App Server  β”‚
                                    β”‚             β”‚   (Node.js)  β”‚
                                    β”‚             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚                    β”‚
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚    Cache    β”‚    β”‚      Database         β”‚
                         β”‚   (Redis)   β”‚    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”‚
                         β”‚  Cluster    β”‚    β”‚  β”‚Primaryβ”‚ β”‚Replicaβ”‚ β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
                                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ’‘ Framework System Design Interview

Saat menghadapi system design problem, gunakan framework ini: 1) Requirements (functional & non-functional), 2) Estimation (users, data, bandwidth), 3) High-level Design, 4) Detailed Design, 5) Trade-offs & Bottlenecks.

2. Scalability

Scalability adalah kemampuan sistem untuk menangani peningkatan beban tanpa menurunkan performa. Ada dua jenis scaling:

Vertical Scaling vs Horizontal Scaling

Aspek Vertical (Scale Up) Horizontal (Scale Out)
CaraTambah resource ke 1 server (RAM, CPU)Tambah server baru
BatasHardware limitHampir tidak terbatas
Kompleksitas🟒 Rendah🟑 Tinggi (perlu load balancer, distributed state)
CostNaik eksponensialLinear scaling
Single Point of Failure⚠️ Yaβœ… Tidak (redundancy)
Cocok untukStartup kecil, database awalProduksi skala besar, high availability

Contoh: Scalability Calculation

Back-of-the-Envelope Estimation β€” Twitter-like System
# Asumsi:
# - 200 juta DAU (Daily Active Users)
# - Setiap user membaca 10 tweets/hari = 2 miliar reads/hari
# - Setiap user membuat 2 tweets/hari = 400 juta writes/hari

# Write per detik:
# 400,000,000 / 86,400 β‰ˆ 4,629 writes/detik

# Read per detik:
# 2,000,000,000 / 86,400 β‰ˆ 23,148 reads/detik

# Storage per hari (rata-rata tweet 300 bytes):
# 400,000,000 Γ— 300 bytes = 120 GB/hari = 43.8 TB/tahun

# Bandwidth (read, 1 KB per tweet + metadata):
# 23,148 reads/detik Γ— 1 KB = ~23 MB/detik β‰ˆ 184 Mbps

# Kesimpulan:
# - Database perlu handle ~5K writes/detik β†’ perlu sharding
# - Cache sangat krusial untuk 23K reads/detik
# - CDN untuk media (foto, video)
# - Message queue untuk async fanout tweet ke followers

Micro vs Macro Scaling

πŸ“‹ Strategi Scaling
  • Database Replication β€” Primary untuk write, replica untuk read
  • Database Sharding β€” Bagi data ke beberapa database berdasarkan key
  • Service Decomposition β€” Pisahkan service berdasarkan domain (microservices)
  • Async Processing β€” Gunakan message queue untuk operasi yang tidak perlu real-time
  • Auto-scaling β€” Tambah/kurangi server otomatis berdasarkan load

3. Load Balancing

Load Balancer mendistribusikan traffic ke beberapa server secara merata. Ini adalah komponen kunci untuk high availability dan horizontal scaling.

Jenis Load Balancing

Algoritma Cara Kerja Cocok Untuk
Round RobinRequest bergantian ke tiap server secara berurutanServer dengan spec sama, request uniform
Weighted Round RobinSeperti round robin tapi server dengan kapasitas lebih mendapat lebih banyakServer dengan spec berbeda
Least ConnectionsKirim ke server dengan koneksi aktif paling sedikitRequest dengan durasi bervariasi
IP HashHash dari IP client menentukan server tujuanSession affinity / sticky session
Consistent HashingDistribusi berdasarkan hash ring β€” minimal reshuffling saat scalingDistributed cache, database sharding
Diagram: Load Balancer Architecture
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚    Load Balancer      β”‚
                    β”‚   (L4 / L7 layer)    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό              β–Ό              β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Server 1 β”‚  β”‚ Server 2 β”‚  β”‚ Server 3 β”‚
        β”‚ (App)    β”‚  β”‚ (App)    β”‚  β”‚ (App)    β”‚
        β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
             β”‚              β”‚              β”‚
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   Database   β”‚
                    β”‚  (Primary +  β”‚
                    β”‚  Replicas)   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Layer 4 (Transport): Distribusi berdasarkan IP + port
Layer 7 (Application): Distribusi berdasarkan URL, headers, cookies

Health Check: Load balancer mengecek server secara periodik
              Server yang down akan di-skip dari rotasi

4. Database Design & Scaling

Database sering menjadi bottleneck pertama saat sistem mulai scale. Memahami strategi scaling database sangat penting.

Database Scaling Strategies

1. Read Replicas

PostgreSQL β€” Replication Setup
# Primary server (postgresql.conf)
wal_level = replica
max_wal_senders = 5
wal_keep_size = '1GB'

# Replica server (postgresql.conf)
primary_conninfo = 'host=primary-db port=5432 user=replicator'
hot_standby = on

# Aplikasi: routing query ke primary/replica
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL  // Primary untuk write
    }
  }
});

// Untuk read-heavy operations, gunakan replica
const readPrisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_REPLICA_URL  // Replica untuk read
    }
  }
});

// Write ke primary
await prisma.user.create({ data: { name: 'Budi' } });

// Read dari replica
const users = await readPrisma.user.findMany();

2. Database Sharding

πŸ“‹ Sharding Strategies
Strategy Cara Kerja Pro Kontra
Range-basedBerdasarkan range ID/tanggalSederhana, range query mudahHotspot pada shard terbaru
Hash-basedHash dari shard keyDistribusi merataRange query sulit
Directory-basedLookup table untuk mappingFleksibelSingle point of failure
Geo-basedBerdasarkan lokasi userLatency rendah per regionCross-region query kompleks

3. CAP Theorem

Diagram: CAP Theorem
              Consistency
                 /\
                /  \
               /    \
              / PICK \
             /  TWO   \
            /──────────\
           /            \
    Availability ──── Partition
                       Tolerance

C (Consistency): Semua node melihat data yang sama
A (Availability): Setiap request mendapat response
P (Partition Tolerance): Sistem tetap jalan meski network partition

Contoh pilihan:
β”œβ”€β”€ CP (Consistency + Partition): MongoDB, HBase
β”‚   └── Data konsisten tapi bisa reject request saat partition
β”œβ”€β”€ AP (Availability + Partition): Cassandra, DynamoDB
β”‚   └── Selalu available tapi data bisa inconsistency sementara
└── CA (Consistency + Availability): PostgreSQL (single node)
    └── Konsisten dan available, tapi tidak partition-tolerant

5. Caching Strategies

Caching adalah teknik menyimpan data yang sering diakses di tempat yang lebih cepat diakses. Ini salah satu optimasi paling powerful untuk mengurangi latency dan beban database.

Caching Levels

Level Teknologi Latency Use Case
Browser CacheHTTP headers (Cache-Control)~0msStatic assets, API responses
CDN CacheCloudflare, CloudFront~5msStatic files, global distribution
Application CacheIn-memory (HashMap)~0.1msConfig, computed values
Distributed CacheRedis, Memcached~1msSession, user data, API results
Database CacheQuery cache, buffer pool~5msFrequent queries

Caching Patterns

Redis β€” Caching Patterns Implementation
import Redis from 'ioredis';
const redis = new Redis();

// ============================================
// PATTERN 1: Cache-Aside (Lazy Loading)
// ============================================
async function getUserById(id: string) {
  const cacheKey = `user:${id}`;

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

  // 2. Cache miss β†’ query database
  console.log('Cache MISS');
  const user = await db.user.findUnique({ where: { id } });

  // 3. Simpan ke cache (TTL 5 menit)
  if (user) {
    await redis.setex(cacheKey, 300, JSON.stringify(user));
  }

  return user;
}

// ============================================
// PATTERN 2: Write-Through
// ============================================
async function updateUser(id: string, data: UserUpdate) {
  // 1. Update database
  const user = await db.user.update({ where: { id }, data });

  // 2. Langsung update cache
  await redis.setex(`user:${id}`, 300, JSON.stringify(user));

  return user;
}

// ============================================
// PATTERN 3: Cache Invalidation
// ============================================
async function deleteUser(id: string) {
  await db.user.delete({ where: { id } });
  // Hapus dari cache
  await redis.del(`user:${id}`);
}

// ============================================
// PATTERN 4: Cache Stampede Prevention
// ============================================
async function getPopularProducts() {
  const cacheKey = 'popular:products';
  const ttl = 3600; // 1 jam

  let data = await redis.get(cacheKey);
  if (data) return JSON.parse(data);

  // Gunakan lock untuk hindari stampede
  const lockKey = `${cacheKey}:lock`;
  const locked = await redis.set(lockKey, '1', 'NX', 'EX', 30);

  if (locked) {
    const products = await db.product.findMany({
      orderBy: { views: 'desc' },
      take: 50
    });
    await redis.setex(cacheKey, ttl, JSON.stringify(products));
    await redis.del(lockKey);
    return products;
  }

  // Tunggu sebentar lalu coba dari cache lagi
  await new Promise(r => setTimeout(r, 100));
  return getPopularProducts();
}
⚠️ Cache Invalidation adalah Masalah Sulit

Phil Butler pernah berkata: "There are only two hard things in Computer Science: cache invalidation and naming things." Pastikan kamu menetapkan TTL yang tepat, dan punya strategi invalidation yang jelas saat data berubah.

6. Message Queue & Async Processing

Message queue memungkinkan komunikasi async antar service. Alih-alih memanggil service lain secara langsung (synchronous), kamu mengirim pesan ke queue dan service lain memprosesnya kapan pun siap.

Kapan Menggunakan Message Queue?

Use Case Tanpa Queue Dengan Queue
Email notificationUser tunggu 3 detik saat checkoutResponse instant, email dikirim async
Image processingUpload timeout untuk file besarUpload selesai, processing di background
Order processingSatu kegagalan = semua gagalRetry otomatis, processing terus walau ada error
Analytics loggingWrite ke analytics di setiap request = lambatLog ke queue, batch insert ke analytics DB
TypeScript β€” RabbitMQ Producer & Consumer
import amqplib from 'amqplib';

// === PRODUCER: Mengirim pesan ke queue ===
async function sendOrderNotification(order: Order) {
  const conn = await amqplib.connect('amqp://localhost');
  const channel = await conn.createChannel();

  const queue = 'order_notifications';
  await channel.assertQueue(queue, { durable: true });

  const message = {
    type: 'ORDER_CREATED',
    data: {
      orderId: order.id,
      userId: order.userId,
      total: order.total,
      timestamp: new Date().toISOString()
    }
  };

  channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)), {
    persistent: true  // Pesan tetap ada walau RabbitMQ restart
  });

  console.log('Order notification sent:', order.id);
  await channel.close();
  await conn.close();
}

// === CONSUMER: Memproses pesan dari queue ===
async function processOrderNotifications() {
  const conn = await amqplib.connect('amqp://localhost');
  const channel = await conn.createChannel();

  const queue = 'order_notifications';
  await channel.assertQueue(queue, { durable: true });

  // Prefetch: proses 1 pesan sekaligus
  channel.prefetch(1);

  console.log('Waiting for order notifications...');

  channel.consume(queue, async (msg) => {
    if (!msg) return;

    const { type, data } = JSON.parse(msg.content.toString());

    try {
      switch (type) {
        case 'ORDER_CREATED':
          await sendEmail(data.userId, 'Order confirmed!');
          await updateAnalytics(data);
          await notifyWarehouse(data.orderId);
          break;
      }

      // Acknowledge: pesan berhasil diproses
      channel.ack(msg);
    } catch (error) {
      // Reject & requeue: pesan akan diproses ulang
      channel.nack(msg, false, true);
    }
  });
}

7. Microservices Architecture

Microservices adalah arsitektur di mana aplikasi dibagi menjadi service-service kecil yang independen, masing-masing bertanggung jawab atas satu fungsi bisnis.

Monolith vs Microservices

Aspek Monolith Microservices
DeploymentSatu aplikasi, satu deployPer-service deploy independen
ScalingScale seluruh aplikasiScale per service sesuai beban
TechnologySatu tech stackBebas pilih tech stack per service
TeamTim besar, satu codebaseTim kecil, ownership jelas
ComplexitySederhana di awalOperational complexity tinggi
CommunicationFunction callNetwork call (HTTP/gRPC/message queue)
DataSatu databaseDatabase per service
Cocok untukStartup, MVP, tim kecilProduk mature, tim besar
Diagram: Microservices E-Commerce Architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      API GATEWAY                              β”‚
β”‚              (Authentication, Rate Limiting, Routing)          β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚             β”‚              β”‚              β”‚
       β–Ό             β–Ό              β–Ό              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   User     β”‚ β”‚  Product β”‚ β”‚   Order   β”‚ β”‚  Payment     β”‚
β”‚  Service   β”‚ β”‚  Service β”‚ β”‚  Service  β”‚ β”‚  Service     β”‚
β”‚            β”‚ β”‚          β”‚ β”‚           β”‚ β”‚              β”‚
β”‚ - Register β”‚ β”‚ - Search β”‚ β”‚ - Create  β”‚ β”‚ - Process    β”‚
β”‚ - Auth     β”‚ β”‚ - CRUD   β”‚ β”‚ - Status  β”‚ β”‚ - Refund     β”‚
β”‚ - Profile  β”‚ β”‚ - Stock  β”‚ β”‚ - History β”‚ β”‚ - Invoice    β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚             β”‚              β”‚              β”‚
      β–Ό             β–Ό              β–Ό              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚User DB   β”‚ β”‚Product DBβ”‚  β”‚Order DB  β”‚  β”‚  Payment DB  β”‚
β”‚(Postgres)β”‚ β”‚(Postgres)β”‚  β”‚(Postgres)β”‚  β”‚  (Postgres)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Communication:
β”œβ”€β”€ Sync: REST/gRPC antar service
β”œβ”€β”€ Async: Message queue (RabbitMQ/Kafka)
└── Events: Event-driven architecture
⚠️ Jangan Mulai dari Microservices

Banyak startup membuat kesalahan dengan langsung pakai microservices padahal belum perlu. Mulai dari modular monolith β€” pisahkan kode ke module yang jelas, dan baru pecah jadi microservices ketika benar-benar butuh (tim besar, service perlu scaling independen, tech stack berbeda).

8. Design Patterns untuk Distributed System

1. Circuit Breaker Pattern

TypeScript β€” Circuit Breaker Implementation
enum CircuitState {
  CLOSED = 'CLOSED',     // Normal β€” request lewat
  OPEN = 'OPEN',         // Gagal terus β€” block request
  HALF_OPEN = 'HALF_OPEN' // Testing β€” coba beberapa request
}

class CircuitBreaker {
  private state = CircuitState.CLOSED;
  private failureCount = 0;
  private lastFailureTime = 0;
  private readonly threshold = 5;         // Max failures
  private readonly timeout = 30000;       // 30 detik cooldown

  async call(fn: () => Promise): Promise {
    if (this.state === CircuitState.OPEN) {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = CircuitState.HALF_OPEN;
      } else {
        throw new Error('Circuit is OPEN β€” service unavailable');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failureCount = 0;
    this.state = CircuitState.CLOSED;
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.threshold) {
      this.state = CircuitState.OPEN;
    }
  }
}

// Penggunaan
const paymentBreaker = new CircuitBreaker();
const result = await paymentBreaker.call(() =>
  paymentService.processPayment(orderId, amount)
);

2. Rate Limiting Pattern

Redis β€” Token Bucket Rate Limiter
-- Redis Lua script: Token Bucket Rate Limiter
-- KEYS[1] = rate_limit:{user_id}
-- ARGV[1] = max_tokens (e.g., 100)
-- ARGV[2] = refill_rate (e.g., 10 per second)
-- ARGV[3] = current_timestamp

local key = KEYS[1]
local max_tokens = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

local data = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(data[1]) or max_tokens
local last_refill = tonumber(data[2]) or now

-- Refill tokens
local elapsed = now - last_refill
local new_tokens = math.min(max_tokens, tokens + elapsed * refill_rate)

if new_tokens >= 1 then
  -- Consume 1 token
  redis.call('HMSET', key, 'tokens', new_tokens - 1, 'last_refill', now)
  redis.call('EXPIRE', key, 3600)
  return 1  -- Allowed
else
  redis.call('HMSET', key, 'tokens', new_tokens, 'last_refill', now)
  return 0  -- Rate limited
end

3. Other Important Patterns

Pattern Fungsi Contoh Penggunaan
Saga PatternDistributed transactions across microservicesOrder β†’ Payment β†’ Inventory β†’ Shipping
CQRSPisahkan model read dan writeRead model dioptimasi untuk query, write model untuk konsistensi
Event SourcingSimpan semua perubahan sebagai eventAudit trail, financial transactions
Retry with Exponential BackoffRetry failed request dengan delay meningkatAPI calls ke service eksternal
BulkheadIsolasikan failure agar tidak menyebarThread pool per service dependency

9. Case Study: Mendesain URL Shortener

Mari kita terapkan semua konsep di atas untuk mendesain sistem URL Shortener seperti bit.ly.

Step 1: Requirements

πŸ“‹ Functional & Non-Functional Requirements

Functional:

  • User bisa memendekkan URL panjang menjadi short URL
  • User bisa redirect dari short URL ke URL asli
  • Custom alias (opsional)
  • Analytics: jumlah klik, referer, geolocation

Non-Functional:

  • 100 juta URL baru per bulan
  • Read:Write ratio = 100:1 (banyak redirect, sedikit create)
  • Latency redirect < 100ms
  • 99.99% availability
  • URL tidak expired (atau custom TTL)

Step 2: High-Level Design

URL Shortener Architecture
Client β†’ Load Balancer β†’ API Server
                              β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β–Ό         β–Ό         β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚ Cache   β”‚ β”‚ URL DB β”‚ β”‚ Analytics β”‚
              β”‚ (Redis) β”‚ β”‚(Postgresβ”‚ β”‚ Service   β”‚
              │         │ │/DynamoDB│ │(Kafka→   │
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Clickhouseβ”‚
                                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Short URL Generation:
β”œβ”€β”€ Base62 encode dari auto-increment ID
β”œβ”€β”€ Atau hash (MD5/SHA256) β†’ ambil 6-7 karakter pertama
└── Contoh: bit.ly/a1B2c3

Read Flow (99.9% traffic):
1. User buka short URL
2. Check Redis cache β†’ if HIT, redirect (301)
3. Cache MISS β†’ query DB β†’ set cache β†’ redirect

Write Flow (0.1% traffic):
1. User submit long URL
2. Generate short code (unique)
3. Save to DB
4. Return short URL

Database Schema:
CREATE TABLE urls (
  id BIGSERIAL PRIMARY KEY,
  short_code VARCHAR(10) UNIQUE NOT NULL,
  long_url TEXT NOT NULL,
  user_id UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  expires_at TIMESTAMP,
  click_count BIGINT DEFAULT 0
);

CREATE INDEX idx_short_code ON urls(short_code);

10. Quiz Pemahaman

Uji pemahaman kamu tentang system design:

Pertanyaan 1: Apa keuntungan utama horizontal scaling?

a) Lebih murah dari vertical scaling
b) Skala hampir tanpa batas dengan redundancy
c) Tidak perlu load balancer
d) Database tidak perlu sharding

Pertanyaan 2: Apa itu CAP Theorem?

a) Sistem harus memilih 2 dari 3: Consistency, Availability, Partition Tolerance
b) Semua sistem harus konsisten
c) Pola caching untuk database
d) Algoritma load balancing

Pertanyaan 3: Kapan menggunakan message queue?

a) Untuk semua komunikasi antar service
b) Ketika operasi tidak perlu selesai secara synchronous
c) Hanya untuk mengirim email
d) Saat database down

Pertanyaan 4: Apa fungsi Circuit Breaker?

a) Mengenkripsi koneksi antar service
b) Mencegah cascade failure dengan memblokir request ke service yang gagal
c) Mendistribusikan traffic ke beberapa server
d) Menyimpan data sementara

Pertanyaan 5: Apa yang dimaksud dengan cache-aside pattern?

a) Cache selalu update duluan sebelum database
b) Aplikasi check cache dulu, kalau miss baru query database lalu simpan ke cache
c) Cache dihapus setiap kali database update
d) Database mengirim data ke cache secara otomatis
πŸ” Zoom
100%
🎨 Tema