Least Connections #

Least connections adalah algoritma load balancing yang mengirimkan setiap request baru ke server dengan jumlah koneksi aktif paling sedikit saat itu. Berbeda dengan round robin yang hanya bergiliran tanpa memperhatikan kondisi aktual server, least connections lebih adaptif — ia secara dinamis menyesuaikan distribusi berdasarkan beban nyata yang sedang ditanggung masing-masing server.

Masalah yang Diselesaikan Round Robin #

Bayangkan skenario ini di mana round robin mulai bermasalah:

Server pool: A, B, C — semua round robin biasa

t=0: Request "export CSV besar" → Server A (mulai proses, butuh 30 detik)
t=1: Request biasa → Server B (selesai 50ms)
t=2: Request biasa → Server C (selesai 50ms)
t=3: Request biasa → Server A  ← round robin giliran A lagi!
                                  Tapi A masih sibuk proses export!
t=4: Request biasa → Server B
t=5: Request biasa → Server C
t=6: Request biasa → Server A  ← A masih sibuk!
...

Selama 30 detik, sepertiga dari semua request dikirim ke Server A yang sedang sibuk memproses satu export besar. Pengguna yang request-nya jatuh ke Server A mengalami latensi tinggi atau timeout.

Dengan least connections, skenario yang sama:

t=0: Request "export CSV besar" → Server A (1 koneksi aktif)
t=1: Request biasa → Server B (0 koneksi aktif) ← least conn pilih B
t=2: Request biasa → Server C (0 koneksi aktif) ← atau C
t=3: Request biasa → Server B atau C             ← A punya 1, B/C punya 0
t=4: Semua request terus ke B dan C selama A sibuk
...
Server A selesai export → koneksi = 0, kembali menerima request baru

Konfigurasi #

upstream app_servers {
    # Cukup tambahkan directive ini untuk mengaktifkan least connections
    least_conn;

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

    # Semua parameter lain tetap berlaku
    keepalive 32;
    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;
    }
}

Cara Nginx Memilih Server #

Saat request baru masuk, Nginx melihat jumlah koneksi aktif ke setiap server dan memilih yang paling sedikit:

flowchart TD
    REQ["Request baru masuk"] --> READ["Baca jumlah koneksi aktif\ndi semua server"]
    READ --> TABLE["Server A: 12 koneksi\nServer B: 3 koneksi\nServer C: 8 koneksi"]
    TABLE --> MIN{"Server mana\njumlah koneksi\npaling sedikit?"}
    MIN --> B["Server B (3 koneksi)\n← pilih ini"]
    B --> SEND["Kirim request ke Server B\nKoneksi B menjadi 4"]
    SEND --> DONE["Setelah request selesai:\nKoneksi B kembali ke 3"]

Yang dihitung sebagai koneksi aktif:

  • Request yang sedang diproses backend (response belum dikirim)
  • Koneksi keepalive yang sedang menunggu request berikutnya

Tiebreaker: Jika beberapa server memiliki jumlah koneksi yang sama persis, Nginx menggunakan weighted round robin di antara mereka sebagai tiebreaker.


Least Connections dengan Weight #

least_conn bisa dikombinasikan dengan weight untuk memperhitungkan server dengan kapasitas berbeda:

upstream app_servers {
    least_conn;

    # Server kuat: anggap "sibuk" setelah 3x lebih banyak koneksi
    server 10.0.0.1:3000 weight=3;

    # Server lemah: anggap "sibuk" lebih cepat
    server 10.0.0.2:3000 weight=1;
}

Dengan weight, formula pemilihan menjadi koneksi_aktif / weight. Server dipilih berdasarkan nilai ini yang paling kecil:

Situasi: Server A (weight=3) punya 12 koneksi aktif
         Server B (weight=1) punya 3 koneksi aktif

Nilai efektif:
  Server A: 12/3 = 4
  Server B:  3/1 = 3

