IP Hash

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.

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"
        }
    }
}
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_hash memastikan 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-ID untuk API yang sudah mengirim user identifier di header — lebih reliable dari ip_hash untuk microservices.

← Sebelumnya: Least Connections   Berikutnya: Weighted →

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