IP Hash #
IP hash adalah algoritma load balancing yang memastikan semua request dari IP address yang sama selalu diarahkan ke backend yang sama. Ini disebut sticky session atau session persistence — seorang user selalu “menempel” ke satu backend selama IP mereka tidak berubah.
Algoritma ini menjawab kebutuhan spesifik: aplikasi yang menyimpan state session di memori server. Ketika user login dan session-nya disimpan di memori backend-1, semua request berikutnya dari user itu harus tetap ke backend-1 — jika diarahkan ke backend-2 atau backend-3, session tidak akan ditemukan.
Cara Kerja IP Hash #
IP client di-hash menggunakan fungsi hash deterministik:
hash("203.0.113.1") % 3 = 0 → backend-1
hash("198.51.100.5") % 3 = 1 → backend-2
hash("192.0.2.42") % 3 = 2 → backend-3
Karena hash adalah deterministik:
203.0.113.1 SELALU ke backend-1
198.51.100.5 SELALU ke backend-2
192.0.2.42 SELALU ke backend-3
Jika backend-2 down (dihapus dari pool):
hash("198.51.100.5") % 2 = 0 → backend-1 (berubah!)
Ini adalah keterbatasan IP hash: jika jumlah backend berubah,
sebagian besar user akan "pindah" ke backend berbeda → session hilang
Konfigurasi IP Hash #
example.com {
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
lb_policy ip_hash
}
}
# Dengan health check
example.com {
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
lb_policy ip_hash
health_uri /health
health_interval 10s
health_timeout 5s
}
}
Masalah dengan IP Hash: NAT dan Proxy #
IP hash memiliki keterbatasan serius dalam lingkungan tertentu:
Masalah 1: NAT (Network Address Translation)
Sebuah perusahaan dengan 500 karyawan semua berbagi 1 IP publik:
Employee-1 (192.168.1.10) → NAT → 203.0.113.1 → backend-1
Employee-2 (192.168.1.11) → NAT → 203.0.113.1 → backend-1
Employee-3 (192.168.1.12) → NAT → 203.0.113.1 → backend-1
...
Employee-500 → NAT → 203.0.113.1 → backend-1 (semua!)
Hasil: backend-1 mendapat semua traffic dari perusahaan tsb.
backend-2 dan backend-3 idle untuk user dari perusahaan ini.
Masalah 2: IP Dinamis (Mobile Users)
User mobile yang pindah dari WiFi ke 4G mendapat IP baru:
Di WiFi: 203.0.113.1 → backend-1 (session ada di backend-1)
Di 4G: 198.51.100.5 → backend-2 (session TIDAK ada di backend-2!)
User terpaksa login ulang karena session hilang.
Masalah 3: IPv6 dan Prefix
IPv6 address sangat panjang dan bisa berubah (privacy extensions)
Pengguna yang sama bisa muncul dengan IPv6 address berbeda.
Cookie-Based Sticky Session: Lebih Reliable #
Untuk sticky session yang lebih reliable dari ip_hash, gunakan cookie-based approach:
example.com {
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
lb_policy cookie {
# Nama cookie yang akan di-set di browser
name caddy_backend
# Secret untuk HMAC signing cookie (cegah tampering)
# GANTI dengan string acak yang panjang di production!
secret "super-secret-key-change-in-production-abc123xyz"
}
}
}
Cara Kerja Cookie-Based Sticky Session #
Request pertama (user belum punya cookie):
GET / HTTP/1.1
Host: example.com
Caddy → pilih backend berdasarkan lb_policy lain (round_robin/least_conn)
→ misalnya backend-2 yang dipilih
Response:
HTTP/1.1 200 OK
Set-Cookie: caddy_backend=HMAC_SIGNED_TOKEN_backend-2; HttpOnly; Secure; SameSite=Strict
Request berikutnya (user sudah punya cookie):
GET /dashboard HTTP/1.1
Host: example.com
Cookie: caddy_backend=HMAC_SIGNED_TOKEN_backend-2
Caddy → baca cookie → decode → backend-2
→ SELALU ke backend-2 selama cookie valid
Keunggulan cookie vs ip_hash:
✓ Bekerja benar di belakang NAT (masing-masing user punya cookie sendiri)
✓ Tidak terpengaruh perubahan IP (mobile, VPN)
✓ Cookie bisa di-set dengan expiry yang tepat
✓ HMAC signing mencegah user memalsukan backend tujuan
✓ Jika backend down, Caddy bisa pilih backend lain dan set cookie baru
Keterbatasan cookie:
✗ Request yang tidak mendukung cookie (API tanpa browser) tidak dapat sticky
✗ Perlu menyimpan mapping di cookie (menambah overhead sedikit)
✗ Private browsing / cookie disabled → tidak bisa sticky
Header-Based Sticky Session #
Untuk API (bukan browser), sticky session bisa berbasis header:
api.example.com {
reverse_proxy backend-1:8080 backend-2:8080 backend-3:8080 {
# Hash berdasarkan header User-ID yang dikirim oleh client API
lb_policy header X-User-ID
}
}
Cara kerja:
Request dari user-123:
GET /api/cart HTTP/1.1
X-User-ID: user-123
hash("user-123") % 3 = 1 → backend-2
SEMUA request dengan X-User-ID: user-123 selalu ke backend-2
User berbeda → backend berbeda
Keunggulan vs ip_hash:
✓ Bekerja benar di belakang NAT (identifikasi per user, bukan per IP)
✓ Stabil meski IP berubah
✓ Cocok untuk API yang sudah mengirim user identifier di header
Strategi Terbaik untuk Aplikasi Stateful #
Daripada mengandalkan sticky session, solusi yang lebih scalable adalah membuat aplikasi stateless:
Pendekatan 1 (tidak ideal): Sticky session via ip_hash atau cookie
- Ketika backend down → user yang "stuck" ke backend itu kehilangan session
- Scaling up/down menyebabkan redistribusi user yang tidak terduga
- Session hilang jika deploy ulang backend
Pendekatan 2 (lebih baik): External session store
- Simpan session di Redis atau database, bukan di memori backend
- Semua backend bisa membaca session yang sama
- Scaling dan failover tidak menyebabkan session hilang
- Load balancer bisa menggunakan algoritma apapun (round_robin, least_conn)
Arsitektur:
Client → Caddy (round_robin) → Backend-1 ──► Redis (session store)
→ Backend-2 ──► Redis
→ Backend-3 ──► Redis
// Contoh: Express dengan session di Redis
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const Redis = require('ioredis');
const redis = new Redis({ host: 'redis', port: 6379 });
app.use(session({
store: new RedisStore({ client: redis }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // Hanya HTTPS
httpOnly: true,
maxAge: 86400000 // 24 jam
}
}));
// Sekarang session tersimpan di Redis, bukan di memori
// Caddy bisa pakai round_robin atau least_conn
IP Hash saat Backend Berubah #
Perubahan jumlah backend menyebabkan redistribusi yang signifikan dengan IP hash klasik:
Dengan 3 backend (hash % 3):
IP-1 → backend-1
IP-2 → backend-2
IP-3 → backend-3
Tambah backend-4 (hash % 4):
IP-1 → backend-1 (tidak berubah)
IP-2 → backend-3 (BERUBAH! sebelumnya backend-2)
IP-3 → backend-4 (BERUBAH! sebelumnya backend-3)
IP-4 → backend-1 (dst...)
~75% user akan "pindah" backend → session hilang massal!
Solusi yang lebih baik adalah Consistent Hashing (tidak built-in di Caddy standar, tersedia di beberapa load balancer khusus). Tapi untuk sebagian besar use case, cookie-based sticky session tetap lebih reliable.
Kapan IP Hash Masih Masuk Akal #
Meski memiliki keterbatasan, ip_hash masih berguna dalam kondisi tertentu:
✓ Internal API di mana semua client adalah server (bukan browser)
- IP server internal stabil dan tidak berganti-ganti
- Tidak ada masalah NAT yang signifikan
✓ Development/staging environment di mana IP stabil
✓ Ketika kamu tidak bisa menggunakan cookie (gRPC, WebSocket murni, CLI tools)
dan tidak ada header user identifier yang bisa digunakan
✓ Cache warming: pastikan request untuk key yang sama selalu ke backend yang sama
sehingga in-memory cache backend lebih efektif (uri_hash lebih tepat untuk ini)
URI Hash untuk Cache Consistency #
Selain ip_hash, Caddy mendukung uri_hash yang mendistribusikan berdasarkan URI request — bukan IP client:
example.com {
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
# Request ke /api/products/123 SELALU ke backend yang sama
# Berguna untuk aplikasi dengan in-memory cache per endpoint
lb_policy uri_hash
}
}
URI hash berguna ketika backend menyimpan hasil komputasi mahal di memori (misal: rendered HTML, hasil query berat). Dengan memastikan URI yang sama selalu ke backend yang sama, cache hit rate meningkat drastis — backend-1 meng-cache /products, backend-2 meng-cache /blog, dan seterusnya.
Ringkasan #
ip_hashmemastikan IP yang sama selalu ke backend yang sama — berguna untuk aplikasi stateful yang menyimpan session di memori.- Keterbatasan serius: pengguna di belakang NAT (perusahaan, mobile carrier) semua mendapat IP yang sama → semua diarahkan ke satu backend saja.
- Cookie-based sticky session (
lb_policy cookie { ... }) jauh lebih reliable dari ip_hash — bekerja per browser, bukan per IP, sehingga benar di belakang NAT.- Solusi terbaik untuk aplikasi stateful: simpan session di Redis atau database external agar semua backend bisa akses session yang sama — eliminasi kebutuhan sticky session sepenuhnya.
- Ketika jumlah backend berubah, ip_hash menyebabkan redistribusi massal (~75% user pindah backend) → session hilang. Cookie-based sticky dapat memitigasi ini lebih baik.
- Gunakan
lb_policy header X-User-IDuntuk API yang sudah mengirim user identifier di header — lebih reliable dari ip_hash untuk microservices.