Mengukur dan mengoptimalkan performa Axum: zero-copy, allocation tuning, biaya sebenarnya dari middleware stack, benchmark unit dengan criterion, dan load testing untuk keputusan berdasarkan data.

Aplikasi kalian bekerja — tetapi seberapa cepat? Episode ini membahas performance: mengukur dengan data (benchmark), menemukan bottleneck, dan mengoptimasi dengan teknik khas Rust/Axum: zero-copy dan allocation tuning. Prinsip utamanya: ukur dulu, baru optimasi — optimasi tanpa data adalah tebakan yang sering salah sasaran.
Mengapa performa perlu episode sendiri? Karena Axum sudah cepat secara bawaan (berdiri di atas hyper), sehingga bottleneck hampir selalu di pilihan kode kalian — string concatenation yang banyak, clone tak perlu, body yang di-buffer penuh, atau middleware yang menambah kerja. Mengenali pola-pola ini membuat aplikasi naik kelas tanpa arsitektur yang lebih rumit.
Sebelum mengoptimasi, petakan di mana request menghabiskan waktu:
Dua hal sering jadi titik berat: database query (bisa ratusan ms) dan serialization JSON. Optimasi micro di handler kadang tidak terlihat dibanding query yang lambat. Selalu ukur.
Zero-copy berarti data diproses tanpa disalin berulang. Contoh nyata di Axum:
// JELEK: klon berlebihan per request
async fn bad_handler(name: String) -> String {
let greeting = format!("Halo, {}!", name.clone()); // clone tak perlu
let log_line = format!("handler dipanggil untuk {}", name); // clone lagi
println!("{log_line}");
greeting
}
// BAIK: pakai &str, tanpa clone
async fn good_handler(name: &str) -> String {
format!("Halo, {name}!")
}Hakikatnya: &str meminjam, String memiliki. Semakin sedikit alokasi heap baru per request, semakin murah handler-nya. format! adalah alokasi baru — gunakan bila perlu, bukan sebagai refleks.
Note
Zero-copy bukan dogma — ini trade-off. Kadang String (ownership) lebih masuk akal daripada &str yang rumit dengan lifetimes. Aturan praktis: hindari clone yang tak perlu dan alokasi ganda; jangan menjadikan "tidak pernah alokasi" sebagai tujuan mutlak.
Alokasi heap adalah biaya; mengurangi jumlahnya membantu — terutama di hot path. Teknik umum:
// JELEK: tumbuh berulang (alokasi ganda)
let mut ids = Vec::new();
for id in 0..1000 {
ids.push(id);
}
// BAIK: alokasi sekali dengan kapasitas
let mut ids = Vec::with_capacity(1000);
for id in 0..1000 {
ids.push(id);
}Vec::with_capacity memesan ruang sekali, menghindari re-allocation saat push melewati kapasitas. Untuk vector yang ukurannya bisa ditebak (jumlah baris hasil query, dll.), pre-allocate selalu menguntungkan.
// BAIK: pola string disusun di startup, bukan per request
const WELCOME: &str = "Selamat datang";Konstanta &'static str tidak mengalokasi di runtime — untuk pesan statis, ini lebih murah daripada format! yang dibangun ulang per request.
Middleware menambah kerja per request — bongkar-pasang header, parse, buffer. tower-http murah, tetapi tidak gratis:
Router::new()
.route("/", axum::routing::get(|| async { "ok" }))
.layer(TraceLayer::new_for_http()) // ~mikrodetik, wajib
.layer(CompressionLayer::new()) // mahal untuk body besar, hemat bandwidth
.layer(TimeoutLayer::new(...)) // hampir gratis
.layer(CorsLayer::new()) // header extraAturan praktis:
TraceLayer dan TimeoutLayer — biaya kecil; pasang selalu.CompressionLayer — biaya CPU untuk body besar. Untuk JSON API kecil, manfaat bisa kurang dari biayanya.from_fn menambah lapisan; ukur dampaknya jika dipasang global.Kuncinya: ukur aplikasi dengan dan tanpa lapisan untuk melihat biaya nyata, bukan menebak.
Criterion adalah standar benchmark Rust. Install sebagai dev-dependency:
cargo add --dev criterionTambahkan ke Cargo.toml:
[[bench]]
name = "serialize"
harness = falseFile benchmark:
use axum::Json;
use criterion::{criterion_group, criterion_main, Criterion};
use serde::Serialize;
#[derive(Serialize)]
struct Post {
id: uuid::Uuid,
title: String,
body: String,
}
fn bench_json_serialize(c: &mut Criterion) {
let post = Post {
id: uuid::Uuid::new_v4(),
title: "Judul".into(),
body: "Isi".into(),
};
c.bench_function("serialize_post_json", |b| {
b.iter(|| Json(&post))
});
}
criterion_group!(benches, bench_json_serialize);
criterion_main!(benches);Jalankan:
cargo benchCriterion memberi interval kepercayaan dan — setelah run kedua — membandingkan perubahan performa antar commit. Gunakan ini untuk memverifikasi: "apakah optimasi ini benar-benar mempercepat?"
Warning
Jangan pernah benchmark di laptop dengan aplikasi lain berjalan atau dalam mode power-saver — hasilnya menyesatkan. Benchmark untuk perbandingan relatif (commit A vs B), bukan angka absolut. Angka absolut hanya bermakna di lingkungan produksi yang sama.
Criterion mengukur unit; load test mengukur aplikasi penuh dengan lalu lintas nyata. oha, bombardier, atau wrk adalah tool populer:
# 1000 request, 50 konkuren
oha -n 1000 -c 50 http://localhost:3000/postsOutput yang perlu diperhatikan:
| Metrik | Arti |
|---|---|
| Requests/sec | Throughput |
| Latency P50/P95/P99 | Distribusi waktu respons |
| Error rate | % request gagal |
| Timeout | Request melewati batas |
Aturan load test:
Setelah data masuk, optimasi berdasarkan bukti:
// BAIK: tidak membawa body (bisa besar) untuk list
sqlx::query_as::<_, PostSummary>(
"SELECT id, title FROM posts ORDER BY created_at DESC LIMIT 20",
)Bawalah hanya kolom yang dipakai list view — SELECT * membawa data yang tidak dipakai dan memperbesar payload.
Limit + offset dari episode 4/9 — aplikasi dengan 1 juta post tidak boleh mengembalikan semuanya.
max_connections dari episode 9: terlalu kecil = antrian; terlalu besar = boros. Mulai max_connections = core * 2 + 1 (aturan umum untuk PostgreSQL) lalu tuning dengan load test.
// BAIK: struct sudah Serialize, tidak perlu serde_json::json! manual
Ok(Json(post))serde_json::json!({ "post": {...} }) membangun Value perantara lalu serialize lagi. Untuk hot path, serialize struct langsung.
Urutan yang benar:
# 1. Baseline
cargo bench
oha -n 5000 -c 50 http://localhost:3000/posts > baseline.txt
# 2. Identifikasi bottleneck (log latency dari episode 17)
# 3. Terapkan satu perubahan
# 4. Ulangi benchmark, bandingkan dengan baseline
# 5. Pertahankan jika terbukti lebih baik; revert jika tidakSatu perubahan per iterasi — jika menerapkan tiga optimasi sekaligus, tidak ada yang tahu mana yang bekerja. Prinsip ini disebut one change at a time dan merupakan disiplin utama performance engineering.
Tip
Performance yang benar sering terlihat membosankan: query yang efisien, pagination, pool yang pas, dan menghindari alokasi tak perlu. Bukan "trik ajaib". Jika aplikasi kalian diukur dan temuan utamanya ada di database, optimasi handler 10 microsecond tidak akan menyelamatkan query 300ms.
Pada episode 25 ini kalian bisa mengukur dan mengoptimasi:
&str vs String, with_capacity, hindari format berulang.oha) untuk end-to-end.Di episode 26 selanjutnya kita melihat cakrawala: ekosistem & tren modern 2026 — perkembangan Axum 0.8.9, axum-extra 0.12.x, perbandingan dengan Actix/poem/rocket, dan arah Rust di edge computing. Sampai jumpa di episode 26!