Round Robin

Round Robin #

Round robin adalah algoritma load balancing paling klasik dan paling mudah dipahami: setiap request baru diarahkan ke upstream berikutnya dalam urutan melingkar. Request pertama ke upstream-1, kedua ke upstream-2, ketiga ke upstream-3, keempat kembali ke upstream-1, dan seterusnya.

Kesederhanaan inilah yang membuat round robin menjadi pilihan default di hampir semua load balancer termasuk Caddy — ia mudah diprediksi, mudah di-debug, dan bekerja sangat baik untuk mayoritas use case.

Cara Kerja Round Robin #

Caddy menerima 9 request berturut-turut:

Request  1 → backend-1:3000
Request  2 → backend-2:3000
Request  3 → backend-3:3000
Request  4 → backend-1:3000   (kembali ke awal)
Request  5 → backend-2:3000
Request  6 → backend-3:3000
Request  7 → backend-1:3000
Request  8 → backend-2:3000
Request  9 → backend-3:3000

Setiap backend mendapatkan jumlah request yang sama:
  backend-1: 3 request (33%)
  backend-2: 3 request (33%)
  backend-3: 3 request (33%)

Distribusi ini dijamin merata dalam hal jumlah request — bukan dalam hal beban aktual, karena setiap request bisa memiliki durasi dan berat komputasi yang berbeda.


Konfigurasi Round Robin #

example.com {
    # Round robin adalah default — tidak perlu menulis lb_policy
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000
}

# Penulisan eksplisit (sama hasilnya)
example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
        lb_policy round_robin
    }
}

# Dengan health check (direkomendasikan untuk production)
example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
        lb_policy       round_robin
        health_uri      /health
        health_interval 10s
        health_timeout  5s
    }
}

Round Robin dengan Docker Compose #

Pola yang sangat umum: beberapa container aplikasi di belakang Caddy:

# docker-compose.yml
services:
  caddy:
    image: caddy:2.8.4
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    networks:
      - app-network

  app-1:
    image: myapp:latest
    networks:
      - app-network
    environment:
      - NODE_ENV=production
      - INSTANCE=1

  app-2:
    image: myapp:latest
    networks:
      - app-network
    environment:
      - NODE_ENV=production
      - INSTANCE=2

  app-3:
    image: myapp:latest
    networks:
      - app-network
    environment:
      - NODE_ENV=production
      - INSTANCE=3

networks:
  app-network:
    driver: bridge

volumes:
  caddy_data:
# Caddyfile
example.com {
    reverse_proxy app-1:3000 app-2:3000 app-3:3000 {
        lb_policy       round_robin
        health_uri      /health
        health_interval 15s
        health_timeout  5s
    }
}

Round Robin dengan Scaling Dinamis #

Dengan Admin API Caddy, kamu bisa menambah atau mengurangi upstream tanpa restart:

# Lihat konfigurasi upstream saat ini
curl -s http://localhost:2019/config/apps/http/servers/srv0/routes \
  | jq '.[0].handle[0].routes[0].handle[0].upstreams'

# Tambah upstream baru secara dinamis
curl -X POST http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/routes/0/handle/0/upstreams \
  -H "Content-Type: application/json" \
  -d '{"dial": "app-4:3000"}'

# Reload config dari file (cara lebih mudah)
sudo systemctl reload caddy
# atau
caddy reload --config /etc/caddy/Caddyfile

Round Robin vs Algoritma Lain: Kapan Memilih Round Robin #

Gunakan Round Robin ketika:
  ✓ Semua backend memiliki spesifikasi hardware yang sama
  ✓ Request bersifat homogen — durasi pemrosesan mirip satu sama lain
  ✓ Aplikasi sepenuhnya stateless (tidak menyimpan state di memori)
  ✓ Kamu ingin distribusi yang paling mudah diprediksi dan di-debug
  ✓ Beban per request relatif seragam (misal: API CRUD sederhana)

Pertimbangkan algoritma lain ketika:
  ✗ Backend memiliki kapasitas berbeda (gunakan weighted)
  ✗ Durasi request sangat bervariasi (gunakan least_conn)
  ✗ Aplikasi menyimpan state session di memori (gunakan ip_hash atau cookie)
  ✗ Kamu ingin cache consistency untuk URI yang sama (gunakan uri_hash)

Simulasi: Perbandingan Round Robin vs Least Connections #

Skenario: 6 request masuk, durasi pemrosesan bervariasi
Backend-1 memproses cepat (50ms per request)
Backend-2 memproses lambat (500ms per request)

Round Robin:
  T=0ms   Req-1 → backend-1 (50ms)
  T=0ms   Req-2 → backend-2 (500ms)
  T=0ms   Req-3 → backend-1 (50ms)    backend-1 sudah selesai Req-1
  T=0ms   Req-4 → backend-2 (500ms)   backend-2 MASIH proses Req-2!
  T=0ms   Req-5 → backend-1 (50ms)    backend-1 sudah selesai Req-3
  T=0ms   Req-6 → backend-2 (500ms)   backend-2 MASIH proses Req-2 & 4!
  
  Hasil: backend-2 terbebani dengan antrian, backend-1 idle

