Round Robin #
Round robin adalah algoritma load balancing default Nginx — jika kita tidak menentukan algoritma lain di dalam upstream block, inilah yang digunakan. Cara kerjanya sederhana: setiap request diteruskan ke server berikutnya dalam daftar secara bergiliran. Kesederhanaan ini membuatnya menjadi titik awal yang tepat untuk memahami load balancing di Nginx.
Cara Kerja Internal #
Nginx mempertahankan sebuah pointer yang menunjuk ke server “berikutnya” dalam upstream pool. Setiap kali request masuk, pointer bergerak ke server berikutnya. Ketika mencapai akhir daftar, pointer kembali ke awal.
sequenceDiagram
participant C as Klien
participant N as Nginx
participant A as Server A
participant B as Server B
participant CC as Server C
C->>N: Request 1
N->>A: Forward ke Server A
A->>N: Response
N->>C: Response ke klien
C->>N: Request 2
N->>B: Forward ke Server B
B->>N: Response
N->>C: Response ke klien
C->>N: Request 3
N->>CC: Forward ke Server C
CC->>N: Response
N->>C: Response ke klien
C->>N: Request 4
N->>A: Kembali ke Server A
A->>N: Response
N->>C: Response ke klienSetiap server mendapat jumlah request yang kurang lebih sama dari waktu ke waktu. Ini berasumsi bahwa setiap request memiliki beban pemrosesan yang serupa dan setiap server memiliki kapasitas yang sama — asumsi yang berlaku untuk banyak kasus, tapi tidak semua.
Konfigurasi Dasar #
# /etc/nginx/conf.d/myapp.conf
# Definisi upstream pool — daftar backend servers
upstream app_servers {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
listen 443 ssl;
http2 on;
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;
}
}
Nama upstream (app_servers) bisa berupa apapun yang valid sebagai identifier — ia hanya digunakan sebagai referensi di proxy_pass. Nginx akan mendistribusikan request ke ketiga server secara bergiliran.
Parameter Server: Mengontrol Perilaku Individual #
Setiap server dalam upstream block bisa dikonfigurasi dengan parameter tambahan yang mengontrol bagaimana ia diperlakukan dalam rotasi:
upstream app_servers {
# Server normal — ikut rotasi penuh
server 10.0.0.1:3000;
# Server yang sementara dikeluarkan dari rotasi secara manual
# Request tidak akan dikirim ke sini, tapi server tetap dihitung
# dalam hashing (penting untuk ip_hash agar tidak mengacak mapping)
server 10.0.0.2:3000 down;
# Server backup — hanya aktif jika semua server utama down/unavailable
# Ideal untuk menampilkan halaman maintenance
server 10.0.0.3:3000 backup;
# max_fails=3: setelah 3 kegagalan dalam window fail_timeout...
# fail_timeout=30s: ...selama 30 detik, server ditandai unavailable
# setelah 30 detik, Nginx coba lagi (coba 1 request)
server 10.0.0.4:3000 max_fails=3 fail_timeout=30s;
# max_conns=100: batasi jumlah koneksi simultan ke server ini
# berguna untuk melindungi server yang lebih lemah dari overload
server 10.0.0.5:3000 max_conns=100;
# Kombinasi semua parameter:
server 10.0.0.6:3000 max_fails=3 fail_timeout=30s max_conns=200;
}
Bagaimana max_fails dan fail_timeout Bekerja #
flowchart TD
REQ["Request masuk"] --> CHECK{"Server A\navailable?"}
CHECK -- "Ya" --> SEND["Kirim ke Server A"]
SEND --> RESULT{"Berhasil?"}
RESULT -- "Ya" --> OK["Response ke klien\nReset fail counter"]
RESULT -- "Tidak\nerror atau timeout" --> FAIL["Tambah fail counter\nCoba server lain via\nproxy_next_upstream"]
FAIL --> MAX{"Fail counter\n≥ max_fails?"}
MAX -- "Tidak" --> NEXT["Server A masih\ndalam rotasi"]
MAX -- "Ya" --> DOWN["Tandai Server A 'down'\nselama fail_timeout detik"]
DOWN --> WAIT["Setelah fail_timeout:\nCoba 1 request ke Server A"]
WAIT --> RESULT2{"Berhasil?"}
RESULT2 -- "Ya" --> RESTORE["Server A kembali\nke rotasi normal"]
RESULT2 -- "Tidak" --> DOWNSecara konkret:
max_fails=3 fail_timeout=30s:
[t=0s] Kegagalan ke-1 → fail counter = 1
[t=5s] Kegagalan ke-2 → fail counter = 2
[t=10s] Kegagalan ke-3 → fail counter = 3 → Server DITANDAI DOWN
[t=40s] (30s berlalu) → Nginx kirim 1 request ke server
[t=40s] Berhasil → Server kembali ke rotasi, fail counter = 0
[t=40s] Gagal → Server ditandai down lagi, tunggu 30s lagi
Default nilai jika tidak ditentukan: max_fails=1 fail_timeout=10s — satu kegagalan langsung mengeluarkan server selama 10 detik. Ini terlalu agresif untuk kebanyakan kasus, terutama jika server sesekali slow karena GC pause.
Failover: proxy_next_upstream #
Ketika Nginx gagal meneruskan request ke server yang dipilih, ia bisa mencoba server berikutnya. Perilaku ini dikontrol oleh proxy_next_upstream:
upstream app_servers {
server 10.0.0.1:3000 max_fails=2 fail_timeout=10s;
server 10.0.0.2:3000 max_fails=2 fail_timeout=10s;
server 10.0.0.3:3000 max_fails=2 fail_timeout=10s;
}
server {
location / {
proxy_pass http://app_servers;
# Kondisi yang memicu retry ke server lain
# error: koneksi gagal/ditolak
# timeout: proxy_connect_timeout atau proxy_read_timeout terlampaui
# http_502, http_503, http_504: error dari backend
proxy_next_upstream error timeout http_502 http_503 http_504;
# Batas waktu total untuk semua percobaan (semua server)
proxy_next_upstream_timeout 10s;
# Maksimum berapa kali mencoba server lain
proxy_next_upstream_tries 3;
}
}
Hati-hati dengan POST dan operasi yang mengubah data.proxy_next_upstreamakan mengirim ulang request ke server lain jika server pertama error. Jika backend sudah mulai memproses request POST sebelum mengirim error, request bisa dieksekusi dua kali (double write, double transaction). Gunakanproxy_next_upstreamdengan hati-hati untuk endpoint yang tidak idempotent.
Server Backup dan Halaman Maintenance #
Server backup hanya menerima traffic ketika semua server utama tidak tersedia. Ini adalah cara yang elegan untuk menampilkan halaman maintenance alih-alih error 502:
upstream app_servers {
server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
# Server backup: Nginx statis yang menampilkan halaman maintenance
server 10.0.0.3:8080 backup;
}
server {
# Server backup: jalankan Nginx terpisah di port 8080
# yang hanya melayani halaman statis maintenance
listen 8080;
server_name _;
root /var/www/maintenance;
location / {
try_files /maintenance.html =503;
add_header Retry-After "300" always;
}
}
Atau lebih sederhana, gunakan error_page bersama proxy_intercept_errors:
server {
location / {
proxy_pass http://app_servers;
proxy_intercept_errors on;
# Tampilkan halaman maintenance jika semua backend down (502/503)
error_page 502 503 504 /maintenance.html;
}
location = /maintenance.html {
root /var/www;
internal;
add_header Content-Type text/html;
}
}
Keepalive Pool ke Upstream #
Untuk performa tinggi, Nginx bisa mempertahankan pool koneksi keepalive ke backend daripada membuka koneksi baru untuk setiap request:
upstream app_servers {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
# Pertahankan 32 koneksi idle ke setiap server
# (total pool = 32 per server, bukan 32 keseluruhan)
keepalive 32;
# Berapa lama koneksi idle dipertahankan
keepalive_timeout 60s;
# Setelah berapa request per koneksi sebelum ditutup dan dibuka baru
keepalive_requests 1000;
}
server {
location / {
proxy_pass http://app_servers;
proxy_http_version 1.1; # Wajib untuk keepalive
proxy_set_header Connection ""; # Hapus Connection header dari klien
}
}
Dampak nyata keepalive: untuk aplikasi dengan 1000 req/s ke backend lokal, overhead TCP handshake (~1ms per koneksi) dikalikan 1000 = 1 detik overhead per detik hanya dari handshake. Dengan keepalive, overhead ini mendekati nol.
Zona Shared Memory: zone Directive #
Tanpa directive zone, setiap worker process Nginx memiliki data upstream-nya sendiri — fail counter tidak dibagi antar worker. Dengan zone, data dibagi di shared memory:
upstream app_servers {
# Buat shared memory zone bernama "app" sebesar 64k
# Data fail counter, status server, dll. dibagi antar semua worker
zone app_upstream 64k;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
keepalive 32;
}
Dengan zone, ketika worker-1 mencatat kegagalan ke Server A, worker-2 dan worker-3 juga langsung tahu. Ini membuat passive health check jauh lebih responsif — tidak perlu setiap worker mengalami kegagalan sendiri sebelum mengeluarkan server dari rotasi.
Logging dan Monitoring Round Robin #
http {
# Format log yang mencakup info backend
log_format upstream_log
'$remote_addr [$time_local] "$request" '
'$status $upstream_addr ' # IP backend yang dipilih
'$upstream_status ' # Status code dari backend
'$upstream_response_time ' # Waktu backend merespons
'$request_time';
server {
access_log /var/log/nginx/app-access.log upstream_log;
location / {
proxy_pass http://app_servers;
}
}
}
# Analisis distribusi traffic per server
awk '{print $5}' /var/log/nginx/app-access.log | sort | uniq -c | sort -rn
# Output:
# 3421 10.0.0.1:3000
# 3389 10.0.0.2:3000
# 3398 10.0.0.3:3000
# → Distribusi merata: round robin bekerja dengan baik
# Monitor error per server
awk '{print $5, $6}' /var/log/nginx/app-access.log | grep "50[0-9]" | sort | uniq -c
# → Deteksi server mana yang sering error
Decision Tree: Kapan Gunakan Round Robin #
flowchart TD
START["Mulai pilih algoritma\nload balancing"] --> Q1{"Semua server\nkapasitas sama?"}
Q1 -- "Tidak" --> WEIGHTED["Gunakan Weighted\n(sesuai kapasitas)"]
Q1 -- "Ya" --> Q2{"Request duration\nbervariasi jauh?"}
Q2 -- "Ya (ada yang\nbertahan detik/menit)" --> LEASTCONN["Gunakan Least Connections\n(adaptif terhadap beban)"]
Q2 -- "Tidak\n(konsisten < 500ms)" --> Q3{"Butuh session\npersistence?"}
Q3 -- "Ya" --> Q4{"Session di memori\nlokal server?"}
Q4 -- "Ya" --> IPHASH["Gunakan IP Hash\natau hash cookie"]
Q4 -- "Tidak\nRedis atau DB" --> RR["✓ Gunakan Round Robin\noptimal untuk kasus ini"]
Q3 -- "Tidak" --> RRMengelola Upstream Tanpa Downtime #
Perubahan di upstream block bisa diterapkan tanpa memutus koneksi yang sedang berjalan menggunakan nginx -s reload:
# Alur kerja reload yang aman:
# 1. Edit konfigurasi
vim /etc/nginx/conf.d/upstream.conf
# 2. Validasi konfigurasi sebelum diterapkan
nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# 3. Reload: menerapkan konfigurasi baru tanpa mematikan server
nginx -s reload
# Worker process lama menyelesaikan koneksi yang ada
# Worker process baru mulai dengan konfigurasi baru
# Tidak ada request yang terputus
Internally, saat nginx -s reload dijalankan:
- Master process membaca konfigurasi baru
- Master process fork worker process baru dengan konfigurasi baru
- Worker process lama diberi sinyal untuk tidak menerima request baru
- Worker process lama menyelesaikan request yang sedang diproses
- Worker process lama selesai dan mati secara graceful
Koneksi keepalive ke klien yang sudah established akan ditutup setelah keepalive_timeout — tidak ada koneksi yang di-drop paksa.
Menambah Server ke Upstream (Zero-Downtime) #
# Sebelum:
upstream app_servers {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
keepalive 32;
zone app_upstream 64k;
}
# Setelah (tambah server baru):
upstream app_servers {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000; # ← tambah baris ini
keepalive 32;
zone app_upstream 64k;
}
# nginx -t && nginx -s reload
Menghapus Server dari Upstream (Graceful Drain) #
# Step 1: Tandai sebagai down (hentikan request baru)
upstream app_servers {
server 10.0.0.1:3000;
server 10.0.0.2:3000 down; # ← tidak terima request baru
keepalive 32;
zone app_upstream 64k;
}
# nginx -t && nginx -s reload
# Tunggu request yang sedang berjalan selesai (cek log)
# tail -f /var/log/nginx/access.log | grep "10\.0\.0\.2"
# Step 2: Hapus dari konfigurasi setelah yakin tidak ada request aktif
upstream app_servers {
server 10.0.0.1:3000;
# server 10.0.0.2:3000; ← sudah bisa dihapus
keepalive 32;
zone app_upstream 64k;
}
# nginx -t && nginx -s reload
Tuning Nginx untuk Throughput Tinggi dengan Round Robin #
Untuk production dengan traffic tinggi, beberapa pengaturan Nginx perlu disesuaikan agar load balancing berjalan optimal:
# /etc/nginx/nginx.conf
worker_processes auto; # Satu worker per CPU core
worker_rlimit_nofile 65535; # Limit file descriptor per worker
events {
worker_connections 4096; # Maksimum koneksi simultan per worker
use epoll; # Event model efisien di Linux
multi_accept on; # Accept multiple connections sekaligus
}
http {
upstream app_servers {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
keepalive 64;
keepalive_requests 10000;
zone app_upstream 128k;
}
# Nonaktifkan access log di endpoint yang sangat sering diakses
# (misalnya health check yang dipanggil setiap detik)
location /health {
proxy_pass http://app_servers;
access_log off;
}
# Aktifkan sendfile untuk serving file statis
sendfile on;
tcp_nopush on;
tcp_nodelay on;
}
Menghitung Kapasitas Teoritis #
Kapasitas maksimum Nginx (teoritis):
worker_processes = 4 (4 core)
worker_connections = 4096
Total koneksi simultan = 4 × 4096 = 16.384
(termasuk koneksi klien DAN koneksi ke backend)
Untuk reverse proxy, setiap request pakai 2 koneksi
(klien → Nginx, Nginx → backend)
Total request simultan = 16.384 / 2 = 8.192
Dengan keepalive pool (koneksi backend di-reuse):
Total request simultan jauh lebih tinggi
karena koneksi ke backend tidak membuka/tutup setiap request
Kesalahan Umum yang Harus Dihindari #
# ✗ SALAH: Tidak ada zone directive — fail counter tidak sinkron antar worker
upstream app_servers {
server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
# Tidak ada zone!
}
# Worker proses yang berbeda masing-masing punya fail counter sendiri.
# Butuh max_fails × jumlah_worker kegagalan sebelum server ditandai down
# ✓ BENAR: Dengan zone directive
upstream app_servers {
zone app_upstream 64k; # Shared memory antar semua worker
server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
}
# ✗ SALAH: Lupa proxy_http_version 1.1 saat pakai keepalive
upstream app_servers {
server 10.0.0.1:3000;
keepalive 32;
}
server {
location / {
proxy_pass http://app_servers;
# Tidak ada proxy_http_version 1.1!
# Keepalive tidak akan bekerja dengan HTTP/1.0 (default)
}
}
# ✓ BENAR: Selalu sertakan proxy_http_version 1.1 saat pakai keepalive
server {
location / {
proxy_pass http://app_servers;
proxy_http_version 1.1; # ← Wajib untuk keepalive
proxy_set_header Connection ""; # ← Hapus Connection header dari klien
}
}
Ringkasan #
- Round robin adalah algoritma default Nginx — tidak perlu directive tambahan, cukup daftar server di upstream block.
- Parameter
max_failsdanfail_timeoutmengontrol passive health check — defaultmax_fails=1 fail_timeout=10sseringkali terlalu agresif; setmax_fails=3 fail_timeout=30ssebagai titik awal yang lebih wajar.- Server
backuphanya aktif ketika semua server utama down — ideal untuk halaman maintenance yang informatif.proxy_next_upstreammemungkinkan retry ke server lain, tapi hati-hati untuk request POST yang tidak idempotent — bisa menyebabkan double processing.- Gunakan
zonedirective agar data fail counter dan status server dibagi di antara semua worker process Nginx.- Tambahkan
keepalivedi upstream block untuk menghindari overhead TCP handshake berulang-ulang ke backend.