Weighted #

Dalam mengelola infrastruktur komputasi skala besar, jarang sekali kita menemukan klaster di mana setiap server backend memiliki spesifikasi perangkat keras (hardware) yang identik. Beberapa server mungkin berjalan di mesin fisik (bare-metal) berspesifikasi tinggi, sementara lainnya berjalan di mesin virtual (VM) sedang, atau kontainer kecil di komputasi awan. Untuk memaksimalkan efisiensi perangkat keras ini, Caddy menyediakan fitur Weighted Load Balancing (penyeimbangan beban berbasis bobot). Kita akan mempelajari konsep distribusi berdasarkan bobot, rumus pembagian rasio beban, kombinasi dengan algoritma Least Connections, serta implementasinya dalam strategi Canary Release dan Blue-Green Deployment.


Konsep Weighted Load Balancing #

Kebijakan Weighted Load Balancing memungkinkan kita sebagai administrator jaringan untuk menetapkan nilai bobot (weight) numerik kepada masing-masing server backend (upstream). Bobot ini bertindak sebagai penentu porsi atau persentase lalu lintas yang harus diarahkan ke server tersebut.

Semakin tinggi nilai bobot suatu server, semakin banyak pula permintaan yang disalurkan Caddy kepadanya. Secara matematis, probabilitas atau rasio lalu lintas yang diterima oleh server ke-$i$ dihitung dengan rumus:

$$\text{Rasio Lalu Lintas}_i = \frac{\text{Bobot}i}{\sum{j=1}^{n} \text{Bobot}_j}$$

Sebagai contoh, kita memiliki 3 server dengan konfigurasi bobot berikut:

  • Server A (Weight: 5) (VM Enterprise)
  • Server B (Weight: 3) (VM Standard)
  • Server C (Weight: 2) (VM Micro)

Jumlah total bobot adalah $5 + 3 + 2 = 10$. Rasio lalu lintas yang akan dialirkan oleh Caddy adalah:

  • Server A menerima: $\frac{5}{10} = 50%$ dari total request.
  • Server B menerima: $\frac{3}{10} = 30%$ dari total request.
  • Server C menerima: $\frac{2}{10} = 20%$ dari total request.

Berikut adalah visualisasi aliran pembagian beban berdasarkan rasio bobot tersebut:

flowchart TD
    Reqs["10 Request Masuk"] --> Caddy{"Caddy Proxy"}
    
    Caddy -->|"Bobot: 5 (50% rute)"| S_A["Server A (VM Enterprise)"]
    Caddy -->|"Bobot: 3 (30% rute)"| S_B["Server B (VM Standard)"]
    Caddy -->|"Bobot: 2 (20% rute)"| S_C["Server C (VM Micro)"]

    style S_A stroke:#43a047,stroke-width:2px
    style S_B stroke:#0288d1,stroke-width:2px
    style S_C stroke:#e53935,stroke-width:2px

Pembagian bobot ini secara efektif melindungi server kecil (Server C) dari bahaya kelebihan beban (overload) sementara tetap memanfaatkan daya proses penuh dari server besar (Server A).


Algoritma Smooth Weighted Round Robin (SWRR) #

Pada implementasi Weighted Round Robin tradisional (WRR biasa), jika kita memiliki Server A dengan bobot 5, Server B dengan bobot 1, dan Server C dengan bobot 1, penjadwal (scheduler) akan mengirimkan 5 request berturut-turut ke Server A, lalu 1 ke Server B, dan 1 ke Server C. Pola ini memicu masalah Bursty Load, di mana Server A langsung dibombardir oleh lonjakan request secara berturut-turut yang dapat menaikkan latensi respons seketika.

Untuk mengatasinya, Caddy secara internal mengadopsi algoritma Smooth Weighted Round Robin (SWRR) (algoritma yang sama yang diimplementasikan oleh Nginx). SWRR secara dinamis mendistribusikan request secara selang-seling agar beban terbagi secara halus.

Algoritma ini bekerja dengan mencatat tiga variabel untuk setiap upstream:

  1. Weight: Bobot statis yang kita konfigurasi di Caddyfile.
  2. EffectiveWeight: Bobot aktual yang dapat diturunkan secara dinamis jika Caddy mendeteksi backend mulai lambat merespon.
  3. CurrentWeight: Bobot dinamis yang berubah setiap kali ada request baru.

Setiap kali request baru masuk, sirkuit SWRR menjalankan langkah berikut:

  • Untuk setiap upstream: CurrentWeight = CurrentWeight + EffectiveWeight.
  • Pilih upstream dengan CurrentWeight tertinggi sebagai target.
  • Kurangi CurrentWeight terpilih dengan total seluruh EffectiveWeight klaster: $$\text{CurrentWeight}{\text{terpilih}} = \text{CurrentWeight}{\text{terpilih}} - \sum \text{EffectiveWeight}$$

