Membedakan gangguan sementara (flake) dari kegagalan nyata (fail), menerapkan strategi retry dengan exponential backoff untuk error transien seperti 5xx dan 429, serta membedah akar masalah bottleneck lewat breakdown metrik waktu HTTP k6 dari blocked hingga receiving.

Pada episode 14, kalian menghubungkan k6 ke ekosistem observability: metrik dialirkan ke Grafana lewat InfluxDB dan Prometheus, sehingga performa sistem bisa dilihat dalam bentuk time-series. Kalian kini bisa melihat masalah. Episode 15 ini mengajarkan cara menanggapinya dengan benar — dua skill yang sama pentingnya dalam operasi nyata.
Pertama, kita pahami perbedaan mendasar antara flake (gangguan sementara) dan fail (kegagalan nyata). Kedua, kita rancang retry dengan backoff — perilaku yang wajib dimiliki setiap client yang beradab saat server sedang tertekan. Terakhir, kita bongkar metrik waktu HTTP k6 lapis demi lapis untuk menemukan di mana tepatnya waktu hilang. Mari mulai.
Bayangkan kalian menelepon sebuah call center. Kadang terdengar nada sibuk karena semua operator sedang menangani panggilan lain — itu flake: gangguan yang hilang dengan sendirinya, dan panggilan ulang beberapa detik kemudian kemungkinan besar berhasil. Kalau nada sibuk selalu terdengar setiap kali, atau telepon diangkat tapi langsung ditutup, itu fail: ada masalah struktural yang tidak akan hilang hanya karena kalian menelepon ulang.
Dalam load testing, pembedaan ini menentukan reaksi tim:
Mengapa pembedaan ini penting? Kalau kalian memperlakukan flake sebagai fail, tim akan memburu hantu — mengganti load balancer, menambah node, mengubah konfigurasi — padahal akar masalahnya hanya satu koneksi yang kedip. Sebaliknya, kalau fail dianggap flake, bug nyata lolos ke produksi. Aturannya: retry untuk flake, investigasi untuk fail, dan jadikan error rate (bukan satu request) sebagai sinyal keputusan.
Retry adalah menebus flake: coba lagi, tetapi dengan disiplin. Ada tiga keputusan yang harus dibuat: status mana yang boleh dicoba ulang, berapa lama menunggu antar percobaan, dan berapa banyak percobaan maksimal.
Jangan pernah retry semua kegagalan. Pengelompokan yang benar:
429 Too Many Requests, seluruh 5xx, dan error jaringan/timeout. Kategori ini menandakan server sedang sibuk atau sedang pulih — percobaan ulang wajar.4xx selain 408 dan 429 (misalnya 400, 401, 403, 404). Ini kesalahan request kalian sendiri; mengulang dengan payload yang sama hanya menambah beban server dan memperlambat test.Backoff adalah jeda antar percobaan. Simple backoff menunggu durasi tetap, misalnya satu detik setiap kali. Exponential backoff menggandakan jeda tiap percobaan: 200 ms, lalu 400 ms, lalu 800 ms. Analoginya seperti menghidupkan mesin mobil di pagi yang dingin: menekan starter terus-menerus hanya menguras aki, sedangkan menunggu sebentar lalu mencoba lagi justru paling efektif. Exponential backoff juga mencegah thundering herd — puluhan ribu request yang baru saja gagal mencoba lagi di detik yang sama, membuat server yang sedang bangkit malah terjatuh lagi.
import { sleep } from 'k6';
const DEFAULT_MAX_ATTEMPTS = 4;
const DEFAULT_BASE_DELAY_MS = 200;
export function retryWithBackoff(fn, options = {}) {
const maxAttempts = options.maxAttempts || DEFAULT_MAX_ATTEMPTS;
const baseDelayMs = options.baseDelayMs || DEFAULT_BASE_DELAY_MS;
let lastResponse;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
lastResponse = fn();
if (!isRetryable(lastResponse.status)) {
return lastResponse;
}
if (attempt === maxAttempts) {
return lastResponse;
}
const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
sleep(delayMs / 1000);
}
return lastResponse;
}
function isRetryable(status) {
return status === 429 || status >= 500;
}Perhatikan alurnya: fungsi callback dieksekusi sebagai satu percobaan, hasilnya dicek terhadap status retryable, dan jeda sleep benar-benar berjalan di antara percobaan — bukan hanya perhitungan angka. Ketika semua percobaan gagal, helper mengembalikan response terakhir alih-alih melempar error, sehingga check() tetap bisa menilai hasilnya dan threshold tetap bekerja. Pemakaiannya di dalam skenario:
import { check } from 'k6';
import http from 'k6/http';
import { retryWithBackoff } from './lib/retry.js';
export default function () {
const response = retryWithBackoff(() => {
return http.get('https://api.example.com/checkout');
});
check(response, {
'checkout 200': (r) => r.status === 200,
});
}Important
Satu hal jujur yang wajib kalian tahu: setiap http.get di dalam helper dicatat k6 sebagai request terpisah, termasuk yang gagal. Artinya retry tidak menyembunyikan kegagalan dari metrik http_req_failed — yang gagal tetap dihitung. Ini justru sehat untuk transparansi; atur threshold error rate kalian secara realistis, atau gunakan custom metric jika ingin memisahkan angka request yang akhirnya sukses setelah retry dari yang benar-benar gagal.
Ketika threshold dilanggar, refleks pertama banyak orang adalah menebak: apakah database-nya? load balancer-nya? kode baru? Tebakan mahal. k6 memberi alat yang lebih baik: metrik timing breakdown yang memecah durasi sebuah request HTTP menjadi fase-fase berurutan. Dengan membandingkan fase mana yang membengkak, kalian mengubah dugaan menjadi diagnosa. Ketika k6 run script.js berakhir dengan threshold yang dilanggar, inilah peta yang harus kalian buka lebih dulu:
| Metrik | Fase yang diukur | Bila membengkak, periksa |
|---|---|---|
http_req_blocked | Waktu menunggu sebelum koneksi tersedia | DNS, pool koneksi penuh, resource lokal mesin test |
http_req_connecting | Handshake TCP dengan server | Jaringan antara client dan server, firewall, routing |
http_req_tls_handshaking | Negosiasi TLS | CPU server TLS, cipher, sertifikat, OCSP |
http_req_sending | Mengirim payload request | Ukuran payload, upload bandwidth |
http_req_waiting | Menunggu respons server (TTFB) | Logika aplikasi, query database, koneksi server |
http_req_receiving | Menerima body respons | Ukuran body, download bandwidth |
Tiga fase pertama (blocked, connecting, tls_handshaking) terjadi sebelum server sempat mengolah apa pun — di sana waktu dihabiskan di jalan. Tiga fase terakhir (sending, waiting, receiving) adalah waktu yang berkaitan dengan server dan payload. Dalam praktiknya, http_req_waiting hampir selalu biang keladi: aplikasi yang melambat membuat waktu menunggu membengkak, dan sebagai efek lanjutan http_req_blocked ikut naik karena koneksi menumpuk mengantre di pool — fenomena klasik yang sering salah didiagnosis sebagai masalah jaringan padahal akarnya di aplikasi.
Kalian bisa menyalurkan metrik http_req_* ke Grafana (episode 14) dan membandingkan p95 tiap fase di satu panel. Pola yang perlu dikenali:
waiting dan blocked naik bersamaan → aplikasi melambat, koneksi mengantre. Sasar debugging ke server aplikasi dan database.connecting atau tls_handshaking tinggi sendirian → masalah di path jaringan atau handshake; cek DNS, region, dan sertifikat.receiving tinggi dengan waiting normal → body respons membengkak; cek apakah client meminta data yang terlalu banyak.blocked tinggi tanpa connecting naik → resource mesin test kalian yang habis; kurangi VU atau naikkan mesin.Ditambah dengan error rate yang dipecah per status code — misalnya 429 mendominasi berarti rate limiting, 502 mendominasi berarti gateway atau proxy yang bermasalah — kalian punya peta yang cukup untuk menemukan lapisan yang harus diinvestigasi, tanpa menebak-nebak.
| Kesalahan | Dampak | Perbaikan |
|---|---|---|
| Retry semua error termasuk 4xx | Waktu test boros, beban server bertambah | Retry hanya 429, 5xx, dan timeout |
| Retry tanpa backoff | Thundering herd saat server pulih | Pakai exponential backoff |
| Tanpa batas percobaan | Test menggantung atau durasi membengkak | Tetapkan max attempts |
| Satu request gagal langsung dinyatakan fail | False alarm, tim memburu hantu | Ukur error rate, bukan kejadian tunggal |
Analisis hanya http_req_duration | Salah diagnosis lapisan masalah | Baca breakdown per fase |
| Lupa bahwa retry tetap dihitung di metrik | Ekspektasi salah pada angka error rate | Sesuaikan threshold atau pakai custom metric |
Episode 15 memberi kalian dua senjata untuk menghadapi degradasi: disiplin — membedakan flake dari fail, merancang retry dengan backoff dan batas percobaan, memilih status yang layak diulang — dan kejelasan — membaca breakdown metrik http_req_blocked, connecting, tls_handshaking, sending, waiting, dan receiving untuk menemukan lapisan yang sebenarnya bermasalah.
Kabar baiknya, seluruh strategi ini bisa dijalankan tanpa pengawasan manusia. Di episode 16, kita masukkan k6 ke dalam CI/CD dan workflow otomasi: GitHub Actions, GitLab CI, dan Jenkins, dengan exit code 99 sebagai bahasa universal yang menggagalkan build ketika threshold dilanggar. Performa kalian akan segera menjadi keputusan otomatis, bukan opini. Sampai jumpa!