Optimasi Nginx dan Go Backend untuk 100k Concurrent Requests
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap arsitektur web modern, tantangan menembus batas 100,000 concurrent requests (C10K/C100K problem) bukan lagi sekadar teori akademik, melainkan kebutuhan operasional harian bagi perusahaan teknologi skala enterprise. Arsitektur tradisional yang menggunakan model one thread per connection langsung runtuh ketika dihadapkan pada lonjakan trafik masif, karena konsumsi memori untuk stack thread OS akan segera menghabiskan kapasitas RAM server, diikuti oleh overhead context switching CPU yang melumpuhkan sistem.
Bottleneck performa dan keamanan umum di dunia nyata biasanya bersumber dari:
- Limitasi Sistem Operasi (Kernel Limits): Default file descriptor (
nofile) pada Linux yang dibatasi rendah (biasanya 1024), serta parameter TCP stack yang belum di-tuning untuk koneksi berumur pendek (TIME_WAIT state exhaustion). - Konfigurasi Default Nginx: Penggunaan modul event standar atau pembatasan
worker_connectionsyang kecil, serta ketiadaan optimasi buffer dan keepalive. - Garbage Collection (GC) Go Runtime: Alokasi heap yang masif dan tidak terkontrol memicu Stop-The-World (STW) pauses yang melonjakkan latensi tail (p99/p99.9) secara drastis di bawah beban tinggi.
Perbandingan Kondisi: Sebelum vs Sesudah Optimasi
| Metrik / Parameter | Sebelum Optimasi (Default) | Sesudah Optimasi (Enterprise C100K) | Dampak Bisnis |
|---|---|---|---|
| Max Concurrent Connections | ~1,024 - 10,000 | 100,000+ | Mampu menangani flash sale tanpa downtime |
| P99 Latency | > 850 ms (Beban Penuh) | < 25 ms | Pengalaman pengguna (UX) tetap mulus |
| Memory Footprint per 10k Conns | ~4 GB - 8 GB | < 400 MB | Efisiensi biaya infrastruktur (Cloud Cost Reduction) |
| CPU Utilization under Load | 90% (Banyak Context Switch) | 45% (Efisien via Epoll/Goroutine) | Stabilitas server terjaga dalam jangka panjang |
2. Konsep Inti & Deep Dive Arsitektur
Untuk memahami bagaimana arsitektur ini bekerja di balik layar (under the hood), kita harus meneliti interaksi antara sistem operasi Linux, Nginx sebagai reverse proxy, dan Go backend.
[Client 100k Conns] ---> (TCP Handshake / Epoll) ---> [Nginx Reverse Proxy]
|
(Unix Socket / Keepalive TCP)
v
[Go Backend (Goroutines)]
|
(Connection Pooling / Redis)
v
[Database / Storage]
Alur Eksekusi & Pemrosesan Data
- Multiplexing I/O dengan Epoll: Nginx memanfaatkan system call
epolldi Linux (menggantikanselectataupolllama). Alih-alih melakukan polling pada setiap deskriptor file,epollmenggunakan event-driven notification di tingkat kernel. Ketika sebuah socket siap dibaca atau ditulis, kernel memberi tahu Nginx secara langsung. - Asynchronous Non-Blocking Workers: Nginx menjalankan sejumlah worker process (biasanya disetel sama dengan jumlah inti CPU fisik). Setiap worker dapat mengelola ribuan koneksi secara bersamaan dalam event loop tunggal tanpa membuat thread baru per koneksi.
- Go Runtime & M:N Scheduler: Permintaan yang lolos dari Nginx diteruskan ke backend Go. Go menggunakan runtime scheduler sendiri yang menerapkan model M:N (memetakan $M$ goroutine ke $N$ OS threads). Goroutine sangat ringan (hanya membutuhkan ~2KB stack awal dibandingkan ~1MB pada thread OS), memungkinkan jutaan goroutine berjalan simultan.
3. Perbandingan Komparatif / Analisis Pendekatan
Berikut adalah analisis mendalam mengenai pendekatan arsitektural dalam menangani konkurensi tinggi:
| Pendekatan Arsitektur | Kelebihan | Kekurangan | Trade-off Sumber Daya |
|---|---|---|---|
| Thread-per-Connection (Apache Prefork) | Kompatibilitas tinggi dengan skrip lama (PHP lama), mudah debug. | Konsumsi RAM masif, tidak skalabel di atas 10k koneksi. | RAM: Sangat Tinggi CPU: Tinggi (Context Switch) |
| Event-Driven Non-Blocking (Nginx + Node/Go) | Penggunaan RAM sangat efisien, latensi rendah, throughput tinggi. | Kompleksitas debugging asynchronous, callback hell (jika tidak dikelola baik). | RAM: Sangat Rendah CPU: Optimal |
| Kernel Bypass (DPDK / XDP) | Latensi mikrodetik, bypass TCP/IP stack kernel standar. | Kompleksitas implementasi ekstrem, butuh hardware khusus (NIC tertentu). | RAM: Rendah CPU: Maksimal (Dedicated Core) |
[!TIP] Untuk 99% kasus enterprise, pendekatan Event-Driven Reverse Proxy (Nginx) + M:N Scheduler Backend (Go) adalah sweet spot terbaik antara performa, kemudahan pemeliharaan, dan efisiensi biaya hardware.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum menyentuh konfigurasi aplikasi, pondasi sistem operasi (Linux Kernel) harus diperkuat agar mampu menampung 100k file descriptor dan koneksi TCP simultan.
1. Tuning Kernel Parameters (/etc/sysctl.conf)
Tambahkan konfigurasi berikut untuk mengoptimalkan jaringan dan buffer memori:
# Meningkatkan batas maksimal file descriptor global
fs.file-max = 2097152
# Mengoptimalkan port range untuk koneksi keluar
net.ipv4.ip_local_port_range = 1024 65535
# Reuse koneksi TIME_WAIT untuk koneksi TCP baru secara aman
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# Meningkatkan ukuran backlog queue untuk SYN packets
net.ipv4.tcp_max_syn_backlog = 65535
net.core.somaxconn = 65535
# Mengoptimalkan ukuran memori buffer TCP (Read/Write)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# Meningkatkan batas maksimal paket dalam queue jaringan
net.core.netdev_max_backlog = 100000
Terapkan perubahan dengan perintah:
sysctl -p
2. Konfigurasi Limits Sistem (/etc/security/limits.conf)
* soft nofile 655350
* hard nofile 655350
* soft nproc 655350
* hard nproc 655350
root soft nofile 655350
root hard nofile 655350
5. Panduan Implementasi Langkah demi Langkah
Berikut adalah konfigurasi lengkap dan siap produksi untuk Nginx Reverse Proxy dan Go Backend.
A. Konfigurasi Nginx (/etc/nginx/nginx.conf)
user nginx;
worker_processes auto;
worker_rlimit_nofile 655350;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Optimasi I/O dan jaringan
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 10000;
types_hash_max_size 2048;
server_tokens off;
# Buffer tuning untuk mencegah disk write berlebih
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
output_buffers 1 32k;
postpone_output 1460;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
upstream go_backend {
server 127.0.0.1:8080 weight=1 max_fails=3 fail_timeout=10s;
keepalive 1000;
keepalive_requests 10000;
keepalive_timeout 60s;
}
server {
listen 80 backlog=65535 reuseport;
server_name api.enterprise.internal;
location / {
proxy_pass http://go_backend;
proxy_http_version 1.1;
# Hapus header koneksi lama untuk mendukung keepalive backend
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;
# Timeout tuning
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer proxy
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
}
}
}
B. Implementasi Go Backend (main.go)
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"runtime"
"syscall"
"time"
)
func main(
) {
// Memanfaatkan seluruh core CPU yang tersedia
numCPU := runtime.NumCPU()
runtime.GOMAXPROCS(numCPU)
log.Printf("Starting Go Backend using %d CPU cores\n", numCPU)
// Tuning GC (Opsional: tingkatkan GOGC jika alokasi memori sangat tinggi untuk kurangi frekuensi GC)
// os.Setenv("GOGC", "100") // Default 100
mux := http.NewServeMux()
muxt.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"status":"healthy\