Benchmark tool internal dengan workload nyata lebih berguna daripada benchmark synthetic ketika tujuan Anda adalah memperbaiki pengalaman developer dan performa CI/CD yang benar-benar dirasakan tim. Jika yang diuji adalah linter, test runner, build tool, atau executor pipeline, maka beban kerja terbaik biasanya bukan micro-benchmark terisolasi, melainkan operasi yang memang dijalankan tim setiap hari: install dependency, lint changed files, build production, test subset, test full suite, dan job CI pada branch aktif.

Masalah utama benchmark synthetic adalah hasilnya sering terlalu rapi: dataset kecil, cache ideal, tidak ada kontensi CPU, tidak ada I/O nyata, dan tidak ada variasi ukuran proyek. Akibatnya, tool yang terlihat unggul di demo belum tentu lebih cepat atau lebih stabil di monorepo, CI shared runner, atau laptop developer dengan cache yang sering berubah.

Mengapa benchmark synthetic sering menyesatkan

Benchmark synthetic tetap ada gunanya. Ia bagus untuk mengisolasi satu operasi spesifik, misalnya parsing, hashing, atau kompilasi file tunggal. Namun untuk evaluasi tool internal, synthetic benchmark memiliki keterbatasan besar:

  • Tidak merepresentasikan pola kerja tim. Tim jarang hanya menjalankan satu file atau satu operasi kecil.
  • Menutupi biaya integrasi, seperti startup process, dependency graph resolution, file system traversal, cache restore, dan reporting.
  • Melebihkan kondisi ideal. Banyak benchmark dijalankan di mesin bersih tanpa workload lain, padahal CI biasanya berbagi CPU, disk, dan jaringan.
  • Tidak mengukur reliabilitas. Tool yang sangat cepat tetapi flaky bisa memperlambat delivery secara total.

Dalam konteks DX dan CI/CD, pertanyaan yang lebih relevan bukan "tool mana paling cepat di benchmark vendor?", tetapi "tool mana yang memperpendek siklus edit-run-debug dan mengurangi waktu tunggu pipeline di repo kami?".

Apa yang dimaksud workload nyata

Workload nyata adalah sekumpulan skenario yang diambil dari aktivitas normal tim. Bukan simulasi generik, melainkan perintah, input, ukuran repo, dan pola perubahan yang memang terjadi. Contohnya:

  • Lint untuk file yang berubah di pull request kecil.
  • Lint full repo pada branch utama.
  • Build aplikasi frontend production dengan plugin yang benar-benar dipakai.
  • Compile backend service dengan dependency internal nyata.
  • Menjalankan unit test subset pada developer machine.
  • Menjalankan full test suite di CI.
  • Job pipeline yang memulihkan cache, mengunduh dependency, lalu build dan test.

Kalau Anda membandingkan dua tool, misalnya dua linter atau dua test runner, gunakan workload yang sama, input yang sama, dan boundary yang sama. Jangan membandingkan satu tool dalam mode incremental melawan tool lain dalam mode full scan kecuali itu memang use case yang ingin diuji.

Metrik yang sebaiknya diukur

Waktu eksekusi total penting, tetapi tidak cukup. Untuk evaluasi yang relevan, ukur beberapa metrik sekaligus.

1. Lead time per aktivitas

Ini adalah metrik paling dekat ke pengalaman nyata. Ukur berapa lama developer atau pipeline menunggu hasil.

  • Waktu lint changed files
  • Waktu full lint
  • Waktu build production
  • Waktu unit test subset
  • Waktu full test suite
  • Waktu total job CI

Gunakan median dan percentile, bukan hanya rata-rata. Rata-rata mudah bias oleh satu run ekstrem.

2. Cache hit rate

Banyak tool modern tampak cepat karena caching. Itu valid, tetapi Anda harus tahu kapan cache membantu dan kapan tidak. Ukur:

  • Hit rate cache lokal developer
  • Hit rate cache CI
  • Biaya restore dan save cache
  • Persentase run yang fallback ke full rebuild

Tool dengan cache canggih bisa unggul besar pada warm run, tetapi biasa saja pada cold run. Jika branch CI sering menghasilkan cache miss, keuntungan riil bisa lebih kecil dari angka promosi.

3. Flake rate