Least Connections:
  T=0ms   Req-1 → backend-1 (50ms)   [backend-1: 1 conn, backend-2: 0 conn]
  T=0ms   Req-2 → backend-2 (500ms)  [backend-1: 1 conn, backend-2: 1 conn]
  T=50ms  Req-3 → backend-1 (50ms)   [backend-1: 0 conn → pilih ini]
  T=100ms Req-4 → backend-1 (50ms)   [backend-1: 0 conn → pilih ini]
  T=150ms Req-5 → backend-1 (50ms)   [backend-1: 0 conn → pilih ini]
  T=200ms Req-6 → backend-1 (50ms)   [backend-1: 0 conn → pilih ini]
  
  Hasil: backend-1 digunakan lebih optimal karena lebih cepat

Ini menunjukkan bahwa round robin bisa menyebabkan ketidakseimbangan ketika ada perbedaan kecepatan antar backend. Untuk kasus seperti ini, least_conn lebih optimal.


Health Check dengan Round Robin #

Round robin secara default akan terus mengirim request ke backend yang sedang down sampai kamu mengkonfigurasi health check:

example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
        lb_policy round_robin
        
        # ── Active Health Check ──────────────────────────────────
        # Caddy proaktif cek setiap backend setiap 10 detik
        health_uri      /health           # Endpoint khusus untuk health check
        health_interval 10s               # Seberapa sering cek
        health_timeout  5s                # Timeout per cek
        health_status   200               # Status code yang dianggap sehat
        
        # ── Passive Health Check ─────────────────────────────────
        # Caddy amati error dari request nyata
        health_fails    3                 # Unhealthy setelah 3 gagal berturut
        health_passes   2                 # Pulihkan setelah 2 sukses berturut
    }
}
Dengan health check, jika backend-2 down:

Request  1 → backend-1:3000  ✓
Request  2 → backend-2:3000  ✗ (gagal — health check catat)
Request  3 → backend-3:3000  ✓
Request  4 → backend-1:3000  ✓
Request  5 → backend-2:3000  ✗ (gagal lagi — health check catat)
Request  6 → backend-3:3000  ✓
[backend-2 gagal 3 kali → tandai unhealthy]
Request  7 → backend-1:3000  ✓ (backend-2 di-skip)
Request  8 → backend-3:3000  ✓ (backend-2 di-skip)
Request  9 → backend-1:3000  ✓ (backend-2 di-skip)
[health check aktif terus cek backend-2 setiap 10s]
[backend-2 pulih → tandai healthy kembali]
Request 10 → backend-2:3000  ✓ (backend-2 kembali dalam rotasi)

Endpoint /health yang Baik #

Backend harus menyediakan endpoint /health yang melakukan pengecekan yang berarti:

// Contoh Node.js/Express — health endpoint yang baik
app.get('/health', async (req, res) => {
    const checks = {
        status: 'ok',
        timestamp: new Date().toISOString(),
        uptime: process.uptime(),
        checks: {}
    };
    
    try {
        // Cek koneksi database
        await db.query('SELECT 1');
        checks.checks.database = 'ok';
    } catch (err) {
        checks.checks.database = 'error';
        checks.status = 'degraded';
    }
    
    try {
        // Cek koneksi Redis
        await redis.ping();
        checks.checks.redis = 'ok';
    } catch (err) {
        checks.checks.redis = 'error';
        // Redis down tidak selalu critical
    }
    
    const statusCode = checks.status === 'ok' ? 200 : 503;
    res.status(statusCode).json(checks);
});

Monitoring Round Robin via Admin API #

# Lihat status semua upstream
curl -s http://localhost:2019/reverse_proxy/upstreams/ | jq .

# Output contoh:
# [
#   {
#     "address": "backend-1:3000",
#     "healthy": true,
#     "num_requests": 4521,
#     "fails": 0
#   },
#   {
#     "address": "backend-2:3000",
#     "healthy": false,    ← Ini unhealthy!
#     "num_requests": 4498,
#     "fails": 3
#   },
#   {
#     "address": "backend-3:3000",
#     "healthy": true,
#     "num_requests": 4539,
#     "fails": 1
#   }
# ]

# Cek distribusi request dari access log
cat /var/log/caddy/access.log | jq -r '.upstream_latency' | \
    awk '{sum+=$1; count++} END {print "avg:", sum/count, "ms"}'

Ringkasan #

  • Round robin adalah algoritma default Caddy — setiap request diarahkan ke upstream berikutnya secara bergiliran.
  • Round robin bekerja paling baik ketika semua backend homogen (spesifikasi sama) dan request bersifat stateless dengan durasi pemrosesan yang seragam.
  • Selalu tambahkan health_uri dan health_interval di production — tanpanya round robin akan terus kirim request ke backend yang sudah down.
  • Gunakan least_conn jika durasi request sangat bervariasi — round robin bisa menyebabkan beberapa backend terbebani sementara yang lain idle.
  • Untuk monitoring distribusi traffic, gunakan Admin API (/reverse_proxy/upstreams/) untuk melihat jumlah request dan status health per upstream secara real-time.

← Sebelumnya: Proxy Cache   Berikutnya: Least Connections →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact