Kata “cepat” yang tidak didukung angka hanyalah perasaan — dan perasaan berubah tergantung siapa yang menilai. Pengujian performa mengubah klaim itu menjadi data: berapa milidetik latensi pada beban berapa pengguna, dan angka mana yang memburuk setelah perubahan kemarin. Pada chapter penutup section ini kita akan membahas tiga jenis pengujian performa, cara membaca statistik p50/p95/p99 secara jujur, alat load test yang umum, pentingnya pemanasan sebelum mengukur, serta bagaimana anggaran performa dan deteksi regresi menjaga kualitas secara otomatis di CI. Matematika persentil dan logika anggaran kita uji langsung dengan dataset tetap; alat load test sendiri membutuhkan aplikasi yang hidup sehingga disajikan sebagai contoh perintah.

Tiga Jenis Pengujian Performa #

Tidak ada satu jenis pengujian untuk semua pertanyaan; yang Anda tanyakan menentukan jenis ujinya. Tabel berikut memetakan tiga jenis utama:

JenisPertanyaan yang dijawabDurasi khasYang diamati
Load testapakah sistem tahan beban harian yang ditargetkan?15–60 menitlatensi, throughput, error rate pada beban normal dan puncak
Stress testdi titik berapa sistem mulai gagal?bertahap meningkatbatas kapasitas, perilaku saat kelebihan beban, pemulihan
Soak testapakah sistem stabil dalam jangka panjang?berjam-jam hingga berhari-harikebocoran memori, koneksi yang menggantung, latensi yang merambat naik

Load test adalah jenis yang paling sering Anda jalankan: tetapkan target realistis (misalnya 200 pengguna bersamaan) lalu pastikan latensi dan error rate tetap dalam batas. Stress test sengaja mendorong melampaui batas untuk menemukan titik runtuh — lebih baik menemukannya di ruang uji daripada saat kampanye promosi. Soak test menangkap penyakit lambat yang tidak terlihat pada uji singkat: memori yang bocor sedikit demi sedikit atau koneksi database yang tidak pernah ditutup baru terasa setelah berjam-jam, dan grafik latensi yang menanjak perlahan adalah gejalanya.

Statistik yang Jujur: p50, p95, dan p99 #

Rata-rata adalah statistik yang paling mudah menipu: seratus permintaan cepat dapat menyembunyikan satu permintaan yang sangat lambat. Persentil membedah distribusinya — p50 (median) berarti separuh permintaan lebih cepat dari angka itu, p95 berarti 95% lebih cepat, p99 berarti 99% lebih cepat. Mari kita hitung ketiganya pada dataset latensi tetap:

php
<?php
function persentil(array $sampel, float $persen): float
{
    $urut = $sampel;
    sort($urut);
    $indeks = (int) ceil($persen / 100 * count($urut)) - 1;
    return (float) $urut[max(0, $indeks)];
}

// 100 sampel latensi (ms) — dataset tetap agar hasil dapat direproduksi
$sampel = [];
for ($i = 1; $i <= 100; $i++) {
    // 96 permintaan cepat (10-12 ms) dan 4 permintaan lambat (205-280 ms)
    $sampel[] = ($i % 25 === 0) ? 180 + $i : 10 + ($i % 3);
}

$rataRata = array_sum($sampel) / count($sampel);

printf("Rata-rata: %.2f ms\n", $rataRata);
printf("p50: %.1f ms\n", persentil($sampel, 50));
printf("p95: %.1f ms\n", persentil($sampel, 95));
printf("p99: %.1f ms\n", persentil($sampel, 99));

Output yang Dihasilkan:

text
Rata-rata: 20.26 ms
p50: 11.0 ms
p95: 12.0 ms
p99: 255.0 ms

Function persentil() mengurutkan salinan sampel, menghitung posisi indeks untuk persentil yang diminta, lalu mengambil nilai pada posisi tersebut. Dataset berisi 96 permintaan cepat (10–12 ms) dan 4 permintaan lambat (205–280 ms) — komposisi yang realistis ketika sebagian kecil permintaan menabrak jalur lambat seperti cache miss atau query berat. Hasilnya bercerita: pengalaman “umum” (p50) memang sehat di 11 ms, tetapi p99 mencapai 255 ms. Rata-rata 20,26 ms tidak mewakili siapa-siapa — hampir semua pengguna mengalami 11–12 ms, sementara satu dari seratus mengalami 255 ms. Angka p99 itulah yang menentukan cerita pengguna terburuk: pada satu juta permintaan per hari, 1% berarti sepuluh ribu pengguna menabrak jalur lambat tersebut.

Alat Load Test #

Alat load test mengirim banyak permintaan HTTP bersamaan lalu melaporkan statistiknya. Tiga nama yang paling sering dijumpai:

AlatBentukKelebihanKeterbatasan
wrkcommand-line ringansangat cepat, satu baris perintahskenario terbatas tanpa skrip Lua
ApacheBench (ab)command-line klasiktersedia di mana saja bersama Apachestatistik terbatas, pengembangan minimal
k6skenario JavaScriptskenario bertahap (login → telusuri → checkout), threshold bawaanperlu biner terpisah
bash
# ApacheBench: 200 permintaan, 10 koneksi paralel
ab -n 200 -c 10 https://contoh.example.com/produk

# wrk: 2 thread, 50 koneksi, selama 30 detik
wrk -t2 -c50 -d30s https://contoh.example.com/produk

# k6: jalankan skenario dari berkas JavaScript
k6 run skenario.js

Mulailah dari alat paling sederhana yang menjawab pertanyaan Anda: satu baris ab cukup untuk membandingkan dua versi endpoint. Naiklah ke k6 ketika Anda butuh skenario multi-langkah — pengguna login, menelusuri katalog, menambahkan ke keranjang — atau threshold otomatis yang menggagalkan uji saat p95 melewati batas. Berikut contoh skenario k6 yang memasang threshold pada p95:

javascript
import http from "k6/http";
import { check } from "k6";

export const options = {
    vus: 50,          // 50 pengguna virtual
    duration: "60s",  // selama 60 detik
    thresholds: {
        // Gagal bila p95 GET /produk melewati 300 ms
        "http_req_duration{status:200}": ["p(95)<300"],
    },
};

export default function () {
    const res = http.get("https://contoh.example.com/produk");
    check(res, { "status 200": (r) => r.status === 200 });
}

Pemanasan Sebelum Mengukur #

Pengukuran yang jujur dimulai setelah sistem “memanas”: opcache terisi, koneksi database terbangun, cache internal terisi jalur-jalur panas. Tanpa pemanasan, iterasi-iterasi awal yang lambat mencemari rata-rata dan Anda mengoptimalkan dingin yang tidak akan pernah dilihat pengguna. Pola pengukurannya:

php
<?php
function ukurRataRata(callable $fungsi, int $iterasi, int $pemanasan = 0): float
{
    // Pemanasan: jalur kode "memanas" lebih dulu (opcache, cache internal)
    for ($i = 0; $i < $pemanasan; $i++) {
        $fungsi();
    }

    $mulai = hrtime(true); // waktu resolusi nanodetik
    for ($i = 0; $i < $iterasi; $i++) {
        $fungsi();
    }
    $durasiNs = hrtime(true) - $mulai;

    return $durasiNs / $iterasi / 1e6; // milidetik per iterasi
}

Parameter $pemanasan menjalankan function beberapa kali tanpa diukur lebih dulu; pengukuran dengan hrtime() (resolusi nanodetik) baru dimulai setelahnya, dan hasilnya dibagi jumlah iterasi menjadi milidetik per panggilan. Jalankan pengukuran beberapa kali dan lihat sebarannya, bukan sekali lalu percaya. Dan ingat aturan emas micro-benchmark: angka absolut berbeda antar mesin, jadi bandingkan implementasi di mesin yang sama pada momen yang sama — bukan hasil kemarin di laptop dengan hasil hari ini di server.

Anggaran Performa di CI #

Anggaran performa (performance budget) mengubah target menjadi pengujian yang bisa lulus atau gagal: tetapkan batas untuk metrik-metrik kunci, lalu biarkan CI membandingkan hasil uji terhadapnya. Logikanya sederhana sehingga bisa kita jalankan langsung:

php
<?php
function periksaAnggaran(array $anggaran, array $aktual): array
{
    $hasil = [];
    foreach ($anggaran as $metrik => $batas) {
        $hasil[] = sprintf(
            "%-14s %s (batas %s)",
            $metrik,
            $aktual[$metrik] <= $batas ? "LULUS" : "GAGAL",
            $batas
        );
    }
    return $hasil;
}

$anggaran = ["p95_ms" => 300, "jumlah_query" => 10, "memori_mb" => 64];
$aktual = ["p95_ms" => 241, "jumlah_query" => 9, "memori_mb" => 58];

