Tuning Next.js & Nginx: Atasi Server Choke & Load Test

Aditya Y PradhanaAditya Y Pradhana/
Studi Kasus Tuning Performance Next.js & Nginx: Dari Server Choke hingga Tahan Gempuran Load Test
Studi Kasus Tuning Performance Next.js & Nginx: Dari Server Choke hingga Tahan Gempuran Load Test

Ringkasan

  • Strategi Scaling: Mengatasi bottleneck CPU dengan transisi dari PM2 Fork Mode ke Cluster Mode untuk distribusi beban multi-core.

  • Optimasi Jaringan: Mengidentifikasi dampak kritis bandwidth ISP dan implementasi kompresi Gzip/Brotli untuk mengurangi payload hingga 85%.

  • Caching Agresif: Mengatasi kegagalan bot SEO (Ahrefs/Semrush) dengan override header Cache-Control pada Nginx untuk mencegah direct hit ke origin server.

  • Network Tuning: Meningkatkan kapasitas antrean koneksi melalui optimasi kernel TCP Linux untuk mencegah Socket Timeout pada traffic tinggi.

Mengelola aplikasi Next.js dalam skala produksi seringkali menghadirkan tantangan yang tidak terlihat saat fase pengembangan. Banyak pengembang berasumsi bahwa spesifikasi server yang tinggi sudah cukup, namun kenyataannya, bottleneck sering terjadi pada konfigurasi runtime, limitasi jaringan, hingga manajemen cache yang tidak efisien. sebagai contoh misalnya website kita ingin memiliki target kunjungan yang cukup banyak, seperti 1000 hit dalam satu waktu. Artikel ini membedah studi kasus nyata dalam melakukan tuning performance pada stack Next.js dan Nginx untuk mengubah server yang mudah "choke" menjadi sistem yang tahan gempuran load test.

1. Pengenalan Alat Ukur: Mengapa Menggunakan wrk?

Sebelum melakukan optimasi, kita membutuhkan alat ukur yang akurat. Dalam eksperimen ini, kami menggunakan wrk, sebuah HTTP benchmarking tool modern berbasis command line yang dirancang untuk efisiensi ekstrem. Berbeda dengan ApacheBench (ab) yang bersifat single-threaded dan seringkali menjadi bottleneck itu sendiri saat menguji concurrency tinggi, wrk menggunakan arsitektur event-driven (mirip dengan epoll dan kqueue).

wrk memanfaatkan teknologi C-based event loop yang memungkinkan satu mesin penguji mengirimkan puluhan ribu request per detik tanpa membebani CPU penguji secara berlebihan. Hal ini sangat krusial untuk mensimulasikan kondisi real-world traffic spike.

Command utama yang digunakan dalam pengujian ini adalah:

wrk -t8 -c1000 -d20s --latency https://domain-target/

  • -t8: Mengalokasikan 8 thread di sisi client untuk memaksimalkan utilisasi CPU penguji.

  • -c1000: Membuka 1.000 concurrent TCP connections secara bersamaan untuk menguji ketahanan socket server.

  • -d20s: Durasi pengujian selama 20 detik untuk mendapatkan rata-rata throughput yang stabil.

  • --latency: Menampilkan statistik distribusi latensi, yang sangat penting untuk mengidentifikasi tail latency (p99) yang seringkali menjadi penyebab user merasa aplikasi "lemot".

Catatan Teknis: Perlu diperhatikan bahwa melakukan pengujian SSL/TLS dengan 1.000 concurrency menggunakan wrk memberikan overhead CPU tambahan baik di sisi client maupun server karena proses TLS Handshake dan dekripsi. Hal inilah yang menyebabkan latensi pada HTTPS cenderung sedikit lebih tinggi dibandingkan pengujian HTTP biasa.

Running 20s test @ https://ip-server/
  12 threads and 2000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.00us    0.00us   0.00us    -nan%
    Req/Sec    13.35     14.32   250.00     89.88%
  Latency Distribution
     50%    0.00us
     75%    0.00us
     90%    0.00us
     99%    0.00us
  1858 requests in 20.10s, 268.84MB read
  Socket errors: connect 0, read 0, write 0, timeout 1858
  Non-2xx or 3xx responses: 264