Tool cepat tapi tidak stabil sering lebih mahal secara operasional. Ukur seberapa sering job perlu diulang tanpa perubahan input.

  • Test yang kadang gagal tanpa perubahan kode
  • Build yang gagal karena race condition atau resource starvation
  • Runner yang timeout sporadis

Flake rate rendah sering lebih berharga daripada percepatan kecil pada durasi rata-rata.

4. Konsumsi CPU, RAM, dan I/O

Ini penting untuk shared runner, laptop developer, atau monorepo besar. Tool yang memonopoli CPU atau RAM bisa mengganggu workload lain atau memicu OOM.

  • Peak memory usage
  • CPU time atau utilisasi rata-rata
  • I/O read/write besar pada file system
  • Jumlah process dan thread

Jika dua tool punya waktu serupa, tool dengan jejak resource lebih kecil biasanya lebih mudah dioperasikan.

5. Variabilitas hasil

Tool yang sesekali sangat cepat tetapi tidak konsisten lebih sulit diprediksi. Catat:

  • Median
  • P95 atau P99 jika jumlah run cukup
  • Rentang min-max
  • Simpangan antar run pada mesin yang sama

Desain benchmark yang adil

Benchmark yang berguna harus adil, bisa diulang, dan mudah diaudit. Beberapa prinsip berikut membantu.

Gunakan baseline yang setara

Bandingkan perilaku yang benar-benar setara. Contoh:

  • Mode production lawan production, bukan development lawan production.
  • Parallelism yang eksplisit, bukan default berbeda tanpa dicatat.
  • Input file yang sama persis.
  • Konfigurasi rule lint atau transform test yang setara semampunya.

Kalau dua tool tidak mendukung fitur yang sama, dokumentasikan perbedaannya. Benchmark tanpa konteks sering menghasilkan keputusan salah.

Pisahkan cold cache dan warm cache

Ini wajib. Cold cache mengukur biaya saat cache belum tersedia. Warm cache mengukur manfaat saat kondisi ideal tercapai.

  • Cold cache: hapus cache tool, cache dependency yang relevan bila memang ingin mengukur dari nol, dan jalankan workflow.
  • Warm cache: jalankan sekali untuk mengisi cache, lalu ulangi skenario yang sama.

Laporkan kedua hasil. Dalam CI, cold cache bisa terjadi pada runner baru, key cache berubah, atau branch jarang aktif. Warm cache lebih relevan untuk loop lokal developer dan pipeline yang stabil.

Kunci lingkungan uji

Jangan mengubah terlalu banyak variabel sekaligus. Minimal catat:

  • Jenis mesin atau runner
  • Jumlah vCPU dan RAM
  • Sistem operasi
  • Kondisi concurrency: eksklusif atau shared
  • Ukuran repo dan jumlah file
  • Commit atau snapshot kode yang diuji

Kalau benchmark dijalankan di CI shared runner, hasil bisa terpengaruh noisy neighbor. Untuk keputusan investasi tool, ulangi sebagian tes di runner yang lebih terkontrol.

Ulangi beberapa kali

Satu run tidak cukup. Lakukan beberapa pengulangan untuk setiap skenario. Untuk workload CI, 5-10 run per skenario sudah jauh lebih baik daripada satu angka tunggal. Gunakan median sebagai angka utama, lalu lihat sebarannya.

Contoh eksperimen membandingkan 2 tool di repo tim

Misalkan tim ingin membandingkan dua kombinasi tool:

  • Kandidat A: tool yang saat ini dipakai tim.
  • Kandidat B: alternatif baru yang diklaim lebih cepat.

Anggap eksperimennya fokus pada tiga aktivitas: lint, build, dan test. Tujuannya bukan mencari angka absolut terbaik, melainkan melihat dampak pada workload nyata repo tim.

Skenario workload

  1. Lint PR kecil: jalankan lint hanya pada file yang berubah di 1 pull request representatif.
  2. Lint full repo: jalankan lint ke seluruh workspace.
  3. Build production: bangun artifact yang sama dengan yang dipakai deploy.
  4. Unit test subset: test paket atau folder yang sering berubah.
  5. Full test suite di CI: semua test dengan konfigurasi CI.