foreach (periksaAnggaran($anggaran, $aktual) as $baris) {
    echo $baris . "\n";
}

Output yang Dihasilkan:

text
p95_ms         LULUS (batas 300)
jumlah_query   LULUS (batas 10)
memori_mb      LULUS (batas 64)

Setiap metrik dibandingkan dengan batasnya dan dilaporkan LULUS atau GAGAL — tiga metrik pada contoh ini lolos semua. Di pipeline CI, job menjalankan skenario load test terhadap build baru, mengumpulkan metrik aktual, lalu keluar dengan exit code gagal bila ada satu saja baris GAGAL; merge tertolak sampai angkanya diperbaiki atau anggarannya direvisi secara sadar. Pilih sedikit metrik yang benar-benar mencerminkan pengalaman pengguna: p95 endpoint utama, jumlah query per halaman, dan puncak memori adalah trio yang bagus untuk mulai.

Deteksi Regresi #

Anggaran menjawab “apakah masih dalam batas”; deteksi regresi menjawab pertanyaan yang lebih tajam: “apakah lebih buruk daripada sebelumnya?”. Bandingkan hasil hari ini terhadap baseline — hasil terbaik yang stabil — dengan toleransi untuk menyerap derau pengukuran:

php
<?php
function cekRegresi(float $baseline, float $sekarang, float $toleransi): string
{
    if ($sekarang <= $baseline * (1 + $toleransi)) {
        return "aman";
    }
    return sprintf("REGRESI: %.1f%% di atas baseline", ($sekarang / $baseline - 1) * 100);
}

echo cekRegresi(200.0, 228.0, 0.15) . "\n"; // naik 14%: masih dalam toleransi
echo cekRegresi(200.0, 271.0, 0.15) . "\n"; // naik 35,5%: regresi

Output yang Dihasilkan:

text
aman
REGRESI: 35.5% di atas baseline

Kenaikan dari 200 ms ke 228 ms (+14%) masih ditoleransi — selisih sekecil itu sering kali hanya derau mesin — sedangkan 271 ms (+35,5%) melampaui toleransi 15% dan ditandai regresi. Alur kerjanya: baseline disimpan sebagai artefak (berkas atau hasil CI), setiap build membandingkan diri terhadapnya, dan setiap perbaikan performa yang bertahan memperbarui baseline agar ambang terus naik. Ketika regresi terdeteksi, perjalanan git bisect pada commit-commit terbaru biasanya langsung menemukan pelakunya.

Kesalahan Umum #

Pengujian performa mudah dilakukan salah dengan hasil yang tampak meyakinkan. Kesalahan-kesalahan yang paling sering terjadi:

  • Hanya menguji localhost — latensi jaringan nyata tidak terwakili; localhost tetap berguna untuk membandingkan dua versi kode, tetapi jangan menyimpulkan pengalaman pengguna darinya.
  • Tanpa pemanasan — iterasi dingin mencemari statistik dan menghasilkan kesimpulan yang salah tentang kode yang sebenarnya sehat.
  • Data tidak realistis — menguji tabel berisi sepuluh baris padahal produksi memiliki sepuluh juta; indeks dan query berperilaku sangat berbeda pada kedua skala itu.
  • Hanya melihat rata-rata — p99 yang buruk tersembunyi di balik rata-rata yang manis; selalu lapor persentil.
  • Menguji cache yang selalu dingin atau selalu panas — keduanya mengukur dunia yang berbeda; uji keduanya karena pengguna nyata berada di keduanya.
  • Beban lain ikut berjalan — build di mesin yang sama dengan load test membuat angkanya bergantung pada siapa yang kebagian CPU; sisihkan mesin untuk pengukuran.

Ringkasan #

Pengujian performa menutup siklus yang dibangun seluruh section ini: memahami metrik, memprofil, meng-cache, mengoptimalkan query, menekan memori, dan menstreaming respons — semuanya bermuara pada angka yang membuktikan hasilnya. Kita telah membedah tiga jenis pengujian (load, stress, soak), membaca p50/p95/p99 secara jujur, memilih alat dari ab hingga k6, mengukur setelah pemanasan, dan menjaga kualitas lewat anggaran performa serta deteksi regresi di CI. Dengan siklus ukur–perbaiki–uji yang berjalan otomatis, performa berhenti menjadi perasaan dan menjadi fitur aplikasi yang dijaga setiap hari — warisan terbaik yang bisa dibawa dari section ini ke proyek PHP Anda berikutnya.

Referensi #