Requests/sec:     92.44
Transfer/sec:     13.38MB

2. Masalah Awal: PM2 Mode Fork vs Mode Cluster

Salah satu kesalahan umum dalam deployment Node.js adalah menjalankan aplikasi dalam single process. Pada kondisi awal, aplikasi Next.js dijalankan menggunakan PM2 Fork Mode. Dalam mode ini, aplikasi hanya berjalan pada satu instance (single process), yang berarti hanya satu core CPU yang bekerja meskipun server memiliki banyak core.

Saat dihantam 1.000 concurrent requests, hasilnya fatal: satu core CPU langsung menyentuh 100% (bottleneck), sementara core lainnya menganggur. Akibatnya, terjadi penumpukan request di antrean, memicu deretan Socket error timeout, dan hanya sebagian kecil request yang berhasil diproses.

Eksperimen 1: Migrasi ke PM2 Cluster Mode

Untuk mengatasi hal ini, konfigurasi PM2 diubah menjadi Cluster Mode dengan 4 instance/workers. Perubahan ini memungkinkan beban rendering dan routing terbagi rata secara round-robin ke 4 core CPU server. Melalui monitoring htop, terlihat beban CPU terdistribusi secara merata (~100% per core).

Hapus service pm2, lalu jalankan dengan mode cluster:

pm2 delete 15 && pm2 start npm --name "nama_service" -i 4 -- run start -- -p nomor_port

wrk -t8 -c1000 -d20s --latency https://ip-isp1/ -H "Host: www.domain-target.com"
Running 20s test @ https://ip-isp1/
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.56s   207.32ms   1.99s    57.64%
    Req/Sec    22.11     22.33   101.00     85.05%

  Latency Distribution
     50%    1.53s
     75%    1.77s
     90%    1.84s
     99%    1.97s
  1642 requests in 20.08s, 276.72MB read
  Socket errors: connect 0, read 0, write 0, timeout 1439
Requests/sec:     81.78
Transfer/sec:     13.78MB

Hasil Evaluasi: Meskipun utilisasi CPU sudah maksimal, throughput aplikasi SSR (Server-Side Rendering) dan RSC (React Server Components) tetap memiliki batas atas (computational limit). Karena Node.js bersifat single-threaded per proses, beban komputasi untuk merender halaman yang kompleks tetap bisa memicu timeout jika jumlah request yang masuk melebihi kecepatan render server. Hal ini menunjukkan bahwa scaling CPU saja tidak cukup; kita perlu optimasi di lapisan jaringan dan cache.

3. Analisis Bandwidth: Dampak Fisik Pipa Jaringan

Seringkali kita terlalu fokus pada CPU dan RAM, namun melupakan network pipe saturation. Untuk mengisolasi masalah, kami melakukan pengujian menggunakan dua jalur ISP berbeda dengan IP static public untuk melihat apakah bandwidth menjadi penghambat.

A. Pengujian Jalur ISP1 (~350 Mbps)

wrk -t8 -c1000 -d20s --latency https://ip-isp1/ -H "Host: www.target-domain.com"
Running 20s test @ https://ip-isp1/
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.17s   453.46ms   2.00s    60.65%
    Req/Sec    24.39     14.31    90.00     50.04%

  Latency Distribution
     50%    1.18s
     75%    1.54s
     90%    1.81s
     99%    1.98s

  3546 requests in 20.10s, 666.77MB read
  Socket errors: connect 0, read 0, write 0, timeout 1978

Requests/sec:    176.40
Transfer/sec:     33.17MB 

Pada jalur ini, pipa bandwidth langsung tersumbat penuh (saturated). Throughput terkunci di kisaran ~33 MB/s (sekitar 264–280 Mbps). Terjadi lebih dari 2.000 timeout karena data tertahan di limitasi fisik jalur ISP. Requests per second (req/sec) tertahan di angka rendah, yakni ~175–186 req/sec.