Penelusuran Langkah (Mathematical Trace) Algoritma SWRR #

Mari kita telaah jalannya matematika SWRR untuk 7 request berturut-turut pada klaster 3 server dengan bobot A:5, B:1, C:1 (Total bobot = 7):

Request Ke-CurrentWeight Sebelum Tambah (A, B, C)Tambahkan EffectiveWeight (A+5, B+1, C+1)Target Terpilih (Tertinggi)Pengurangan Target Terpilih (Target - 7)CurrentWeight Akhir (A, B, C)
10, 0, 05, 1, 1A (5)5 - 7 = -2-2, 1, 1
2-2, 1, 13, 2, 2A (3)3 - 7 = -4-4, 2, 2
3-4, 2, 21, 3, 3B (3) [tie-break]3 - 7 = -41, -4, 3
41, -4, 36, -3, 4A (6)6 - 7 = -1-1, -3, 4
5-1, -3, 44, -2, 5C (5)5 - 7 = -24, -2, -2
64, -2, -29, -1, -1A (9)9 - 7 = 22, -1, -1
72, -1, -17, 0, 0A (7)7 - 7 = 00, 0, 0
  • Hasil Distribusi: A, A, B, A, C, A, A
  • Analisis: Setelah request ke-7 selesai, nilai CurrentWeight kembali bersih ke angka awal 0, 0, 0 secara sempurna. Kita melihat bahwa beban 5 request ke Server A tidak dikirimkan secara beruntun di awal, melainkan diselingi secara halus oleh Server B pada request ke-3 dan Server C pada request ke-5.

Konfigurasi Weighted di Caddy #

Di dalam Caddyfile, kita dapat menerapkan bobot pada upstream dengan menuliskan opsi weight secara individual untuk setiap target dial di belakang direktif reverse_proxy.

Ada dua cara penulisan sintaksis yang didukung oleh Caddy:

1. Sintaks Terstruktur (Sangat Direkomendasikan) #

Menuliskan setiap backend menggunakan direktif to dan menyisipkan atribut weight di dalamnya:

# Contoh penulisan terstruktur dengan bobot
app.example.com {
    reverse_proxy {
        # VM Enterprise
        to backend-1:3000 weight 5
        
        # VM Standard
        to backend-2:3000 weight 3
        
        # VM Micro
        to backend-3:3000 weight 2
        
        lb_policy round_robin
    }
}

2. Sintaks Sebaris (Shortcut) #

Menuliskan bobot langsung menggunakan notasi URL query-style pada alamat dial (biasa digunakan untuk konfigurasi baris tunggal yang ringkas):

# Contoh penulisan shortcut sebaris
app.example.com {
    reverse_proxy backend-1:3000?weight=5 backend-2:3000?weight=3 backend-3:3000?weight=2
}

Integrasi dengan Least Connections (Weighted Least Connections) #

Ketika kita mengombinasikan parameter bobot (weight) dengan algoritma Least Connections (least_conn), Caddy tidak lagi sekadar mencari server dengan jumlah koneksi terkecil secara mutlak. Caddy akan mengevaluasi rasio beban dengan membagi jumlah koneksi aktif dengan nilai bobot masing-masing server.

Target server dengan nilai rasio terkecil akan dipilih untuk menerima request berikutnya:

$$\text{Rasio Beban} = \frac{\text{Koneksi Aktif}}{\text{Bobot}}$$

Simulasi Perhitungan Rasio Beban #

Bayangkan kita memiliki 2 server aktif:

  • Server A (Weight: 2): Sedang memproses 10 koneksi aktif.
  • Server B (Weight: 5): Sedang memproses 15 koneksi aktif.

Klien baru mengirimkan request ke Caddy. Mari kita bandingkan ke mana request akan dirutekan:

  1. Rasio Beban Server A: $\frac{10}{2} = 5.0$
  2. Rasio Beban Server B: $\frac{15}{5} = 3.0$
  • Keputusan Caddy: Permintaan baru akan diarahkan ke Server B, meskipun secara jumlah mutlak Server B sedang memproses koneksi yang lebih banyak (15 koneksi) dibandingkan Server A (10 koneksi). Hal ini logis karena Server B memiliki spesifikasi hardware 2.5 kali lebih kuat (weight 5 vs 2), sehingga kapasitas siaganya sebenarnya masih jauh lebih besar daripada Server A.
# Konfigurasi Weighted Least Connections
app.example.com {
    reverse_proxy {
        to backend-1:3000 weight 2
        to backend-2:3000 weight 5
        
        lb_policy least_conn
    }
}