Pilih 2-3 PR historis yang mewakili perubahan kecil, sedang, dan cukup besar. Ini penting karena beberapa tool sangat cepat pada perubahan kecil tetapi tidak banyak berbeda pada full run.

Contoh harness sederhana

Tidak perlu framework benchmark khusus. Shell script atau job CI terpisah sering cukup, asalkan konsisten.

#!/usr/bin/env bash
set -euo pipefail

TOOL_NAME="$1"
SCENARIO="$2"
RUN_ID="$(date +%s)"

mkdir -p benchmark-results

reset_caches() {
  rm -rf .cache
  rm -rf node_modules/.cache 2>/dev/null || true
  rm -rf .tool-cache 2>/dev/null || true
}

run_and_measure() {
  local label="$1"
  shift
  /usr/bin/time -v "$@" 2>"benchmark-results/${TOOL_NAME}-${SCENARIO}-${label}-${RUN_ID}.time"
}

case "$SCENARIO" in
  lint-changed-cold)
    reset_caches
    run_and_measure cold ./scripts/run-lint-changed.sh "$TOOL_NAME"
    ;;
  lint-changed-warm)
    run_and_measure warm ./scripts/run-lint-changed.sh "$TOOL_NAME"
    ;;
  build-cold)
    reset_caches
    run_and_measure cold ./scripts/run-build.sh "$TOOL_NAME"
    ;;
  build-warm)
    run_and_measure warm ./scripts/run-build.sh "$TOOL_NAME"
    ;;
  test-full-cold)
    reset_caches
    run_and_measure cold ./scripts/run-test-full.sh "$TOOL_NAME"
    ;;
  test-full-warm)
    run_and_measure warm ./scripts/run-test-full.sh "$TOOL_NAME"
    ;;
  *)
    echo "Unknown scenario: $SCENARIO" >&2
    exit 1
    ;;
esac

Contoh di atas sengaja sederhana. Poin pentingnya:

  • Reset cache dilakukan eksplisit untuk skenario cold.
  • Perintah lint/build/test dibungkus script per skenario supaya input tetap konsisten.
  • /usr/bin/time -v dipakai untuk menangkap waktu dan memori tanpa bergantung pada tool tertentu.

Kalau benchmark dijalankan di CI, simpan output sebagai artifact agar mudah dibandingkan lintas run.

Contoh struktur script per skenario

#!/usr/bin/env bash
set -euo pipefail
TOOL_NAME="$1"

case "$TOOL_NAME" in
  candidate-a)
    ./node_modules/.bin/tool-a lint $(cat .benchmark/changed-files.txt)
    ;;
  candidate-b)
    ./node_modules/.bin/tool-b check $(cat .benchmark/changed-files.txt)
    ;;
  *)
    echo "Unknown tool: $TOOL_NAME" >&2
    exit 1
    ;;
esac

Dengan pendekatan ini, tim bisa meninjau dengan jelas apa yang dibandingkan dan apa yang tidak.

Template tabel hasil benchmark

Jangan berhenti pada satu kolom “lebih cepat”. Tabel hasil sebaiknya mencakup durasi, resource, stabilitas, dan konteks cache.

SkenarioToolCacheMedian DurasiP95Peak RAMCPU RelatifCache HitFlake RateCatatan
Lint changed filesACold.....................
Lint changed filesBCold.....................
Build productionAWarm.....................
Build productionBWarm.....................
Full test suiteACold.....................
Full test suiteBCold.....................

Jika Anda ingin membuat keputusan yang lebih tajam, tambahkan dua kolom lagi:

  • Developer impact: apakah perubahan terasa pada loop lokal?
  • Operational cost: apakah ada perubahan kompleksitas konfigurasi, debugging, atau maintenance?

Warm cache vs cold cache: cara membaca hasilnya

Perbedaan hasil warm dan cold cache sering menjadi inti keputusan.

Ketika warm cache jauh lebih baik

Ini biasanya berarti tool sangat bergantung pada cache. Keuntungannya besar bila:

  • Repo stabil dan perubahan lokal kecil.
  • CI memiliki cache yang konsisten dan cepat dipulihkan.
  • Key cache jarang invalid.

Namun ada risiko bila:

  • Cache sering rusak atau sulit dipahami.
  • Branch feature jarang mendapat hit.
  • Biaya upload/download cache hampir menyamai keuntungan komputasi.