Pilih Server A? Tidak → nilai A (4) > nilai B (3)
Pilih Server B? Ya   → nilai B (3) < nilai A (4)

Padahal secara absolut Server A punya 12 koneksi dan Server B hanya 3.
Tapi secara proporsional terhadap kapasitas, Server B lebih "sibuk".

Perbandingan Mendalam: Round Robin vs Least Connections #

flowchart LR
    subgraph RR["Round Robin"]
        direction TB
        R1["Request 1 → A"]
        R2["Request 2 → B"]
        R3["Request 3 → C"]
        R4["Request 4 → A (walaupun A masih sibuk!)"]
    end

    subgraph LC["Least Connections"]
        direction TB
        L1["Request 1 → A (A: 1 koneksi)"]
        L2["Request 2 → B (B: 0 koneksi)"]
        L3["Request 3 → C (C: 0 koneksi)"]
        L4["Request 4 → B atau C (A masih 1, B dan C sudah 0)"]
    end
AspekRound RobinLeast Connections
DistribusiMerata secara giliranMerata secara beban aktual
AdaptifTidakYa — otomatis hindari server sibuk
KompleksitasSangat sederhanaSedikit lebih kompleks
Request cepat (<100ms)Hampir sama hasilnyaHampir sama hasilnya
Request lambat (>1 detik)Bisa terjadi “penumpukan”Otomatis terhindar
WebSocketTidak idealLebih baik — handle long-lived conn
Cocok untukRequest seragam, cepatRequest bervariasi durasinya

Skenario Ideal untuk Least Connections #

1. API dengan Endpoint Campuran #

# Aplikasi yang punya endpoint cepat dan lambat sekaligus:
# GET /products → 20ms (query sederhana)
# GET /analytics/report → 10 detik (query berat)
# POST /export → 30 detik (generate file)

upstream api_backend {
    least_conn;  # Otomatis hindari server yang sedang proses report/export

    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 10.0.0.3:3000 max_fails=3 fail_timeout=30s;

    keepalive 32;
    zone api_upstream 64k;
}

2. WebSocket dan Koneksi Long-Lived #

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    upstream ws_backend {
        least_conn;  # WebSocket bisa bertahan jam-jaman → least conn sangat membantu

        server 10.0.0.1:4000;
        server 10.0.0.2:4000;
        server 10.0.0.3:4000;

        keepalive 16;
        zone ws_upstream 32k;
    }

    server {
        location /ws/ {
            proxy_pass          http://ws_backend;
            proxy_http_version  1.1;
            proxy_set_header    Upgrade    $http_upgrade;
            proxy_set_header    Connection $connection_upgrade;
            proxy_read_timeout  3600s;
        }
    }
}

Dengan WebSocket, satu koneksi bisa bertahan berjam-jam. Round robin bisa menyebabkan satu server penuh dengan koneksi WebSocket lama sementara server lain kosong karena “giliran” request WebSocket baru terus ke server yang berbeda. Least connections otomatis menyeimbangkan ini.

3. Microservices dengan Beban Berbeda #

# Service A: API ringan
upstream service_a {
    # round robin cukup — semua request cepat
    server 10.0.1.1:3000;
    server 10.0.1.2:3000;
    keepalive 32;
}

# Service B: Proses berat (video transcoding, ML inference, dll)
upstream service_b {
    least_conn;  # Wajib — durasi per request sangat bervariasi (1s - 5 menit)

    server 10.0.2.1:3001;
    server 10.0.2.2:3001;
    keepalive 8;
}

Monitoring Distribusi Koneksi #

# Lihat koneksi aktif ke setiap upstream server (jika ada stub_status)
curl http://localhost/nginx_status

# Dengan nginx-module-vts atau prometheus exporter, bisa lihat per-server:
# nginx_upstream_peers_active{upstream="app_servers", server="10.0.0.1:3000"} 12
# nginx_upstream_peers_active{upstream="app_servers", server="10.0.0.2:3000"} 3
# nginx_upstream_peers_active{upstream="app_servers", server="10.0.0.3:3000"} 8

