Membawa aplikasi Yii dari satu server ke skala produksi: aplikasi stateless dengan Redis untuk cache dan session, read replicas untuk query berat, horizontal scaling dengan load balancer, serta pengukuran performa dengan load test dan tuning PHP-FPM dan OPcache.

Aplikasi kalian lulus: fitur lengkap, aman, terdeploy otomatis. Lalu traffic naik — 100, 1000, 10.000 request per detik. Di sinilah batas arsitektur diuji. Satu server tidak akan bertahan selamanya; pertanyaannya bukan apakah skalanya bertambah, tapi apakah aplikasi kalian bisa bertambah skala tanpa ditulis ulang.
Kabar baiknya, semua fondasi yang kita bangun di episode-episode sebelumnya — stateless (episode 21), Redis, cache bertingkat (episode 11), worker (episode 12) — adalah bahan-bahan scaling. Di episode 24 ini kita merakitnya menjadi arsitektur yang bisa berkembang: read replicas, load balancer, dan pengukuran yang jujur dengan load test.
Scaling horizontal (menambah instance) hanya bekerja jika aplikasi stateless: instance mana pun bisa melayani request mana pun tanpa kehilangan konteks. Dua syaratnya sudah kita bangun:
Yang belum boleh ada di aplikasi stateless: file upload lokal tanpa shared storage, cron yang hanya jalan di satu instance (bisa ganda atau ganda-ganda saat instance banyak), dan session filesystem.
Database sering menjadi bottleneck pertama. Solusi klasik: pisahkan baca dan tulis — tulis ke primary, baca dari replica. Yii mendukung ini lewat slaveConfig:
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=primary.example.com;dbname=blog',
'username' => 'app_write',
'password' => '***',
'slaveConfig' => [
'class' => 'yii\db\Connection',
'username' => 'app_read',
'password' => '***',
],
'slaves' => [
['dsn' => 'mysql:host=replica-1.example.com;dbname=blog'],
['dsn' => 'mysql:host=replica-2.example.com;dbname=blog'],
],
],Satu aturan yang wajib dipahami: Yii mengirimkan query baca ke slave dan query tulis ke master secara otomatis. Ini bekerja lewat deteksi statement — SELECT/SHOW ke slave, INSERT/UPDATE/DELETE ke master. Namun ada kasus yang perlu kalian tangani manual:
// menulis lalu langsung membaca di request yang sama
$order->save();
// replikasi butuh waktu (replication lag) — paksa baca dari master
$id = $order->id;
$fresh = Order::find()
->onCondition(['id' => $id])
->useMaster() // baca langsung dari primary
->one();useMaster() berguna untuk skenario yang peka replication lag: setelah menulis, langsung membaca datanya. Untuk laporan dan agregasi yang toleran lag, biarkan Yii memilih slave.
Saat satu instance tidak cukup, tambahkan instance di belakang load balancer. Ini langkah yang hanya layak setelah fondasi stateless + Redis selesai:
services:
app:
build: .
deploy:
replicas: 3
nginx:
image: nginx:alpine
volumes:
- ./docker/nginx/conf.d:/etc/nginx/conf.d:ro
ports:
- "80:80"Nginx upstream melakukan load balancing sederhana:
upstream yii_app {
server app:9000;
server app:9000;
server app:9000;
}
server {
location ~ \.php$ {
fastcgi_pass yii_app;
include fastcgi_params;
}
}Catatan penting saat horizontal scaling: cron dan queue worker jangan digandakan tanpa sadar. Job terjadwal harus dijalankan dari satu instance khusus (atau pakai lock/mutex), dan queue worker diatur eksplisit di instance worker sendiri — bukan otomatis di semua instance app.
Scaling tanpa pengukuran adalah menebak. Sebelum menambah instance, ukur dulu — bisa jadi masalahnya satu query yang bisa diperbaiki dengan cache (episode 11), bukan arsitektur.
ab -n 10000 -c 100 http://localhost:8080/import http from 'k6/http';
export const options = {
stages: [
{ duration: '1m', target: 50 }, // ramping ke 50 VU
{ duration: '2m', target: 100 }, // beban puncak
{ duration: '1m', target: 0 }, // turun
],
};
export default function () {
http.get('http://localhost:8080/post/index');
}Metrik yang dibaca dari hasil load test:
| Metrik | Arti | Ambang Wajar |
|---|---|---|
| Request per second | Throughput | Naik dengan scaling |
| Latency p50/p95/p99 | Kecepatan persentil | p99 di bawah 300ms |
| Error rate | Request gagal | Mendekati 0 |
| CPU/memori server | Resource consumption | Tidak jenuh di puncak |
Warning
Jangan pernah load test langsung ke production database. Gunakan dataset tiruan dengan skala dan pola yang mirip (jumlah baris, distribusi data), atau lakukan di lingkungan staging yang terisolasi. Load test yang serampangan bisa merusak data production dan memberi hasil yang menyesatkan sekaligus.
Sebelum menambah instance, pastikan setiap instance di-tuning optimal. Dua target utama: OPcache (bytecode ter-cache, bukan ter-kompilasi ulang) dan PHP-FPM pool (jumlah worker yang pas).
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0validate_timestamps=0 menonaktifkan pengecekan timestamp file — PHP tidak pernah memeriksa ulang apakah file berubah. Ini yang membuat kode ter-cache maksimal. Konsekuensinya: setiap deploy harus me-restart PHP-FPM (atau opcache_reset()) agar kode baru aktif.
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 1000max_requests membuat setiap worker menangani maksimal 1000 request lalu restart — mencegah akumulasi memory leak jangka panjang. Mulai dari max_children kecil dan naikkan bertahap sambil memantau memori server; rumus kasar: RAM per worker ≈ memory_limit PHP.
Tanpa monitoring, tuning tidak ada artinya. Rekap metrik yang sudah kita bangun: log aplikasi (episode 13), Redis metrics (episode 21), dan sekarang metrik server:
docker stats # CPU/memori per container
df -h # kapasitas disk
free -m # memoriUntuk skala serius, pasang stack observability (Prometheus + Grafana, atau service terkelola) yang mengumpulkan metrik dari semua instance — kita singgung ini di episode 25 sebagai arah pengembangan berikutnya.
Urutan yang disarankan saat aplikasi mulai melambat — jangan loncat langsung ke instance baru:
getOrSet untuk query panas, fragment caching untuk view mahal.Setiap langkah adalah keputusan yang diukur, bukan perasaan. Arsitektur yang baik adalah yang bisa menjelaskan mengapa setiap lapisan ada.
Inti yang harus dibawa pulang:
slaveConfig) memisahkan beban baca-tulis; useMaster() untuk konsistensi.ab/k6, pantau p50/p95/p99 dan error rate.validate_timestamps=0) dan pool FPM (max_requests).Di episode 25 selanjutnya, kita menengok lanskap keseluruhan: ekosistem & tren modern 2026 — posisi Yii3 (rilis 31 Des 2025) di tengah Yii 2.0.55 dan 1.1.32, kompatibilitas dengan RoadRunner dan FrankenPHP, arah migrasi Yii2 ke Yii3, serta tren PHP 8.4/8.5 di industri. Sampai jumpa di episode 25!