B. Pengujian Jalur ISP2 (~600 Mbps)

Dengan pipa yang lebih lebar, throughput melonjak drastis hingga ~64–66 MB/s (sekitar 512–528 Mbps). Requests per second naik lebih dari 100% menjadi ~355–366 req/sec. Lebih dari 50% total request berhasil diselesaikan, dengan total transfer data mencapai 1.3 GB dalam 20 detik.

wrk -t8 -c1000 -d20s --latency https://ip-isp2/ -H "Host: www.target-domain.com"
Running 20s test @ https://ip-isp2/
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.25s   428.41ms   2.00s    61.78%
    Req/Sec    47.65     23.61   227.00     72.45%

  Latency Distribution
     50%    1.26s
     75%    1.61s
     90%    1.83s
     99%    1.98s

  7148 requests in 20.09s, 1.26GB read
  Socket errors: connect 0, read 0, write 0, timeout 3058
Requests/sec:    355.82
Transfer/sec:     64.10MB

Pelajaran Krusial: Payload halaman Next.js tanpa kompresi bisa membengkak hingga ~190 KB per request. Pada concurrency tinggi, bandwidth ISP menjadi tembok utama. Ini membuktikan bahwa optimasi ukuran payload (kompresi) jauh lebih efektif daripada sekadar menambah RAM server.

4. Diagnostik Cache & Kegagalan Bot SEO

Fenomena aneh terjadi ketika website terasa cepat saat dibuka manual, namun alat crawling seperti Ahrefs dan Semrush melaporkan timeout. Setelah melakukan inspeksi response header Nginx via curl, ditemukan akar masalahnya.

Next.js secara default mengirimkan header: cache-control: private, no-cache, no-store, max-age=0, must-revalidate. Header ini secara eksplisit memerintahkan reverse proxy (Nginx) untuk tidak menyimpan cache. Akibatnya, setiap request dari bot SEO yang sangat intensif dianggap sebagai direct hit ke engine Next.js dan database.

Ketika ribuan request bot masuk bersamaan, socket backlog menumpuk, CPU memuncak, dan akhirnya server mengalami timeout. Nginx yang seharusnya menjadi perisai justru hanya menjadi "kurir" yang meneruskan beban berat ke origin server.

5. Eksperimen Direct Hit ke Cloudflare: Mengapa Gagal?

Ada upaya untuk melakukan benchmark langsung ke IP Anycast Cloudflare menggunakan wrk:

wrk -t8 -c1000 -d20s --latency https://[IP_CLOUDFLARE]/ -H "Host: www.domain-target.com"

Hasilnya adalah Connection timed out. Hal ini terjadi karena dua alasan teknis:

Solusi benchmark yang benar adalah menggunakan domain penuh yang sudah ter-resolve atau menggunakan parameter --resolve pada curl untuk memetakan domain ke IP tertentu.

6. Solusi Akhir untuk High Availability

Berdasarkan eksperimen di atas, berikut adalah arsitektur optimasi yang diterapkan untuk mencapai ketahanan sistem yang sempurna:

A. Micro-Caching Nginx dengan Override Agresif

Saya mengonfigurasi Nginx untuk mengabaikan header anti-cache dari Next.js menggunakan direktif proxy_ignore_headers Cache-Control Expires Set-Cookie;. Dengan menerapkan micro-caching (menyimpan halaman di RAM selama 5–10 detik), beban server berkurang drastis karena ribuan request identik hanya diproses satu kali oleh Next.js.

⚠️ PERINGATAN PENTING: Penggunaan proxy_ignore_headers Set-Cookie Cache-Control; secara global sangat berbahaya. Jangan terapkan konfigurasi ini pada halaman yang membutuhkan autentikasi (seperti /dashboard, /admin, atau /checkout). Jika diterapkan pada halaman privat, User A bisa melihat data session milik User B karena halaman ter-cache oleh Nginx. Gunakan konfigurasi ini HANYA pada lokasi URL publik seperti landing page, blog, atau homepage.