# Dari access log — rata-rata response time per server
awk '{print $5, $8}' /var/log/nginx/access.log | \
    awk '{sum[$1]+=$2; count[$1]++} END {for(k in sum) print k, sum[k]/count[k], "avg_rt"}' | \
    sort -k2 -n

Load Testing: Membuktikan Perbedaan Least Connections vs Round Robin #

Cara terbaik memahami kapan least_conn lebih baik adalah mengukurnya langsung dengan workload yang mencerminkan kondisi produksi:

# Install wrk untuk load testing
brew install wrk  # macOS
apt install wrk   # Ubuntu

# Skenario 1: Semua request cepat dan seragam
# Ekspektasi: round robin dan least_conn hampir sama
wrk -t12 -c400 -d30s --latency http://example.com/api/products

# Skenario 2: Campuran request cepat dan lambat
# Tambahkan satu endpoint lambat selama test:
# /api/report (5 detik per request)
# Ekspektasi: least_conn jauh lebih baik
wrk -t12 -c400 -d30s --latency \
    -s mixed_workload.lua \
    http://example.com/
-- mixed_workload.lua: 90% request cepat, 10% request lambat
math.randomseed(os.time())

request = function()
    local r = math.random()
    if r < 0.9 then
        return wrk.format("GET", "/api/products")
    else
        return wrk.format("GET", "/api/report")  -- endpoint 5 detik
    end
end

Membandingkan Hasil #

# Output wrk menunjukkan:
# Latency distribution: 50th, 75th, 90th, 99th percentile
# Request/sec
# Transfer/sec

# Round robin dengan mixed workload:
# Latency distribution:
#   50%    45ms
#   75%   890ms    ← lonjakan drastis
#   90%  4320ms    ← banyak request "ketiban" server yang sibuk
#   99%  8910ms

# Least connections dengan mixed workload:
# Latency distribution:
#   50%    43ms
#   75%    89ms    ← jauh lebih konsisten
#   90%   156ms
#   99%   890ms    ← p99 jauh lebih rendah

Konfigurasi Production-Ready dengan Least Connections #

Template konfigurasi lengkap untuk produksi dengan least_conn:

http {
    upstream api_backend {
        # Algoritma: least connections
        least_conn;

        # Shared memory untuk data antar worker
        zone api_upstream 128k;

        # Server-server production
        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 10.0.0.3:3000 max_fails=3 fail_timeout=30s;

        # Server backup: tampil jika semua server utama down
        server 10.0.0.4:8080 backup;

        # Keepalive pool ke backend
        keepalive 64;
        keepalive_timeout 60s;
        keepalive_requests 1000;
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name api.example.com;

        location /api/ {
            proxy_pass         http://api_backend;
            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;

            # Retry ke server lain jika ada error
            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
            proxy_next_upstream_timeout 10s;

            # Timeout
            proxy_connect_timeout  5s;
            proxy_read_timeout    30s;

            # Header debug untuk monitoring (bisa dinonaktifkan di production)
            add_header X-Upstream-Server $upstream_addr always;
        }
    }
}

Praktik Terbaik dan Anti-Pattern #

Yang Harus Dilakukan #

