Belajar Symfony - Scaling & High Availability
Episode 24 of 27

Belajar Symfony - Scaling & High Availability

Menskalakan aplikasi Symfony: prinsip stateless, memindahkan session & cache ke Redis bersama, database read replicas dengan Doctrine, horizontal scaling di balik load balancer, serta load testing dengan k6 untuk menemukan bottleneck dan tuning.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Deploy berhasil di episode 23 — lalu traffic naik, dan server mulai mengeluh. Episode 24 adalah jawaban produksi untuk pertanyaan itu: scaling & high availability. Bagaimana satu instance menjadi banyak instance tanpa rusak.

Mengapa ini topik inti Symfony? Karena keunggulan arsitektur Symfony yang kita pelajari — stateless services, container, cache terpisah — justru dirancang untuk dijalankan banyak instance. Scaling bukan menambah server secara membabi buta; ia tentang memastikan setiap instance setara dan bisa dihapus kapan saja tanpa kehilangan state.

Prinsip Pertama: Stateless App

Hukum scaling horizontal: instance tidak boleh menyimpan state yang tidak bisa dibagi. Jika dua instance aplikasi berjalan, dan user dialihkan ke instance lain — sesi, cache, dan file upload harus tetap tersedia.

100%

Yang harus dibagi antar instance:

StateTempat yang benar
SessionRedis (shared)
Cache (app)Redis
Rate limiter counterRedis (episode 20)
UploadObject storage (S3) / shared volume
Messenger queueRedis/AMQP (episode 10)

Yang boleh lokal per instance: opcache, twig cache, log file (tapi lebih baik log terpusat — episode 15).

Session di Redis

Default session tersimpan di file — per instance, sehingga pindah instance = kehilangan login. Pindahkan ke Redis:

config/packages/framework.yaml
framework:
    session:
        handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler
        cookie_secure: auto
        cookie_httponly: true
        cookie_samesite: lax
        gc_maxlifetime: 7200
 
    cache:
        app: cache.adapter.redis
        system: cache.adapter.system

cookie_secure: auto → hanya kirim cookie via HTTPS di production; cookie_httponly: true & cookie_samesite: lax adalah default yang aman (mencegah akses JS dan mitigasi CSRF tambahan).

Database Read Replicas

Beban baca (SELECT) biasanya jauh melebihi tulis. PostgreSQL/MySQL mendukung read replicas; Doctrine bisa memisahkan baca ke replica:

DATABASE_URL dengan replica
DATABASE_URL=postgresql://app:pass@primary:5432/app
DATABASE_REPLICA_URL=postgresql://app:pass@replica:5432/app
config/packages/doctrine.yaml
doctrine:
    dbal:
        connections:
            default:
                url: '%env(DATABASE_URL)%'
                replicas:
                    replica1:
                        url: '%env(DATABASE_REPLICA_URL)%'

Doctrine otomatis mengarahkan SELECT ke replica dan INSERT/UPDATE/DELETE ke primary. Penting: data yang baru ditulis mungkin belum ada di replica (replication lag) — untuk kasus seperti "halaman setelah submit", paksa primary:

Paksa baca dari primary
$this->getEntityManager()
    ->getConnection()
    ->executeStatement('SET TRANSACTION READ WRITE');
 
// atau pada kasus tertentu, gunakan TransactionalEntityManager:
$this->em->find(Article::class, $id, lockMode: null, options: ['replica' => false]);

Aturan praktis: gunakan primary untuk alur yang langsung menyajikan data yang baru ditulis (misal redirect setelah POST), replica untuk agregasi/laporan yang toleran keterlambatan.

Horizontal Scaling di Balik Load Balancer

Dengan stateless yang rapi, scaling adalah operasi rutin:

  1. Build image (episode 23) — ID image sama untuk semua instance.
  2. Tambah instance di belakang load balancer (nginx/HAProxy/ALB).
  3. Health check menentukan instance sehat:
Health check nginx
upstream symfony_app {
    least_conn;
    server app1:8080;
    server app2:8080;
    server app3:8080;
}
 
server {
    location / {
        proxy_pass http://symfony_app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
 
    location /health {
        proxy_pass http://symfony_app/health;
    }
}

X-Forwarded-* penting: Symfony perlu tahu IP asli & protokol asli user (untuk logging & forced HTTPS). Header X-Forwarded-Proto juga berpengaruh ke cookie_secure di atas.

Load Testing: Temukan Bottleneck

Jangan menebak — ukur. k6 adalah tool load testing yang praktis:

JSscript/load-test.js
import http from 'k6/http';
import { check } from 'k6';
 
export const options = {
    stages: [
        { duration: '30s', target: 50 },
        { duration: '1m', target: 200 },
        { duration: '30s', target: 0 },
    ],
};
 
export default function () {
    const res = http.get('http://localhost:8080/articles');
    check(res, {
        'status 200': (r) => r.status === 200,
        'p95 < 300ms': (r) => r.timings.duration < 300,
    });
}
Jalankan load test
k6 run script/load-test.js

Skenario bertahap (naik → peak → turun) mengungkap:

GejalaKemungkinan bottleneck
p95 meledak saat concurrency naikDatabase (query) atau opcache cold
Latency tinggi tapi CPU rendahWait pada I/O (DB, Redis, HTTP keluar)
CPU 100% per instanceLogika PHP berat / worker mode belum aktif
Timeout saat bebanPool DB / koneksi habis

Profil setiap temuan dengan profiler (episode 15): query N+1 (episode 21), cache yang belum dipasang (episode 14), atau model eksekusi (episode 22).

Autoscaling

Saat pola sudah stabil, otomasikan penambahan instance berdasarkan metrik:

Metrik yang dipantau untuk autoscale
CPU usage per instance
Error rate (5xx)
p95 latency
Antrian Messenger (episode 10)

Aturan autoscale yang sehat: gunakan metrik antrian & latensi, bukan hanya CPU — aplikasi bisa CPU rendah tapi antrian menumpuk (menunggu DB). Kombinasikan: scale berdasarkan p95 latency & antrian; boundary atas error rate.

Tip

Scaling vertikal (menambah RAM/CPU satu server) selalu lebih mahal dan ada batasnya; scaling horizontal (menambah instance) adalah tujuan arsitektur. Tapi jangan scaling horizontal sebelum memastikan bottleneck bukan database — menambah 10 aplikasi di depan 1 database yang lambat hanya memindahkan antrian.

Common Pitfalls

  • Session file tertinggal di produksi — pindah ke Redis sebelum multi-instance.
  • Cache app per-instance — data basi tak konsisten antar instance; gunakan Redis shared.
  • Replication lag — baca primary untuk data yang baru ditulis.
  • Mengabaikan X-Forwarded-* — IP & HTTPS salah terdeteksi di balik proxy.

Penutup

Pada episode 24 ini, kalian telah menskalakan aplikasi dengan benar.

Inti yang harus dibawa pulang:

  • Stateless adalah syarat mutlak scaling horizontal.
  • Session, cache, rate limiter, queue → Redis/AMQP shared lintas instance.
  • Read replicas di Doctrine memisahkan baca/tulis; hati-hati replication lag.
  • Load balancer + health check + X-Forwarded-* yang benar.
  • Load test (k6) untuk menemukan bottleneck; tuning dengan profiler.

Di episode 25 selanjutnya kita melihat peta besar: Ekosistem & Tren Modern 2026 — Symfony 8.1 & 7.4 LTS, API Platform, UX packages, integrasi FrankenPHP/RoadRunner, dan fitur PHP 8.4/8.5. Sampai jumpa di episode 25!

Belajar Symfony - Scaling & High Availability | Belajar Symfony