B. Implementasi Kompresi Gzip & Brotli

Mengaktifkan kompresi tingkat tinggi memotong payload dari ~190 KB menjadi <20 KB. Hal ini menghemat bandwidth hingga 85%+, menghilangkan pipe congestion, dan mempercepat TTFB (Time to First Byte) secara signifikan.

C. Tuning Kernel TCP Linux

Untuk menangani ribuan koneksi simultan, kami meningkatkan limit antrean di level OS Linux:

  • net.core.somaxconn: Meningkatkan batas antrean koneksi yang menunggu untuk diterima oleh aplikasi.

  • tcp_max_syn_backlog: Meningkatkan jumlah koneksi "setengah terbuka" (half-open) untuk mencegah serangan SYN flood dan menangani lonjakan traffic.

D. Strategi Edge Caching (Cloudflare)

Memanfaatkan fitur Cache Everything pada Cloudflare Page Rules memastikan bahwa 90%+ request publik dan bot SEO diserap di level Edge CDN. Dengan demikian, traffic hanya akan menyentuh origin server jika konten benar-benar sudah basi, menjaga server tetap stabil meskipun terjadi lonjakan traffic masif.

Bonus: Solusi Alternatif di Level Next.js

Selain optimasi di layer Nginx, Anda bisa memanfaatkan fitur bawaan Next.js App Router untuk mengurangi beban server. Penggunaan export const revalidate = 10; atau ISR (Incremental Static Regeneration) memungkinkan Next.js melakukan caching di layer aplikasi sebelum request menyentuh reverse proxy, yang secara signifikan mengurangi beban komputasi SSR.

Implementasi Konfigurasi Nginx Lengkap

Berikut adalah contoh konfigurasi Nginx yang menggabungkan semua optimasi di atas. Pastikan Anda mendefinisikan proxy_cache_path di dalam blok http {} sebelum menggunakannya di blok server {}.

# Letakkan ini di dalam blok http { ... }
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=nama-cache:10m max_size=1g inactive=60m use_temp_path=off;

server {
    listen 80;
    server_name www.domain-target.com;

    # Gzip Compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_proxied any;
    gzip_comp_level 6;

    location / {
        # Micro-caching untuk halaman publik
        proxy_cache nama_cache;
        proxy_cache_valid 200 10s;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        
        # Override header Next.js (HANYA untuk halaman publik)
        proxy_ignore_headers Cache-Control Expires Set-Cookie;
        
        # Proxy settings
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_cache_control;

        # Debugging cache status
        add_header X-Cache-Status $upstream_cache_status;
    }

    # Contoh penanganan halaman privat (TIDAK di-cache)
    location /dashboard {
        proxy_cache off;
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
    }
}

Kesimpulan: Before vs After Tuning

Berikut adalah ringkasan dampak nyata dari proses tuning yang telah dilakukan:

Parameter Benchmark

Sebelum Tuning (Direct Next.js / Fork)

Setelah Tuning (Nginx Micro-cache + Gzip)

Requests / Sec

~175 – 350 req/sec

2.000 – 10.000+ req/sec (Estimasi HIT)

Payload Size

~190 KB (Uncompressed)

< 20 KB (Gzip Enabled)

Bandwidth Demand

Heavy (~66 MB/s / Saturated)

Efficient (~3-8 MB/s)

Socket Timeout

Ribuan (~2.000-3.000 errors)

0 (Zero Error)

Status Bot SEO

Timeout / Fail (Ahrefs/Semrush)

Pass / Fast Response

Solusi yang relevan

Service

Jasa Pembuatan Website

Pembuatan website custom yang cepat, modern, dan siap jualan.

Lihat Solusi →

Dapatkan Artikel Terbaru!

Berlangganan newsletter kami untuk mendapatkan tips dan insight menarik langsung ke inbox Anda.

Kami tidak akan pernah membagikan email Anda (No Spam).