# ✓ Selalu gunakan zone directive untuk shared memory
upstream app_servers {
    least_conn;
    zone app_upstream 128k;  # ← Wajib untuk cluster multi-worker
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

# ✓ Kombinasikan dengan max_fails dan fail_timeout
upstream app_servers {
    least_conn;
    zone app_upstream 128k;
    server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
}

# ✓ Tambahkan keepalive untuk performa optimal
upstream app_servers {
    least_conn;
    zone app_upstream 128k;
    server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
    keepalive 32;   # ← Pertahankan pool koneksi
    keepalive_requests 1000;
}

Anti-Pattern yang Harus Dihindari #

# ✗ Tanpa zone directive: data tidak dibagi antar worker
upstream app_servers {
    least_conn;
    # Tidak ada zone!
    # Worker process 1 punya data koneksinya sendiri
    # Worker process 2 punya data berbeda
    # Distribusi tidak akurat di level cluster
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

# ✗ Least_conn untuk request yang sangat cepat dan seragam
# (bukan salah, tapi overhead yang tidak perlu)
# Jika semua response < 10ms, round robin menghasilkan hasil yang sama
# dan lebih predictable
upstream static_files {
    least_conn;  # ← Tidak perlu untuk serving file statis cepat
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    # Gunakan round robin biasa saja untuk ini
}

# ✓ Yang benar untuk file statis:
upstream static_files {
    # Tidak ada direktive — round robin default
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    keepalive 64;  # Tapi tetap pakai keepalive
}

FAQ dan Troubleshooting #

Q: Apakah least_conn aman untuk semua jenis request?

Ya, least_conn tidak mempengaruhi konten request — ia hanya memilih server tujuan. Request tetap diteruskan secara utuh ke backend. Tidak ada risiko data corruption atau session mixing.

Q: Mengapa distribusi koneksi tidak pernah benar-benar merata?

Karena request selesai pada waktu yang berbeda. Saat request baru masuk, distribusi mencerminkan koneksi aktif saat itu, bukan total request yang pernah diterima. Jika 10.000 request sudah selesai dan hanya 5 yang aktif, distribusi 5 koneksi aktif itu yang menjadi acuan.

Q: Apa yang terjadi jika dua server punya jumlah koneksi yang sama persis?

Nginx menggunakan weighted round robin sebagai tiebreaker. Jika weight semua server sama, ia akan memilih secara round robin di antara server yang tied.

Q: Apakah least_conn berfungsi dengan keepalive pool?

Ya, dan kombinasi ini sangat direkomendasikan. Keepalive mengurangi overhead TCP handshake, sementara least_conn memastikan koneksi keepalive yang ada terdistribusi merata berdasarkan beban aktual.

# Troubleshooting: traffic ke satu server terus-terusan
# Kemungkinan: server lain sedang dalam status max_fails
nginx -s reload  # Reload untuk reset fail counter (tidak ideal di production)

# Atau cek log:
tail -f /var/log/nginx/error.log | grep -i "fail\|down\|unavailable"

# Cek distribusi aktual dari access log:
awk '{print $5}' /var/log/nginx/access.log | sort | uniq -c
# Jika satu server mendapat 0 request, kemungkinan sedang ditandai down
Tips Operasional: Ketika berpindah dari round robin ke least_conn di environment production yang sedang berjalan, tidak ada koneksi yang terputus. nginx -s reload graceful — worker lama menyelesaikan koneksi aktif, worker baru mulai dengan algoritma least_conn yang baru. Pengguna tidak merasakan perbedaan.

Ringkasan #

  • least_conn mengirim request ke server dengan jumlah koneksi aktif paling sedikit — lebih adaptif dari round robin untuk workload yang bervariasi.
  • Bisa dikombinasikan dengan weight untuk memperhitungkan server dengan kapasitas berbeda; formula efektif = koneksi_aktif / weight.
  • Keunggulan utama: secara otomatis menghindari server yang sedang sibuk memproses request berat, tanpa konfigurasi tambahan.
  • Ideal untuk API dengan endpoint campuran (beberapa ringan, beberapa berat) dan WebSocket atau koneksi long-lived lainnya.
  • Untuk request dengan durasi seragam dan pendek (< 100ms), round robin dan least connections menghasilkan distribusi yang hampir identik.
  • Selalu gunakan bersama zone directive agar data koneksi aktif dibagi antar semua worker process Nginx, bukan per-worker.

← Sebelumnya: Round Robin   Berikutnya: IP Hash →

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