Pengujian ini membandingkan implementasi Go dan Node.js menggunakan endpoint, database, dan cache yang sama. Beban diuji pada dua tingkat konkurensi, yaitu 20 VU dan 100 VU. Tujuannya bukan mencari siapa yang menang, melainkan melihat bagaimana perbedaan performa keduanya berubah ketika beban meningkat lima kali lipat.
Rasio throughput Go terhadap Node. Pada 20 VU, selisihnya berbeda-beda tergantung jenis workload. Ketika beban dinaikkan menjadi 100 VU, seluruh endpoint berada pada rentang yang hampir sama, yaitu 1,19×–1,29×.
Semua grafik menggunakan skala yang sama agar hasilnya mudah dibandingkan. Semakin tinggi throughput (RPS), semakin baik performanya. Sebaliknya, semakin rendah nilai p95 latency, semakin baik waktu responsnya.
Diambil langsung dari output handleSummary() setiap run. Seluruh satuan latency dalam milidetik. Seluruh 16 run diselesaikan dengan checks 100% dan HTTP failure 0,00%.
| Stack | VU | RPS | Total req | avg | median | p95 | max | waiting p95 |
|---|---|---|---|---|---|---|---|---|
| /hello · CPU murni, tanpa I/O | ||||||||
| Go | 20 | 3.984,1 | 119.543 | 4,81 | 4,00 | 10,02 | 136,96 | 9,72 |
| Node | 20 | 2.407,6 | 72.232 | 8,15 | 7,00 | 15,87 | 487,57 | 15,71 |
| Go | 100 | 3.638,1 | 109.149 | 27,11 | 21,21 | 60,54 | 356,85 | 58,11 |
| Node | 100 | 2.843,6 | 85.366 | 34,92 | 29,52 | 69,19 | 2.789,34 | 68,84 |
| /products · SELECT ... LIMIT 100 | ||||||||
| Go | 20 | 978,8 | 29.373 | 18,84 | 15,68 | 41,22 | 255,95 | 36,43 |
| Node | 20 | 725,6 | 21.782 | 26,61 | 23,76 | 49,00 | 347,90 | 46,70 |
| Go | 100 | 1.113,1 | 33.428 | 85,48 | 72,24 | 193,01 | 451,22 | 158,97 |
| Node | 100 | 861,4 | 25.920 | 114,97 | 106,80 | 172,60 | 1.070,85 | 171,33 |
| /products-where-like · LIKE '%nomor 99%', sequential scan | ||||||||
| Go | 20 | 554,0 | 16.642 | 34,43 | 31,31 | 70,20 | 147,36 | 67,74 |
| Node | 20 | 392,6 | 11.789 | 49,30 | 42,36 | 98,99 | 423,64 | 95,16 |
| Go | 100 | 577,3 | 17.391 | 170,63 | 138,38 | 413,36 | 1.330,50 | 410,80 |
| Node | 100 | 447,2 | 13.509 | 220,39 | 200,34 | 372,30 | 748,22 | 367,61 |
| /products-with-cache · Redis cache-aside, 100% HIT | ||||||||
| Go | 20 | 1.156,9 | 34.775 | 16,03 | 13,40 | 34,98 | 257,12 | 29,78 |
| Node | 20 | 905,6 | 27.209 | 21,03 | 17,99 | 42,91 | 146,54 | 38,92 |
| Go | 100 | 1.707,1 | 51.315 | 55,00 | 49,90 | 109,30 | 288,79 | 85,59 |
| Node | 100 | 1.439,9 | 43.365 | 66,99 | 62,55 | 113,07 | 1.268,35 | 98,47 |
Pada beban 20 VU, perbedaan performa masih cukup dipengaruhi oleh jenis workload. Endpoint tanpa I/O menunjukkan keunggulan terbesar sebesar 1,65×, sedangkan endpoint yang melibatkan database atau cache memiliki selisih 1,28×–1,41×. Ketika beban dinaikkan menjadi 100 VU, pola tersebut menghilang dan seluruh endpoint berkumpul pada rentang 1,19×–1,29×.
Hal ini terjadi karena pada tingkat konkurensi yang tinggi, faktor pembatasnya bukan lagi seberapa cepat bahasa pemrograman mengeksekusi kode, melainkan panjang antrean pada resource seperti CPU, connection pool, dan jaringan. Ketika resource tersebut mulai mencapai kapasitasnya, keunggulan arsitektur masing-masing bahasa menjadi semakin kecil karena keduanya menghadapi bottleneck yang sama.
Pada 100 VU, Go masih mampu menghasilkan throughput sekitar 29% lebih tinggi dibanding Node pada endpoint database. Namun nilai p95 latency justru lebih tinggi, yaitu 193,01 ms berbanding 172,60 ms pada /products, dan 413,36 ms berbanding 372,30 ms pada /products-where-like.
Hasil ini menunjukkan adanya trade-off antara throughput dan latency. Go memproses lebih banyak request secara bersamaan ke database sehingga antrean menjadi lebih panjang. Sementara itu, event loop tunggal pada Node secara alami membatasi jumlah request yang diproses secara paralel, sehingga throughput lebih rendah tetapi latency tetap lebih terjaga.
Perlu diperhatikan bahwa pada endpoint /products, selisih antara p95 Go dan waktu server-side mencapai sekitar 34 ms, sedangkan pada Node hanya sekitar 1,3 ms. Selisih tersebut kemungkinan berasal dari sisi load generator, bukan dari aplikasi. Karena itu, temuan ini masih perlu divalidasi menggunakan load generator yang berjalan di mesin terpisah.
Endpoint ini merupakan satu-satunya yang throughput Go-nya justru menurun ketika beban dinaikkan dari 20 VU ke 100 VU, yaitu dari 3.984,1 menjadi 3.638,1 RPS. Pada saat yang sama, nilai p95 latency meningkat cukup signifikan dari 10,02 ms menjadi 60,54 ms.
Karena endpoint ini tidak melakukan operasi I/O, performanya hampir sepenuhnya bergantung pada kemampuan CPU. Hasil tersebut menunjukkan bahwa kapasitas CPU sudah mencapai batasnya bahkan sebelum pengujian mencapai 100 VU. Penambahan VU setelah titik saturasi tidak menambah kapasitas, melainkan hanya memperpanjang antrean.
Sebaliknya, Node masih mengalami peningkatan throughput sebesar 18,1% pada endpoint yang sama karena kapasitasnya belum sepenuhnya terpakai pada 20 VU. Hasil ini menunjukkan bahwa melakukan benchmark hanya pada satu tingkat beban dapat menghasilkan kesimpulan yang kurang akurat.
Dibandingkan endpoint yang langsung mengakses database, penggunaan Redis memberikan peningkatan throughput sebesar 18,2% (Go) dan 24,8% (Node) pada 20 VU. Ketika beban dinaikkan menjadi 100 VU, peningkatannya menjadi 53,4% dan 67,2%.
Pada beban rendah, database belum menjadi bottleneck sehingga cache belum memberikan dampak yang signifikan. Ketika jumlah request meningkat dan database mulai menjadi faktor pembatas utama, cache mampu mengurangi beban tersebut sehingga peningkatan performanya menjadi jauh lebih terasa. Hal ini menunjukkan bahwa efektivitas sebuah optimasi sangat bergantung pada kondisi beban saat pengujian dilakukan.
Nilai maksimum latency pada Node lebih tinggi di tiga dari empat endpoint, termasuk lonjakan hingga hampir 2,8 detik pada endpoint /hello saat 100 VU, meskipun median latency-nya tetap rendah pada 29,52 ms.
Pola ini konsisten dengan karakteristik event loop tunggal, yang sesekali mengalami penumpukan pekerjaan sehingga menghasilkan lonjakan latency. Pengecualiannya adalah endpoint /products-where-like, di mana Go memiliki latency maksimum lebih tinggi (1.330 ms) karena endpoint tersebut menghasilkan antrean paling panjang selama pengujian.
Dalam konteks produksi, nilai-nilai inilah yang umumnya menjadi sumber keluhan pengguna, bukan nilai rata-ratanya.
Agar hasil perbandingan tetap adil, kedua aplikasi menggunakan query SQL yang sama, ukuran connection pool yang sama, cache key dan TTL yang identik, serta berbagi instance PostgreSQL dan Redis yang sama. Seluruh pengujian dijalankan menggunakan script k6 yang sama, dengan tingkat beban diatur melalui environment variable.
Ukuran payload juga telah diverifikasi agar tetap konsisten pada kedua stack. Selisihnya konstan pada rentang 109–128 byte di kedua tingkat beban, meskipun ukuran body membesar dari 136 byte menjadi lebih dari 8 KB. Selisih tersebut hanya berasal dari tambahan header bawaan Express dan tidak memengaruhi ukuran body respons.
Sebelum benchmark dijalankan, seluruh endpoint diverifikasi secara manual untuk memastikan respons yang diberikan sudah benar. Script k6 juga tidak hanya memeriksa status HTTP 200, tetapi juga memvalidasi isi respons. Untuk endpoint cache, key Redis dihapus sebelum setiap pengujian sehingga seluruh pengukuran dimulai dari kondisi yang sama. Seluruh 16 pengujian diselesaikan dengan checks 100% dan HTTP failure 0,00%.