IP Hash #

IP hash adalah algoritma load balancing yang memastikan request dari IP address yang sama selalu diarahkan ke server yang sama. Ini mengimplementasikan session persistence (atau sticky sessions) — kemampuan agar satu user terus terhubung ke server yang sama sepanjang sesi mereka, tanpa bergantung pada mekanisme cookie khusus.

Mengapa Session Persistence Dibutuhkan #

Tidak semua aplikasi membutuhkan session persistence. Faktanya, kebutuhan akan ini seringkali adalah gejala dari masalah arsitektur yang lebih dalam. Tapi ada kasus di mana ini genuinely diperlukan:

Aplikasi dengan session berbasis memori lokal. Framework lama (PHP sessions default, beberapa implementasi Express.js) menyimpan data sesi di memori server lokal. Jika request dari user yang sama sampai ke server yang berbeda, server itu tidak punya data sesinya.

Upload file multi-part. Jika user mengupload file besar dalam beberapa bagian (multipart upload, chunked upload), semua bagian harus ke server yang sama agar bisa digabungkan dengan benar. Server yang berbeda tidak punya chunk sebelumnya.

Proses wizard multi-langkah. Checkout, onboarding wizard, atau proses multi-step yang menyimpan state sementara di antara request tanpa database.

In-memory caching yang bersifat personal. Cache yang spesifik per-user dan disimpan di memori server — bukan di Redis — butuh user yang sama ke server yang sama agar cache hit.


Konfigurasi ip_hash #

upstream app_servers {
    ip_hash;   # Aktifkan session persistence berbasis IP

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;

    zone app_upstream 64k;
}

