Semua proyek
Proyek pribadi

Go vs Node.js Load-Testing Benchmark

Studi throughput & latency terkontrol dengan k6

k6Docker ComposeGo 1.24 (net/http)Node 20 (Express 4)PostgreSQL 16Redis 7
712,788
Request
16
Eksekusi
0.00%
Kegagalan HTTP

Ringkasan

Benchmark terkontrol yang membandingkan backend Go dan Node.js di balik endpoint, database, dan cache yang identik. Tujuannya bukan mencari pemenang, melainkan melihat bagaimana selisih performanya bergeser saat beban naik lima kali lipat.

Kedua stack dibebani pada dua tingkat konkurensi - 20 dan 100 virtual user - terhadap empat endpoint yang dipilih untuk mengisolasi perilaku CPU, database, dan cache. Di seluruh endpoint dan kedua tingkat beban, Go unggul pada throughput sebesar 1,19× sampai 1,65×, dan urutannya tidak pernah berbalik.

Tujuan

Mengukur throughput dan latency layanan Go dan Node.js yang setara dalam kondisi identik, lalu mengamati bagaimana selisihnya berubah saat beban naik dari 20 ke 100 virtual user.

Desain benchmark

  • 4 endpoint × 2 stack × 2 tingkat beban = 16 eksekusi.
  • Satu PostgreSQL 16 (10.000 baris) dan Redis 7 yang dipakai bersama - satu instance untuk kedua stack, supaya tidak ada yang dapat lingkungan lebih menguntungkan.
  • SQL yang identik (sama persis karakter demi karakter), LIMIT 100, DB pool 25, Redis pool 25, serta cache key & TTL yang sama di kedua sisi.
  • Script k6 yang sama menjalankan semua kombinasi; tingkat beban diatur lewat environment variable, bukan dengan mengubah isi file.
  • Executor constant-vus, 30 detik per eksekusi, dengan threshold p95 < 500 ms, http_req_failed < 1%, dan checks > 99%.

Cakupan pengujian

  • Endpoint yang diuji

    • /hello - murni CPU, tanpa I/O (baseline penanganan HTTP)
    • /products - query database ringan (LIMIT 100)
    • /products-where-like - LIKE dengan sequential scan (tidak bisa diindeks)
    • /products-with-cache - cache-aside Redis
  • Tingkat beban

    • 20 VU - karakter beban kerja masih mendominasi selisihnya
    • 100 VU - selisih antar endpoint menyempit ke 1,19×–1,29× saat resource bersama jenuh

Strategi pengujian

  • Executor constant-vus, eksekusi 30 detik pada 20 dan 100 VU.
  • Threshold ditegakkan oleh k6: p95 < 500 ms, http_req_failed < 1%, checks > 99%.
  • Kesetaraan payload diverifikasi dalam byte absolut supaya response yang lebih kecil tidak memalsukan kemenangan kecepatan.
  • Check k6 memvalidasi isi body (count === 100) dan header cache X-Cache: HIT, bukan cuma status 200.
  • Key Redis dihapus sebelum tiap eksekusi cache supaya TTL sisa tidak mencemari hasil.

Integritas pengukuran

Pada 100 VU, Node secara tidak terduga mencatat p95 latency lebih rendah daripada Go di kedua endpoint database. Sebelum menganggapnya sebagai hasil, saya memeriksa ke mana waktunya habis: pada /products, bagian request di luar server (p95 total dikurangi waktu tunggu di sisi server) sekitar 34 ms untuk Go berbanding sekitar 1,27 ms untuk Node - hampir 27×. Selisih data terkirim sebesar 27% (258 MB vs 203 MB) tidak bisa menjelaskan selisih waktu 27×, sehingga penyebabnya lebih mengarah ke load generator, bukan ke aplikasinya. Karena k6 dan kedua aplikasi berbagi satu laptop 8 GB, temuan itu saya tahan dulu sampai bisa diulang dengan load generator di mesin terpisah.

Arsitektur

  1. k6 (container)beban constant-vus
  2. go-api / node-apiendpoint identik
  3. PostgreSQL 16dipakai bersama, 10.000 baris
  4. Redis 7cache-aside bersama

Implementasi

Check k6 memeriksa isi response dan kondisi cache, bukan cuma status HTTP.
export const options = {
  scenarios: { load: { executor: 'constant-vus', vus: 100, duration: '30s' } },
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
    checks: ['rate>0.99'],
  },
};

export default function () {
  const res = http.get(`${BASE_URL}/products`);
  check(res, {
    'status 200': (r) => r.status === 200,
    'got 100 products': (r) => r.json().count === 100,
  });
}
Satu script menangani semua kombinasi; stack dan tingkat beban diambil dari env var.
docker run --rm -i --network bench \
  -v "$PWD/k6:/scripts" grafana/k6 run \
  -e BASE_URL=http://go-api:8080 -e STACK=go -e VUS=100 \
  /scripts/scenarios/products.js
Healthcheck Compose mencegah aplikasi start sebelum database siap.
postgres:
  image: postgres:16-alpine
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U benchuser -d benchdb"]
    interval: 5s
    timeout: 3s
    retries: 10

go-api:
  build: ./go-api
  depends_on:
    postgres: { condition: service_healthy }
Cache key dihapus sebelum tiap eksekusi cache supaya sisa TTL tidak membiaskan hasil.
docker exec bench-redis redis-cli DEL products:top100

Hasil - throughput

Throughput (request / detik)GoNode.js

20 VU

/hello

3.984,1
2.407,6

/products

978,8
725,6

/products-where-like

554
392,6

/products-with-cache

1.156,9
905,6

100 VU

/hello

3.638,1
2.843,6

/products

1.113,1
861,4

/products-where-like

577,3
447,2

/products-with-cache

1.707,1
1.439,9

Request per detik per endpoint, Go vs Node.js. Go unggul pada throughput di semua endpoint dan kedua tingkat beban. Sumber: k6/results/*.json.

Hasil

712,788

Request

dari 16 eksekusi

0.00%

Kegagalan HTTP

100%

Check fungsional lolos

1.19–1.65×

Keunggulan throughput Go

413.36 ms

p95 tertinggi

di bawah threshold 500 ms

Tautan