Penyesuaian Nilai Parameter Bobot Saat Lalu Lintas Padat #

Dalam praktik di industri, nilai bobot yang bersifat statis sering kali kurang optimal ketika tipe beban kerja backend bergeser secara dinamis. Misalnya, Server A (VM kuat dengan bobot 10) menangani kueri database yang kompleks, sementara Server B (VM kecil dengan bobot 2) melayani respon JSON statis. Pada saat traffic padat, database Server A mengalami hambatan kueri (query contention), menyebabkan utilisasi CPU melonjak drastis meskipun jumlah koneksi aktifnya masih di bawah rasio pembagian bobot.

Untuk mengatasi ini, tim DevOps harus merancang penyesuaian bobot dinamis berdasarkan faktor beban kerja per core CPU (CPU load-to-core factor). Kita dapat memanfaatkan skrip eksternal yang membaca utilitas CPU backend secara real-time dan memperbarui bobot Caddy secara graceful melalui REST API JSON.

# Contoh klaster dengan opsi dynamic weight tuning
prod.example.com {
    reverse_proxy {
        # VM Database (Weight disesuaikan dinamis oleh daemon monitor)
        to db-app-1:3000 weight 8
        
        # VM Cache / Static API
        to cache-app-2:3000 weight 2
        
        lb_policy least_conn
    }
}

Use Case 1: Klaster Heterogen (Heterogeneous Clusters) #

Dalam dunia nyata, penghematan anggaran cloud sering kali memaksa kita mencampur berbagai tipe VM. Sebagai contoh, kita menempatkan server fisik lokal (On-Premise) milik perusahaan sebagai backend utama, dan beberapa VM cloud kecil (AWS EC2) sebagai backend bantu untuk menangani luapan lalu lintas.

# Konfigurasi klaster heterogen
prod.example.com {
    reverse_proxy {
        # Server Fisik On-Premise (Sangat kuat)
        to 192.168.10.50:8080 weight 10
        
        # AWS EC2 Instance 1 (VM Sedang)
        to 10.0.1.15:8080 weight 3
        
        # AWS EC2 Instance 2 (VM Sedang)
        to 10.0.1.16:8080 weight 3
        
        lb_policy least_conn
    }
}

Dengan konfigurasi ini, 62.5% lalu lintas ($\frac{10}{16}$) akan diproses di server lokal perusahaan kita secara gratis, sementara sisanya dibagi rata ke cloud VM berbayar hanya ketika beban mulai meningkat.


Use Case 2: Canary Deployments (Pelepasan Fitur Bertahap) #

Canary Deployment is adalah strategi pelepasan fitur aplikasi baru di mana kita merilis kode versi baru (v2) hanya ke sebagian kecil pengguna terlebih dahulu untuk menguji stabilitasnya di lingkungan produksi, sementara mayoritas pengguna tetap diarahkan ke kode versi stabil (v1).

flowchart TD
    User["Lalu Lintas Pengguna"] --> Caddy{"Caddy Proxy\n(Canary Split)"}
    
    Caddy -->|"Weight: 95 (95% rute)"| V1["Klaster Stabil (App v1)"]
    Caddy -->|"Weight: 5 (5% rute)"| V2["Klaster Canary (App v2)"]

    style V1 stroke:#0288d1,stroke-width:2px
    style V2 stroke:#e53935,stroke-dasharray: 5,5

Berikut adalah contoh implementasi Canary Release menggunakan pembagian bobot di Caddyfile:

# Canary Deployment 95% vs 5%
app.example.com {
    reverse_proxy {
        # Server Produksi v1 (Stabil)
        to app-v1-node1:3000 weight 95
        
        # Server Baru v2 (Canary)
        to app-v2-canary:3000 weight 5
        
        lb_policy round_robin
    }
}

Skrip Otomasi Transisi Canary Menggunakan REST API Caddy #

Untuk mengotomatiskan pelepasan fitur baru secara bertahap tanpa intervensi manual, kita dapat menulis skrip bash yang secara bertahap menaikkan bobot rute server Canary. Skrip ini memodifikasi array JSON pada path Caddy API /config/apps/http/servers/srv0/routes/0/handle/0/routes/0/handle/0/upstreams secara dinamis:

#!/bin/bash
# canary_rollout.sh

CADDY_API_UPSTREAMS="http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/routes/0/handle/0/upstreams"

# Array tahapan persentase Canary (bobot v1 vs v2)
# Tahap 1: 95 vs 5, Tahap 2: 80 vs 20, Tahap 3: 50 vs 50, Tahap 4: 0 vs 100
stages=("95:5" "80:20" "50:50" "0:100")