Ketika cold cache masih kompetitif

Ini sering tanda tool punya performa dasar yang baik, bukan hanya menang karena cache. Untuk CI ephemeral atau autoscaling runner, performa cold cache sering lebih relevan daripada demo warm run.

Jebakan benchmark yang paling umum

1. Mengukur perintah yang tidak identik

Contoh klasik: satu tool menjalankan lint pada changed files, tool lain pada seluruh repo. Angka jadi tidak bermakna.

2. Menggunakan satu repo contoh yang terlalu kecil

Tool yang unggul pada proyek kecil bisa berbeda perilaku pada monorepo dengan ribuan file, banyak package, atau dependency graph kompleks.

3. Mengabaikan biaya migrasi

Tool baru mungkin lebih cepat, tetapi jika perlu rewrite config besar, custom adapter, atau perubahan workflow tim, ROI-nya bisa rendah.

4. Tidak mengukur stabilitas

Speedup kecil bisa kalah oleh flake rate tinggi, error message buruk, atau debugging yang lebih sulit.

5. Mengambil hasil vendor apa adanya

Benchmark vendor berguna sebagai indikasi awal, bukan dasar keputusan final. Vendor biasanya memilih skenario yang menguntungkan arsitekturnya.

6. Tidak mencatat kondisi lingkungan

Hasil tanpa informasi machine, cache state, dan ukuran workload hampir tidak bisa direproduksi.

Tips debugging saat hasil benchmark terasa aneh

  • Cek I/O: tool bisa lambat bukan karena komputasi, tetapi karena membaca terlalu banyak file atau direktori.
  • Cek startup cost: untuk workload kecil, startup process bisa mendominasi total waktu.
  • Cek parallelism: terlalu banyak worker bisa justru menambah kontensi CPU dan RAM.
  • Cek invalidasi cache: pastikan perubahan kecil tidak memicu rebuild penuh tanpa alasan jelas.
  • Cek antivirus, indexing, atau sinkronisasi file pada mesin developer karena itu sering mengganggu benchmark lokal.
  • Cek log verbose hanya pada investigasi, bukan pada benchmark final, karena logging tambahan sendiri bisa memengaruhi hasil.

Kapan tool lama masih layak dipertahankan

Tujuan benchmark bukan selalu membenarkan migrasi. Kadang hasilnya justru menunjukkan tool lama masih memadai.

Checklist keputusan

  • Apakah tool lama masih memenuhi target lead time tim untuk lint, build, dan test?
  • Apakah bottleneck utama sebenarnya ada di tempat lain, misalnya dependency install, network artifact, atau test database?
  • Apakah tool baru hanya unggul di synthetic benchmark, tetapi tidak signifikan pada workload nyata repo Anda?
  • Apakah tool lama lebih stabil dan lebih mudah di-debug?
  • Apakah migrasi akan menambah kompleksitas config, pelatihan, atau maintenance?
  • Apakah keuntungan tool baru sangat bergantung pada warm cache yang belum tentu konsisten di CI?
  • Apakah speedup yang ada cukup besar untuk membayar biaya migrasi dan risiko perubahan workflow?

Kalau jawaban-jawaban di atas cenderung mengarah ke stabilitas dan kecukupan performa saat ini, mempertahankan tool lama bisa menjadi keputusan yang rasional.

Rangkuman pendekatan yang bisa langsung dipakai

  1. Pilih 3-5 workload nyata dari repo tim, bukan demo generik.
  2. Bandingkan tool pada input dan mode yang setara.
  3. Ukur durasi, cache hit, RAM, CPU, dan flake rate.
  4. Pisahkan hasil cold cache dan warm cache.
  5. Ulangi beberapa run dan pakai median, bukan satu angka tunggal.
  6. Catat biaya migrasi dan dampak operasional, bukan hanya speed.

Benchmark tool internal dengan workload nyata membantu tim membuat keputusan yang lebih jujur: apakah developer benar-benar lebih cepat, apakah CI benar-benar lebih singkat, dan apakah kompleksitas baru benar-benar sepadan. Benchmark yang baik tidak mencari angka paling indah, tetapi angka yang paling berguna untuk kondisi repo dan alur kerja tim Anda.