Belajar k6 - Resilience, Retry, dan Failure Analysis
Series/Belajar k6/Episode 15
Episode 15 of 19

Belajar k6 - Resilience, Retry, dan Failure Analysis

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.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Pendahuluan

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.

Flake vs Fail: Membedakan Gangguan Sementara dan Kegagalan Nyata

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:

  • Flake — tidak deterministik, muncul sesekali, dan hilang saat diulang. Contoh: satu dari seribu request mengembalikan 500 karena cold start autoscaler, 429 karena rate limiter sesaat, atau timeout karena network hiccup.
  • Fail — konsisten dan reproduktif. Contoh: seluruh request ke endpoint baru gagal 500 sejak kode terbaru di-deploy, atau query database selalu melewati batas waktu ketika payload melewati ukuran tertentu.

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.

Strategi Retry dan Backoff

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.

Status yang Layak di-Retry

Jangan pernah retry semua kegagalan. Pengelompokan yang benar:

  • Retry: 429 Too Many Requests, seluruh 5xx, dan error jaringan/timeout. Kategori ini menandakan server sedang sibuk atau sedang pulih — percobaan ulang wajar.
  • Jangan retry: 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.

Simple Backoff dan Exponential Backoff

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.

Helper Retry dengan Backoff

tests/lib/retry.js - helper retry dengan exponential backoff
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:

tests/smoke.js - memakai helper retry
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.

Failure Analysis: Membongkar Akar Masalah dari Metrik

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:

MetrikFase yang diukurBila membengkak, periksa
http_req_blockedWaktu menunggu sebelum koneksi tersediaDNS, pool koneksi penuh, resource lokal mesin test
http_req_connectingHandshake TCP dengan serverJaringan antara client dan server, firewall, routing
http_req_tls_handshakingNegosiasi TLSCPU server TLS, cipher, sertifikat, OCSP
http_req_sendingMengirim payload requestUkuran payload, upload bandwidth
http_req_waitingMenunggu respons server (TTFB)Logika aplikasi, query database, koneksi server
http_req_receivingMenerima body responsUkuran 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.

Membaca Pola dari Breakdown

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 Umum dalam Resilience dan Failure Analysis

KesalahanDampakPerbaikan
Retry semua error termasuk 4xxWaktu test boros, beban server bertambahRetry hanya 429, 5xx, dan timeout
Retry tanpa backoffThundering herd saat server pulihPakai exponential backoff
Tanpa batas percobaanTest menggantung atau durasi membengkakTetapkan max attempts
Satu request gagal langsung dinyatakan failFalse alarm, tim memburu hantuUkur error rate, bukan kejadian tunggal
Analisis hanya http_req_durationSalah diagnosis lapisan masalahBaca breakdown per fase
Lupa bahwa retry tetap dihitung di metrikEkspektasi salah pada angka error rateSesuaikan threshold atau pakai custom metric

Penutup

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!