Semua kerja section ini — index, eager loading, cache — berbagi satu kelemahan: kecepatannya bisa memburuk kembali tanpa ada yang menyadarinya. Query yang hari ini tiga buah bisa menjadi seratus setelah sebuah refactoring, dan tidak ada test fungsional yang protes karena outputnya tetap benar — hanya lebih lambat. Performance testing menutup celah itu dengan menjadikan kecepatan sebagai asersi yang diperiksa otomatis, bukan harapan yang diperiksa manual. Kita akan menulis timing di dalam test, membangun harness benchmark sederhana dengan statistik p50/p95, dan menerapkan disiplin A/B agar setiap klaim “lebih cepat” tahan diperiksa.
Timing di Dalam Test #
Bentuk paling sederhana: ukur durasi operasi di dalam feature test, lalu bandingkan dengan anggaran waktu. Test ini memeriksa endpoint daftar post terhadap anggaran longgar 0,5 detik:
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Route;
use Tests\TestCase;
class PerformaEndpointTest extends TestCase
{
use RefreshDatabase;
public function test_daftar_post_dibalas_dalam_anggaran_waktu(): void
{
Route::get('/api/daftar', function () {
return Post::with(['user', 'category'])->orderBy('id')->limit(50)->get()
->map(fn ($post) => [
'judul' => $post->judul,
'penulis' => $post->user->name,
'kategori' => $post->category->nama,
]);
});
Post::factory()->count(50)->create();
// warm-up: migrasi, kompilasi, koneksi — bukan objek pengukuran
$this->get('/api/daftar')->assertOk();
$mulai = hrtime(true);
$this->get('/api/daftar')->assertOk();
$detik = (hrtime(true) - $mulai) / 1e9;
$this->assertLessThan(
0.5,
$detik,
sprintf('Endpoint menjawab dalam %.3f detik — melebihi anggaran 0,5 detik.', $detik)
);
}
}Output yang Dihasilkan:
PASS Tests\Feature\PerformaEndpointTest
✓ daftar post dibalas dalam anggaran waktu 0.21sDua keputusan di kode ini penting. Pertama, warm-up: request pertama membayar biaya sekali-jalan (kompilasi, koneksi), jadi yang diukur adalah request kedua. Kedua, anggaran 0,5 detik yang longgar — bukan 50 ms yang ketat. Waktu eksekusi bervariasi antar mesin dan antar eksekusi; threshold yang ketat melahirkan test flaky yang gagal acak di CI. Anggaran longgar tetap menangkap musuh utamanya: regresi yang membuat endpoint 10 kali lebih lambat. Test jumlah query dari chapter Eager Loading adalah pasangan deterministiknya — jumlah query stabil, waktu tidak.
Harness Benchmark: p50 dan p95 #
Untuk angka yang jujur, satu pengukuran tidak pernah cukup — kita butuh distribusi. Function kecil ini memukul endpoint N kali dan menghitung statistiknya:
use Illuminate\Http\Request;
function benchmark(string $uri, int $n): array
{
$waktu = [];
for ($i = 0; $i < $n; $i++) {
$mulai = hrtime(true);
$response = app()->handle(Request::create($uri));
$waktu[] = (hrtime(true) - $mulai) / 1e6;
if ($response->getStatusCode() !== 200) {
throw new RuntimeException('status '.$response->getStatusCode());
}
}
sort($waktu);
return [
'n' => $n,
'min' => $waktu[0],
'p50' => $waktu[intdiv($n, 2)],
'p95' => $waktu[(int) floor($n * 0.95)],
'max' => end($waktu),
'rata' => array_sum($waktu) / $n,
];
}Setelah N request, waktu diurutkan lalu dibaca pada posisi persentil: p50 adalah mediannya — setengah request lebih cepat dari angka ini — dan p95 adalah batas 5% request terlambat. Kita jalankan dua putaran untuk melihat stabilitasnya pada endpoint daftar post:
n=100 min= 2.9 p50= 3.0 p95= 3.2 max= 3.5 rata-rata= 3.0 ms
n=100 min= 2.9 p50= 3.0 p95= 3.2 max= 3.3 rata-rata= 3.0 msDua putaran menghasilkan p50 yang identik (3,0 ms) dan p95 yang hampir sama — distribusi yang sehat. Mengapa p95 lebih berarti daripada rata-rata? Karena rata-rata membiarkan satu request 1 detik tersamar di antara 99 request 3 ms (“rata-rata 12,7 ms, aman!”), sedangkan p95 menghadapi kenyataan: 5 dari 100 pengunjung mengalami yang terburuk.
Disiplin A/B: Sebelum dan Sesudah, Adil #
Klaim “optimasi ini mempercepat 5x” hanya sah bila kedua sisi diukur pada kondisi identik: data yang sama, environment yang sama, state cache yang sama-sama hangat, dan jumlah run yang cukup. Berikut A/B nyata pada data yang sama — endpoint identik, satu-satunya perbedaan adalah with() versus lazy loading:
run 1: cepat p50= 3.6 p95= 4.0 | lambat p50= 14.9 p95= 18.3 ms
run 2: cepat p50= 3.3 p95= 3.6 | lambat p50= 15.0 p95= 16.0 msDua putaran memberi bukti berlapis: lazy loading konsisten 4–5 kali lebih lambat pada p50 maupun p95. Jika kedua run menghasilkan angka yang bertengkar (misal 15 ms lalu 40 ms), itu sinyal interferensi — proses lain, cache dingin, atau data berbeda — dan pengukuran harus diulang sebelum ditafsirkan.
Threshold di CI dan Anggaran Regresi #
Strategi anti-flaky untuk pipeline CI berlapis tiga. Pertama, threshold longgar (seperti 0,5 detik tadi) yang hanya menangkap regresi besar. Kedua, pasangkan dengan asersi deterministik — jumlah query, jumlah baris, penggunaan index — yang tidak bergantung keberuntungan mesin. Ketiga, ketika threshold ketat memang dibutuhkan, bandingkan terhadap baseline relatif: ukur kasus pembanding di mesin yang sama pada run yang sama, lalu asersikan rasionya (lambat / cepat < 3), bukan nilai absolutnya — rasio jauh lebih stabil lintas mesin. Anggaran regresi yang sehat menyatakan tujuan bisnisnya secara eksplisit: “p95 endpoint checkout tidak boleh melewati 200 ms” adalah anggaran yang bisa dipertanggungjawabkan; “secepat mungkin” bukan anggaran.
Alat Beban Eksternal #
Benchmark di atas berjalan di dalam satu proses PHP — sempurna untuk iterasi cepat, tetapi tidak menguji aspek yang hanya muncul di bawah beban nyata: konkurensi, antrian PHP-FPM, kehabisan koneksi database. Untuk itu ada alat khusus:
# contoh: k6 — load test 50 virtual user selama 30 detik
k6 run --vus 50 --duration 30s skrip.js
# contoh: Apache JMeter — rencana test GUI/XML dengan thread group dan listener
jmeter -n -t rencana.jmx -l hasil.jtlKeduanya meniru banyak pengguna paralel dan melaporkan p95 di tingkat sistem — angka yang menentukan kapasitas server, bukan hanya kecepatan satu request. Aturan praktisnya: benchmark internal untuk pengembangan sehari-hari, alat beban sebelum event besar atau kapasitas bertambah.
Dengan ini rantai disiplin performa tertutup penuh: ukur dulu, perbaiki yang terbukti mahal, lalu kunci kecepatannya lewat test. Seluruh peralatan ini — dari DB::listen hingga p95 — akan menemani kita di lingkungan yang paling menuntut: produksi, arena section berikutnya.