Setiap ada flash sale, API kamu melambat lalu ikut mati. Frontend menyalahkan backend, backend menyalahkan database, dan tidak ada satu pun yang punya angka untuk membuktikan apa-apa. k6 menutup lubang itu: alat load testing open source dari Grafana yang menembakkan ribuan request virtual ke endpoint kamu, lalu mengembalikan latensi, tingkat kegagalan, dan kapasitas maksimum dalam bentuk metrik yang bisa dibaca siapa pun.
Apa itu k6 dan kenapa bukan JMeter
k6 ditulis dalam Go, tapi skrip ujinya ditulis pakai JavaScript. Bedanya dengan JMeter yang menyeret kamu lewat GUI: k6 adalah satu binary ringan, skrip uji bisa di-commit ke Git seperti kode biasa, dan hasilnya gampang dipipakan ke pipeline CI. Kamu menulis fungsi JavaScript, k6 menjalankannya sebagai virtual user, dan semuanya selesai lewat terminal.
Instalasi k6
Pilih sesuai OS kamu. Di macOS pakai Homebrew, di Windows pakai winget. Setelah itu cek versinya untuk memastikan binary sudah terpasang.
# macOS
brew install k6
# Windows (PowerShell)
winget install k6
# Cek versi
k6 version
Kalau kamu sudah terbiasa dengan Docker, kamu tidak perlu install apa pun — cukup jalankan container grafana/k6 dan mount skripmu. Cara ini paling rapi buat dipakai di dalam pipeline CI.
Siapkan target: API sederhana
Sebelum menembak, kita butuh target. Saya pakai FastAPI karena setup-nya dua menit. Kalau kamu belum pernah menyentuhnya, baca dulu Cara Membuat REST API dengan FastAPI — tapi untuk tutorial ini, file berikut sudah cukup.
# main.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: int
@app.get("/")
def root():
return {"message": "ok"}
@app.post("/items")
def create_item(item: Item):
return {"id": 1, "name": item.name, "price": item.price}
pip install fastapi uvicorn
uvicorn main:app --reload
Skrip k6 pertamamu
Buat file bernama script.js. Struktur skrip k6 cuma dua bagian: blok options yang mengatur berapa banyak virtual user dan berapa lama uji berjalan, lalu fungsi default yang dijalankan setiap virtual user secara berulang.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10,
duration: '30s',
};
export default function () {
const res = http.get('http://localhost:8000/');
check(res, {
'status 200': (r) => r.status === 200,
});
sleep(1);
}
Di sini vus: 10 artinya sepuluh virtual user berjalan bersamaan, dan duration: '30s' artinya mereka terus menembak selama 30 detik. Tiap user melakukan GET ke /, memastikan statusnya 200 lewat check, lalu tidur 1 detik sebelum request berikutnya. Jalankan dengan perintah di bawah.
k6 run script.js
Membaca output: metrik yang penting
k6 mencetak ringkasan seperti ini setelah uji selesai. Jangan panik melihat banyaknya angka — cukup fokus ke lima baris yang saya pangkas di bawah.
checks.........................: 100.00% ✓ 300 ✗ 0
http_req_duration.............: avg=12.34ms min=4.1ms med=10.2ms max=89.3ms p(90)=18.4ms p(95)=24.1ms
http_req_failed..............: 0.00% ✓ 0 ✗ 300
http_reqs.....................: 300 9.98/s
vus...........................: 10 min=10 max=10
http_req_duration mengukur berapa lama satu request selesai dari mulai dikirim sampai respons diterima. Perhatikan p(95) dan p(99), bukan avg — rata-rata menutupi outlier yang bikin sebagian user menunggu lama. http_req_failed adalah persentase request yang gagal dan seharusnya 0 di uji normal. http_reqs menunjukkan total request serta rate per detik, sementara vus mencatat jumlah virtual user aktif.
p(95) artinya 95% request selesai dalam waktu itu. Kalau p(95) melonjak jauh di atas median, ada segelintir request yang jauh lebih lambat dari mayoritas — biasanya pertanda awal bottleneck.
Threshold: jadikan angka sebagai aturan
Angka di terminal cuma berguna kalau ada yang membacanya. Threshold mengubah batasan menjadi keputusan otomatis: kalau metrik menembus ambang, k6 menandai uji sebagai gagal dan keluar dengan exit code non-zero. Itu yang membuat pipeline CI/CD dengan GitHub Actions kamu bisa memblokir deploy secara otomatis.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50,
duration: '1m',
thresholds: {
http_req_duration: ['p(95)<200'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('http://localhost:8000/');
check(res, { 'status 200': (r) => r.status === 200 });
sleep(1);
}
p(95)<200 berarti 95% request harus selesai di bawah 200 milidetik. rate<0.01 berarti tingkat kegagalan harus di bawah 1%. Satu saja tembus, k6 keluar dengan kode gagal — tanpa perlu kamu bolak-balik membaca laporan.
Skenario ramp-up: simulasi traffic bertahap
Traffic asli tidak melompat dari 0 ke 1000 dalam sedetik. Ia merangkak naik. Blok stages memodelkan kenaikan itu supaya hasil ujimu mendekati kondisi nyata.
export const options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m', target: 100 },
{ duration: '30s', target: 0 },
],
};
Urutannya: 30 detik pertama naik dari 1 ke 20 VU, satu menit berikutnya merangkak ke 100, lalu 30 detik terakhir turun kembali ke 0. Pola ini standar untuk load test. Untuk stress test, naikkan target terus sampai API mulai menolak request — catat di angka berapa itu terjadi, karena di situlah batas kapasitasmu.
Menguji endpoint POST dengan payload
GET saja belum cukup. Sebagian besar beban nyata datang dari POST yang membawa payload JSON. Begini cara mengirimnya.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 30,
duration: '20s',
};
export default function () {
const payload = JSON.stringify({
name: 'laptop',
price: 15000000,
});
const params = {
headers: { 'Content-Type': 'application/json' },
};
const res = http.post('http://localhost:8000/items', payload, params);
check(res, {
'status 200': (r) => r.status === 200,
'balasan json': (r) => r.json('name') === 'laptop',
});
sleep(1);
}
r.json('name') membaca field dari badan respons JSON. Kalau endpoint-mu butuh autentikasi, tambahkan header Authorization di params — k6 tidak menyimpan state antar-request, jadi kamu harus login dulu lalu memakai token yang didapat, atau meng-hardcode token untuk keperluan uji.
Jangan pernah menjalankan load test ke server production tanpa izin. k6 memproduksi traffic yang sangat besar dan bisa dianggap serangan DDoS oleh tim ops maupun penyedia hosting kamu. Pakai staging, server lokal, atau koordinasikan dulu dengan tim yang mengelola server itu.
Menyimpan hasil dan visualisasi di Grafana
Tabel di terminal bagus untuk iterasi cepat, tapi kamu butuh riwayat kalau ingin membandingkan performa dari waktu ke waktu. k6 bisa menulis hasil ke JSON atau CSV, atau mengirimnya langsung ke Prometheus dan Grafana kalau kamu sudah punya stack observability.
# Simpan hasil ke file JSON
k6 run --out json=results.json script.js
Mulai dari satu endpoint yang paling bikin kamu khawatir. Jalankan uji 30 VU, catat p(95)-nya, lalu naikkan target bertahap sampai ketemu titik patahnya. Angka itu yang kamu bawa ke rapat — bukan “rasanya sih lemot”.