Menyusun campuran workload average, spike, dan soak dalam satu skrip dengan scenarios executor, memisahkan trafik per endpoint lewat group, dan mengukur hal spesifik dengan custom metrics Trend dan Rate dari k6/metrics.

Di episode 8 kalian menyalurkan data uji ke dalam skrip — beban yang dihasilkan sekarang realistis dari sisi input. Tapi perhatikan: semua contoh sejauh ini memakai satu model beban tunggal. vus: 10 selama satu menit menjawab pertanyaan "bagaimana server menangani beban stabil?" — namun tidak menjawab "bagaimana server menangani lonjakan lima kali lipat?" atau "apakah ada kebocoran memori setelah setengah jam?".
Episode ini menutup fondasi dengan menyatukan ketiganya. Kita akan menyusun skenario kompleks dan multi-endpoint workloads: memahami tiga model beban besar (average, spike, soak), menjalankan beberapa model dalam satu skrip lewat opsi scenarios, memisahkan trafik per endpoint dengan group(), dan mengukur hal yang spesifik bagi bisnis dengan custom metrics Trend dan Rate. Ini episode paling dekat dengan pekerjaan load tester profesional yang sesungguhnya.
Sebelum menulis konfigurasi, pahami mengapa tiga model ini ada. Masing-masing menjawab pertanyaan bisnis yang berbeda, dan mengabaikan salah satunya berarti membiarkan kelas kegagalan tertentu lolos:
| Model | Tujuan | Bentuk Beban | Pertanyaan yang Dijawab |
|---|---|---|---|
| Average / Load | Kondisi normal puncak | Naik bertahap, stabil, turun | Bagaimana performa saat trafik harian tersibuk? |
| Spike | Lonjakan mendadak | Naik drastis dalam hitungan detik | Apakah server selamat dari ledakan trafik? |
| Soak | Ketahanan jangka panjang | Konstan, durasi panjang (jam) | Adakah degradasi karena kebocoran memori atau koneksi? |
Ketiganya tidak saling menggantikan — mereka melengkapi. Tim yang matang menjalankan average setiap rilis, spike sebelum event besar, dan soak berkala untuk memvalidasi stabilitas jangka panjang. Di episode 9 ini kalian akan menjalankan ketiganya dalam satu perintah.
Kunci dari "satu skrip, banyak beban" adalah opsi scenarios. Ia memungkinkan kalian mendefinisikan beberapa skenario yang berjalan bersamaan, masing-masing dengan executor, beban, dan fungsi eksekusinya sendiri:
export const options = {
scenarios: {
smoke: {
executor: "shared-iterations",
vus: 1,
iterations: 10,
exec: "checkoutFlow",
},
load: {
executor: "ramping-vus",
startTime: "30s",
stages: [
{ duration: "1m", target: 80 },
{ duration: "3m", target: 80 },
{ duration: "1m", target: 0 },
],
exec: "checkoutFlow",
},
soak: {
executor: "constant-vus",
startTime: "1m",
vus: 40,
duration: "15m",
exec: "checkoutFlow",
},
},
};Baca strukturnya: setiap skenario punya executor (mekanisme beban), exec (fungsi yang dijalankan), dan opsional startTime (kapan mulai relatif terhadap awal test). Mari petakan ke model workload:
shared-iterations — jumlah total iterasi dibagi merata ke VU. Cocok untuk smoke test: cukup satu VU dan beberapa iterasi untuk memvalidasi kebenaran fungsional sebelum beban besar datang.ramping-vus — VU naik-turun mengikuti stages. Jantung average load dan spike (dengan stages yang menaikkan target sangat cepat).constant-vus — jumlah VU tetap selama durasi tertentu. Bentuk alami soak test.Ada dua executor tambahan yang perlu kalian kenali: constant-arrival-rate dan ramping-arrival-rate — berbasis throughput (jumlah iterasi per detik) alih-alih jumlah VU. Kalian memakainya saat targetnya adalah request per second yang harus dipertahankan, bukan sekadar jumlah pengguna.
Satu aturan emas saat memakai scenarios: vus, duration, dan stages level atas tidak boleh digunakan — semuanya hidup di dalam masing-masing skenario. Jika tetap dipakai, k6 mengabaikannya atau menolak, dan hasilnya membingungkan.
Satu skrip multi-endpoint berarti hasil http_req_duration menggabungkan semua URL. Untuk melihat performa per endpoint, k6 sudah memberi tag url per request. Tapi untuk pemisahan yang lebih bermakna secara bisnis — "alur pembelian" vs "alur pencarian" — gunakan group():
import http from "k6/http";
import { check, group } from "k6";
export function checkoutFlow() {
group("browse", () => {
const res = http.get("https://api.example.com/products");
check(res, { "daftar produk 200": (r) => r.status === 200 });
});
group("beli", () => {
const res = http.post("https://api.example.com/orders", "{}", {
headers: { "Content-Type": "application/json" },
});
check(res, { "order dibuat": (r) => r.status === 201 });
});
}group() membungkus blok request dengan label yang muncul di summary — durasi total, request count, dan check masing-masing grup dilaporkan terpisah. Ini mengubah output dari "kumpulan URL" menjadi "cerita bisnis": berapa lama fase browse? berapa lama fase beli? Bagi tim non-teknis, perbedaan ini sangat berharga.
Metrik bawaan k6 mengukur HTTP secara umum. Tapi bisnis kalian punya metrik sendiri — durasi pembayaran, rasio order gagal, jumlah transaksi per detik. k6/metrics menyediakan empat tipe untuk ini: Counter (jumlah total), Gauge (nilai terakhir), Trend (distribusi nilai, mirip http_req_duration), dan Rate (rasio nilai benar-salah). Dua yang paling sering dipakai: Trend dan Rate.
import http from "k6/http";
import { check } from "k6";
import { Trend, Rate } from "k6/metrics";
const paymentLatency = new Trend("payment_duration", true);
const paymentErrors = new Rate("payment_errors");
export const options = {
thresholds: {
payment_duration: ["p(95)<700"],
payment_errors: ["rate<0.01"],
},
};
export default function () {
const res = http.post("https://api.example.com/payments", "{}", {
headers: { "Content-Type": "application/json" },
});
paymentLatency.add(res.timings.duration);
paymentErrors.add(res.status !== 200);
check(res, { "pembayaran diterima": (r) => r.status === 201 });
}Bedah tiga baris terpentingnya:
new Trend("payment_duration", true) — argumen kedua true memberitahu k6 bahwa ini metrik waktu, sehingga dilaporkan dalam milidetik dengan format yang sama seperti http_req_duration. Nilai ditambah dengan .add(ms).new Rate("payment_errors") — menerima boolean: add(res.status !== 200) menambah pembilang hanya saat kondisi true. Hasilnya adalah rasio error dalam desimal."p(95)<700" dan "rate<0.01" menegakkan standar pada hal yang spesifik bagi bisnis, bukan hanya HTTP mentah.Perhatikan alasan di baliknya: http_req_duration mengukur semua request di skrip, campuran browse dan beli. payment_duration mengisolasi hanya fase pembayaran. Custom metrics adalah cara mengubah data mentah menjadi sinyal yang bisa ditindaklanjuti.
Rangkai semua lapisan — scenarios, group, dan custom metrics — menjadi satu skrip profesional:
import http from "k6/http";
import { check, group } from "k6";
import { Trend, Rate } from "k6/metrics";
const paymentLatency = new Trend("payment_duration", true);
const paymentErrors = new Rate("payment_errors");
export const options = {
scenarios: {
smoke: {
executor: "shared-iterations",
vus: 1,
iterations: 5,
exec: "checkoutFlow",
},
load: {
executor: "ramping-vus",
startTime: "30s",
stages: [
{ duration: "1m", target: 80 },
{ duration: "3m", target: 80 },
{ duration: "1m", target: 0 },
],
exec: "checkoutFlow",
},
soak: {
executor: "constant-vus",
startTime: "1m",
vus: 40,
duration: "15m",
exec: "checkoutFlow",
},
},
thresholds: {
http_req_duration: ["p(95)<500"],
payment_duration: ["p(95)<700"],
payment_errors: ["rate<0.01"],
},
};
export function checkoutFlow() {
group("browse", () => {
check(http.get("https://api.example.com/products"), {
"daftar produk 200": (r) => r.status === 200,
});
});
group("beli", () => {
const pay = http.post("https://api.example.com/payments", "{}", {
headers: { "Content-Type": "application/json" },
});
paymentLatency.add(pay.timings.duration);
paymentErrors.add(pay.status !== 201);
check(pay, { "pembayaran diterima": (r) => r.status === 201 });
});
}Jalankan dengan k6 run script.js dan perhatikan yang terjadi: smoke berjalan lebih dulu memvalidasi alur, lalu load merangkak naik menekan server, dan soak memegang beban stabil selama 15 menit — ketiganya menembak fungsi checkoutFlow yang sama. Satu skrip, tiga pertanyaan bisnis, tiga jawaban terpisah.
Ketiga skenario berbagi fungsi yang sama, jadi pastikan mereka tidak saling mengganggu state — pola per-VU dari episode 5 (__VU) menjamin setiap VU punya sesi sendiri. Jika satu skenario butuh alur berbeda, tinggal definisikan fungsi exec kedua.
vus atau duration di samping scenarios. Konfigurasi level atas dan scenarios tidak boleh bercampur — beban harus hidup di dalam skenario.http_req_duration mencampur semua URL. Pisahkan dengan group() dan custom metrics.startTime untuk skenario paralel. Tanpa jeda, semua skenario menyerbu sejak detik pertama dan hasilnya tak terbaca.Di episode 9 ini kalian telah merakit fondasi lengkap load testing profesional:
scenarios: banyak executor dalam satu skrip, dengan shared-iterations untuk smoke, ramping-vus untuk load, dan constant-vus untuk soak.group(): memisahkan trafik per alur bisnis agar hasil terbaca sebagai cerita, bukan daftar URL.Trend dan Rate dari k6/metrics untuk mengukur dan mengikat standar pada hal yang spesifik.Selamat — kalian kini menulis load test yang tidak hanya mengirim request, tetapi bercerita tentang sistem. Namun ada satu lapisan yang sejauh ini kita perlakukan sebagai kotak hitam: jaringan itu sendiri. Di episode 10 kita membuka lapisan itu — Jaringan, TLS, dan Protocol Support: mengendalikan verifikasi TLS, perilaku koneksi, redirect, dan user agent, serta menguji WebSocket. Karena di bawah beban tinggi, yang pertama menyerah sering kali bukan kode aplikasi kalian, melainkan lapisan jaringannya. Sampai jumpa di episode 10!