check_health() {
    # Periksa tingkat error 5xx di log Caddy 1 menit terakhir
    error_count=$(tail -n 100 /var/log/caddy/access.log | grep -c '"status":5')
    if [ "$error_count" -gt 5 ]; then
        echo "PERINGATAN: Terdeteksi $error_count error. Membatalkan rilis (Rollback)!"
        # Rollback instan ke v1 100%
        curl -X PUT "$CADDY_API_UPSTREAMS" \
          -H "Content-Type: application/json" \
          -d '[{"dial":"app-v1-node1:3000","weight":100},{"dial":"app-v2-canary:3000","weight":0}]'
        exit 1
    fi
}

for stage in "${stages[@]}"; do
    w1=$(echo $stage | cut -d':' -f1)
    w2=$(echo $stage | cut -d':' -f2)
    
    echo "Memulai Tahap Canary: v1=$w1%, v2=$w2%"
    
    # Update bobot secara dinamis di memori Caddy
    curl -s -X PUT "$CADDY_API_UPSTREAMS" \
      -H "Content-Type: application/json" \
      -d '[{"dial":"app-v1-node1:3000","weight":'$w1'},{"dial":"app-v2-canary:3000","weight":'$w2'}]' \
      -o /dev/null
      
    # Tunggu 5 menit untuk observasi kestabilan
    sleep 300
    check_health
done

echo "Rilis Canary App v2 Selesai dengan Sukses!"

Use Case 3: Blue-Green Deployments (Migrasi Zero-Downtime) #

Blue-Green Deployment adalah strategi rilis aplikasi di mana kita memelihara dua lingkungan fisik yang identik secara bersamaan: lingkungan Blue (versi saat ini yang sedang aktif melayani pengguna) dan lingkungan Green (versi baru tempat kita men-deploy dan menguji kode tanpa mengganggu pengguna).

Setelah pengujian di lingkungan Green selesai dan sukses, kita melakukan switch (pengalihan) rute gerbang utama Caddy secara instan.

# Langkah 1: 100% lalu lintas di Blue
# to blue-server:3000 weight 100
# to green-server:3000 weight 0   // Nonaktif

# Langkah 2: Transisi 50-50 (Membagi beban bertahap)
app.example.com {
    reverse_proxy {
        to blue-server:3000 weight 50
        to green-server:3000 weight 50
        
        lb_policy round_robin
    }
}

# Langkah 3: 100% Green aktif, Blue dimatikan
# to blue-server:3000 weight 0
# to green-server:3000 weight 100

Sinkronisasi Skema Database (Expand and Contract Pattern) #

Tantangan terbesar dalam melakukan migrasi Blue-Green zero-downtime adalah menjaga agar database tetap kompatibel saat kedua versi aplikasi (v1 dan v2) mengakses database yang sama secara bersamaan selama masa transisi.

Kita wajib menerapkan pola Expand and Contract (Parallel Run) untuk migrasi skema database:

  1. Expand (Perluas): Tambahkan kolom baru di database tanpa menghapus kolom lama. Ubah kode aplikasi v2 agar menulis ke kedua kolom (kolom lama dan kolom baru), sementara aplikasi v1 tetap menulis ke kolom lama.
  2. Sinkronisasi: Jalankan skrip latar belakang untuk menyalin data historis dari kolom lama ke kolom baru.
  3. Contract (Penyusutan): Setelah rute Caddy sepenuhnya beralih ke lingkungan Green (100% v2), kita men-deploy patch untuk menghentikan penulisan ke kolom lama. Terakhir, kita hapus kolom lama dari database secara aman.

Ringkasan #

  • Definisi Utama: Kebijakan Weighted Load Balancing mendistribusikan lalu lintas berdasarkan nilai bobot (weight) numerik yang disematkan pada masing-masing server backend.
  • SWRR Algoritma: Caddy menggunakan algoritma Smooth Weighted Round Robin untuk mendistribusikan request secara selang-seling, menghindari bursty load.
  • Weighted Least Connections: Caddy membagi jumlah koneksi aktif dengan bobot untuk mencari rasio beban terkecil, mengoptimalkan klaster hardware heterogen.
  • Canary Deployment: Sangat efektif untuk merilis fitur aplikasi baru secara aman dengan mengalirkan persentase kecil lalu lintas (misal 5%) ke instance server baru.
  • Blue-Green Transition: Memfasilitasi migrasi zero-downtime dengan mengalihkan beban lalu lintas secara bertahap dari klaster lama ke klaster baru menggunakan database schema expand and contract.
  • Sintaks Caddyfile: Mendukung penulisan terstruktur menggunakan subdirektif weight pada blok to, atau penulisan instan sebaris menggunakan parameter kueri URL.

← Sebelumnya: IP Hash   Berikutnya: Active Health Check →

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