server {
    listen 443 ssl;
    server_name example.com;

    location / {
        proxy_pass         http://app_servers;
        proxy_http_version 1.1;
        proxy_set_header   Connection    "";
        proxy_set_header   Host          $host;
        proxy_set_header   X-Real-IP     $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

Nginx menghitung hash dari 3 oktet pertama IP address klien ($remote_addr), bukan IP penuh. Jadi 203.0.113.1 dan 203.0.113.254 akan di-hash ke server yang sama. Ini memberi sedikit toleransi untuk pengguna dengan IP yang berubah dalam subnet yang sama.

Bagaimana Hash Dihitung #

IP klien: 203.0.113.42

Nginx mengambil 3 oktet pertama: 203.0.113

Hash("203.0.113") → nilai numerik

nilai % jumlah_server = indeks server

Selama jumlah server tidak berubah, 203.0.113.x SELALU
→ server yang sama

Menonaktifkan Server Sementara dengan ip_hash #

Ketika menggunakan ip_hash, cara menonaktifkan server sangat berbeda dari round robin biasa. Jangan hapus server dari konfigurasi — gunakan parameter down:

upstream app_servers {
    ip_hash;

    server 10.0.0.1:3000;
    server 10.0.0.2:3000 down;   # ← Gunakan ini, bukan hapus baris
    server 10.0.0.3:3000;
}

Mengapa ini penting? Karena ip_hash menentukan server berdasarkan hash % jumlah_server. Jika server dihapus dari daftar, jumlah server berkurang — seluruh mapping berubah. Semua user yang sebelumnya ke Server A mungkin sekarang ke Server B atau C.

Dengan down, server tetap dihitung dalam kalkulasi hash (menjaga mapping tetap konsisten), tapi Nginx tidak mengirim request ke sana. User yang seharusnya ke server down akan dialihkan ke server lain secara otomatis.

flowchart LR
    subgraph BEFORE["Sebelum: 3 server aktif"]
        IP1["IP 203.0.113.x → Server A"]
        IP2["IP 198.51.100.x → Server B"]
        IP3["IP 192.0.2.x → Server C"]
    end

    subgraph WRONG["❌ Hapus Server B dari konfigurasi"]
        W1["IP 203.0.113.x → Server A (ok)"]
        W2["IP 198.51.100.x → Server A atau C (BERUBAH!)"]
        W3["IP 192.0.2.x → Server A atau C (BERUBAH!)"]
    end

    subgraph RIGHT["✓ Tandai Server B sebagai down"]
        R1["IP 203.0.113.x → Server A (tetap sama)"]
        R2["IP 198.51.100.x → Server A atau C (dialihkan karena B down)"]
        R3["IP 192.0.2.x → Server C (tetap sama)"]
    end

Keterbatasan IP Hash #

IP hash memiliki beberapa keterbatasan yang wajib dipahami sebelum mengandalkannya:

1. NAT dan Shared IP #

Semua pengguna yang berada di balik NAT yang sama berbagi satu IP publik. Ini termasuk:

  • Semua karyawan di satu kantor
  • Pengguna di balik router ISP yang melakukan CGNAT
  • Pengguna di balik CDN atau proxy (IP CDN, bukan IP klien asli)
Kantor A (100 karyawan) → semua punya IP publik 203.0.113.1
→ Semua 100 karyawan selalu ke Server A
→ Server A overload, B dan C menganggur

2. Nginx di Balik CDN #

Jika Nginx berada di balik Cloudflare atau CDN lain, $remote_addr adalah IP CDN, bukan IP klien asli. Semua request dari CDN akan hash ke server yang sama karena IP-nya sama.

# Solusi: gunakan IP klien asli dari header CDN
# Dengan set_real_ip_from (perlu konfigurasi tambahan)
set_real_ip_from 103.21.244.0/22;  # IP range Cloudflare
real_ip_header CF-Connecting-IP;

# Setelah ini $remote_addr berisi IP klien asli
# ip_hash akan bekerja dengan IP yang benar

3. Pengguna Mobile #

Pengguna mobile sering berganti IP saat berpindah jaringan (WiFi → 4G → WiFi). Setiap pergantian IP memutus session persistence mereka — request berikutnya bisa ke server yang berbeda.

4. Distribusi Tidak Merata #

Tidak ada jaminan distribusi merata. Distribusi bergantung pada variasi IP yang mengakses aplikasi — dan di internet, distribusi IP tidak uniform.


Alternatif yang Lebih Baik: Hash Directive #

Directive hash memberikan fleksibilitas penuh dalam memilih variabel yang dijadikan basis session persistence:

upstream app_servers {
    # Lebih andal dari ip_hash karena tidak bergantung pada IP
    # Cookie session unik per user, tidak terpengaruh NAT atau mobile
    hash $cookie_session_id consistent;

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;
}

Hash Berdasarkan Header Custom #

upstream app_servers {
    # Jika aplikasi mengirim User-ID dalam header khusus
    hash $http_x_user_id consistent;

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

Hash Berdasarkan Kombinasi Variable #

upstream app_servers {
    # Kombinasi IP + User-Agent — lebih baik dari IP saja untuk NAT
    # (pengguna berbeda di NAT yang sama biasanya punya User-Agent berbeda)
    hash "$remote_addr$http_user_agent" consistent;

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

Parameter consistent: Consistent Hashing #

Parameter consistent menggunakan algoritma consistent hashing (ring/kettle hash). Perbedaannya dari hash biasa:

Hash biasa: server = hash(key) % jumlah_server
  → Jika jumlah server berubah, SEMUA mapping berubah
  → Semua user berpindah server

Consistent hashing: server ditentukan berdasarkan posisi di ring
  → Jika 1 server ditambah/dihapus, hanya ~1/N user yang berpindah
  → N-1 user lainnya tetap ke server yang sama

Ini sangat berguna untuk:

  • Cache server — meminimalkan cache miss saat scaling
  • Situasi di mana perpindahan server harus dihindari
  • Upstream yang sering berubah (auto-scaling)

Arsitektur yang Menghilangkan Kebutuhan Session Persistence #

Ketergantungan pada session persistence adalah tanda arsitektur yang bisa diperbaiki. Solusi jangka panjang:

Pindahkan Session ke Redis #

# Tidak perlu ip_hash lagi setelah ini
upstream app_servers {
    # Round robin atau least_conn — semua server bisa layani siapa saja
    least_conn;

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;

    keepalive 32;
}
// Node.js: simpan session di Redis, bukan memori lokal
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis').createClient({ url: 'redis://redis:6379' });

app.use(session({
    store: new RedisStore({ client: redis }),
    secret: 'my-secret',
    resave: false,
    saveUninitialized: false,
}));
// Sekarang semua server berbagi session yang sama via Redis
// ip_hash tidak diperlukan lagi

JWT untuk Autentikasi Stateless #

# Dengan JWT, tidak ada yang perlu disimpan di server
# Setiap request membawa semua informasi yang diperlukan
upstream app_servers {
    round robin;  # atau least_conn, keduanya ok

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}
// Backend: verifikasi JWT tanpa menyentuh database/session store
app.use((req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    try {
        req.user = jwt.verify(token, process.env.JWT_SECRET);
        next();
    } catch (e) {
        res.status(401).json({ error: 'Unauthorized' });
    }
});

Kapan ip_hash Masih Layak Digunakan #

Meskipun banyak keterbatasannya, ada situasi di mana ip_hash adalah pilihan yang masuk akal:

✓ Aplikasi legacy yang tidak bisa dimodifikasi untuk Redis session
✓ Semua pengguna memiliki IP publik yang berbeda (tidak ada NAT bersama)
✓ Nginx tidak di balik CDN (ip langsung ke pengguna)
✓ Tidak ada kebutuhan scaling horizontal yang sering
✓ Konsistensi distribusi tidak kritis (beberapa server mendapat lebih banyak ok)

Debugging Session Persistence #

Ketika session persistence tidak bekerja seperti yang diharapkan, gunakan langkah-langkah berikut untuk mendiagnosis masalah:

# 1. Verifikasi ip klien yang diterima Nginx
# Tambahkan header debug sementara
# add_header X-Real-IP $remote_addr always;
# add_header X-Forwarded-For $proxy_add_x_forwarded_for always;
curl -sI https://example.com/ | grep -i "x-real\|x-forwarded"

# 2. Cek apakah Nginx di balik CDN — jika ada, $remote_addr adalah IP CDN
# Output seharusnya IP klien, bukan IP Cloudflare (103.x.x.x)

# 3. Verifikasi ke server mana request diarahkan
# add_header X-Upstream-Addr $upstream_addr always;
curl -sI https://example.com/ | grep -i "x-upstream"
# Jalankan beberapa kali dari IP yang sama — harus selalu ke server yang sama

# 4. Cek dari IP yang berbeda — harus ke server yang berbeda
curl -sI --interface eth1 https://example.com/ | grep -i "x-upstream"
# Konfigurasi debug sementara (HAPUS setelah debugging selesai)
server {
    location / {
        proxy_pass http://app_servers;

        # Header debug — tunjukkan ke klien
        add_header X-Real-Client-IP  $remote_addr      always;
        add_header X-Upstream-Server $upstream_addr    always;
        add_header X-Request-ID      $request_id       always;
    }
}

Jika menggunakan Nginx Plus (berbayar), tersedia method session persistence yang lebih canggih menggunakan cookie khusus yang diinsert Nginx:

# Nginx Plus only
upstream app_servers {
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;

    # Nginx Plus insert cookie "SERVERID" ke response
    # Klien mengirim cookie ini kembali di request berikutnya
    # Nginx membaca cookie untuk menentukan server tujuan
    sticky cookie SERVERID expires=1h domain=.example.com path=/;
}

Keunggulan dibanding ip_hash:

  • Tidak terpengaruh oleh NAT — setiap browser punya cookie unik
  • Tidak bermasalah saat pengguna berpindah IP
  • Cookie bisa di-set dengan expiry yang tepat
  • Distribusi lebih merata karena satu cookie = satu binding

Alternatif open-source: gunakan hash $cookie_session_id consistent (sudah dibahas di atas) — tidak secanggih sticky cookie Nginx Plus, tapi tidak memerlukan lisensi berbayar.


Tabel Perbandingan Metode Session Persistence #

MetodeOpen-SourceAndal vs NATAndal vs MobileDistribusi Merata
ip_hashYaTidakTidakTidak dijamin
hash $cookie_session_idYaYaYa (selama cookie ada)Lebih baik
hash $http_x_user_idYaYaYaYa (jika user ID unik)
Sticky Cookie (Nginx Plus)TidakYaYaYa
Redis Session StoreYa (perlu kode)YaYaN/A (stateless)

Jika saat ini menggunakan ip_hash dan ingin migrasi ke solusi yang lebih andal berbasis cookie, lakukan secara bertahap untuk menghindari memutus sesi pengguna yang sedang aktif:

# Langkah 1: Deploy versi aplikasi yang bisa set cookie session_id
# Versi baru harus men-set cookie sebelum Nginx membaca cookie tersebut

# Langkah 2: Update konfigurasi Nginx untuk membaca cookie
# (ip_hash masih aktif sebagai fallback untuk user tanpa cookie)
upstream app_servers {
    # Tahap transisi: gunakan cookie jika ada, fallback ke IP jika tidak
    # (ini tidak bisa dilakukan secara langsung di Nginx open-source,
    #  tapi bisa dengan pendekatan bertahap)

    # SEBELUM: hanya ip_hash
    # ip_hash;

    # SESUDAH: hash cookie
    hash $cookie_session_id consistent;

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;
}
# Strategi transisi yang lebih aman:
# Gunakan dua upstream terpisah dan split traffic berdasarkan keberadaan cookie
map $cookie_session_id $upstream_pool {
    ""       ip_hash_backend;    # Tidak ada cookie → gunakan ip_hash
    default  cookie_backend;     # Ada cookie → gunakan hash cookie
}

upstream ip_hash_backend {
    ip_hash;
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

upstream cookie_backend {
    hash $cookie_session_id consistent;
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

server {
    location / {
        # Route berdasarkan keberadaan cookie
        proxy_pass http://$upstream_pool;
    }
}

Setelah semua user memiliki cookie session (biasanya setelah beberapa hari atau setelah semua sesi lama expired), hapus upstream ip_hash_backend dan buat semua traffic ke cookie_backend.

Catatan Penting Migrasi: Saat melakukan migrasi dari ip_hash ke hash cookie, pastikan aplikasi sudah men-set cookie session sebelum mengubah konfigurasi Nginx. Jika Nginx mulai membaca $cookie_session_id sebelum cookie ada, semua request tanpa cookie akan di-hash dengan nilai kosong — mengakibatkan semua user yang tidak punya cookie diarahkan ke server yang sama.

Perilaku ip_hash untuk IPv4 vs IPv6 #

Perlu dipahami bahwa perilaku ip_hash berbeda untuk IPv4 dan IPv6:

IPv4 (203.0.113.42):
  Nginx mengambil 3 oktet pertama: 203.0.113
  Hash(203.0.113) → server
  Artinya: semua IP dalam /24 yang sama → server yang sama

IPv6 (2001:db8::1):
  Nginx mengambil seluruh IP address
  Hash(2001:db8::1) → server
  Tidak ada pengelompokan per subnet

Di era IPv6 yang semakin luas, perilaku ini bisa menyebabkan distribusi yang lebih baik (karena tidak ada pengelompokan /24 seperti IPv4). Tapi juga berarti pengguna IPv6 yang berganti IP (misalnya karena ISP menggunakan prefix delegation yang berubah) akan kehilangan session persistence.

# Untuk menangani mixed IPv4/IPv6 traffic dengan session persistence:
# Gunakan hash cookie yang tidak bergantung pada IP sama sekali
upstream app_servers {
    hash $cookie_session_id consistent;

    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

# Pastikan aplikasi men-set cookie sebelum request pertama ke upstream:
# Set cookie di Nginx jika belum ada (untuk user yang belum punya session)
map $cookie_session_id $new_session_id {
    ""      $request_id;  # Generate ID dari request_id jika belum ada
    default $cookie_session_id;
}

Ringkasan #

  • ip_hash memastikan request dari IP yang sama selalu ke server yang sama — berguna untuk aplikasi dengan session di memori lokal.
  • Gunakan parameter down (bukan hapus server) untuk menonaktifkan server sementara tanpa mengacak semua mapping hash.
  • Keterbatasan kritis: NAT (banyak user berbagi IP → overload satu server), CDN (IP CDN bukan IP klien), pengguna mobile (ganti IP → session terputus).
  • hash $cookie_session_id consistent adalah alternatif yang jauh lebih andal — tidak bergantung pada IP, lebih stabil saat scaling.
  • Parameter consistent menggunakan consistent hashing ring — hanya sebagian kecil user yang berpindah server ketika server ditambah atau dihapus.
  • Solusi terbaik jangka panjang: pindahkan session ke Redis dan buat aplikasi stateless — session persistence tidak lagi diperlukan sama sekali.

← Sebelumnya: Least Connections   Berikutnya: Weighted Load Balancing →

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