Pelajari cara implement Laravel Octane dengan Swoole dan RoadRunner untuk meningkatkan performa aplikasi hingga 10x lipat. Artikel ini membahas studi kasus nyata dari pengalaman saya sendiri mengelola BuildWithAngga — platform online courses untuk developers Indonesia. Dari response time 3 detik yang memalukan sampai 200ms yang membanggakan.
Lengkap dengan step-by-step setup, code examples, gotchas yang harus dihindari, dan benchmark results yang bisa kamu expect. Kalau kamu pernah struggle dengan Laravel yang lambat meski sudah optimize database dan caching, artikel ini untuk kamu.
Bagian 1: The Embarrassing Truth — Website Sendiri yang Lambat
Saya founder platform online courses. Setiap hari, saya mengajar ribuan developers Indonesia cara bikin website yang bagus, cepat, dan professional.
Tapi website saya sendiri?
Lambat. Embarrassingly lambat.
Ini bukan cerita tentang client atau project orang lain. Ini cerita tentang platform saya sendiri — BuildWithAngga. Platform yang seharusnya menjadi showcase kemampuan saya. Platform yang seharusnya membuktikan bahwa saya tau apa yang saya ajarkan.
Response time homepage: 3.2 detik. Course detail page: 4 detik lebih. Video player initialization: hampir 6 detik. Peak hour saat ada promo? Timeout.
Ironi yang menyakitkan.
The Wake-Up Call
Semuanya dimulai dari DM yang masuk bertubi-tubi.
"Kak Angga, websitenya kok lambat ya? Mau beli course tapi loading terus."
Awalnya saya pikir itu cuma satu dua orang dengan koneksi jelek. Tapi kemudian:
"Mas, checkout timeout terus. Udah coba 3x gagal. Akhirnya beli di platform lain."
Itu sakit. Bukan cuma kehilangan satu sale — tapi kehilangan trust.
Lalu ada review yang masuk:
"Kontennya bagus, sangat membantu belajar Laravel. Tapi websitenya lemot banget, kadang males bukanya."
Bintang 4. Padahal harusnya bisa bintang 5 kalau bukan karena performa.
Saya buka Google Analytics. Data tidak berbohong:
- Bounce rate naik 40% dalam 2 bulan terakhir
- Average session duration turun dari 8 menit ke 3 menit
- Conversion rate drop dari 3.2% ke 1.8%
- Page views per session turun drastis
The Numbers That Hurt
Saya jalankan profiling dan benchmarking untuk dapat gambaran jelas:
| Metric | BuildWithAngga (Before) | Industry Standard | Status |
|---|---|---|---|
| Homepage load | 3.2s | < 2s | ❌ Bad |
| Course listing | 3.8s | < 2s | ❌ Bad |
| Course detail | 4.1s | < 2s | ❌ Bad |
| Video player init | 5.8s | < 3s | ❌ Bad |
| Checkout page | 3.5s | < 1.5s | ❌ Bad |
| API responses (avg) | 800ms - 2s | < 200ms | ❌ Bad |
| Requests/sec (max) | ~100 | 500+ | ❌ Bad |
| Server CPU at peak | 95%+ | < 70% | ❌ Bad |
Setiap metric merah. Semuanya di bawah standard.
Dan ini bukan server kentang. Ini VPS dengan 8 core CPU, 16GB RAM, SSD NVMe. Bukan masalah hardware.
What I Had Already Tried
Yang bikin frustrating, saya sudah melakukan "semua" optimization yang biasa direkomendasikan:
OPTIMIZATIONS YANG SUDAH DILAKUKAN:
✓ Redis untuk cache dan session
✓ Database queries optimized (no N+1)
✓ Eager loading implemented
✓ Route caching (php artisan route:cache)
✓ Config caching (php artisan config:cache)
✓ View caching (php artisan view:cache)
✓ OPcache enabled dan configured
✓ CDN untuk static assets
✓ Database indexes proper
✓ Queue untuk heavy jobs
✓ Image optimization
RESULT: Masih lambat. 🤦
Saya sudah optimize di level aplikasi. Sudah optimize di level database. Sudah optimize di level infrastructure.
Tapi tetap lambat.
The Realization
Setelah deep-dive ke profiling, saya akhirnya paham. Masalahnya bukan di code saya. Masalahnya bukan di query saya. Masalahnya di level yang lebih fundamental:
PHP-FPM harus bootstrap Laravel dari NOL untuk setiap single request.
Dengan 60+ service providers, 200+ routes, puluhan middleware, dan ratusan config files... setiap request harus load semuanya. Lagi. Dan lagi. Dan lagi.
Tidak peduli seberapa optimal code saya, kalau fondasi arsitekturnya memang lambat by design.
"Saya bikin courses tentang Laravel performance. Saya ngajarin best practices. Tapi platform saya sendiri lambat. Kalau ada definisi sempurna untuk kata 'ironi', ini contohnya. Dan ini harus di-fix. Sekarang."
Bagian 2: Understanding the Problem — Kenapa PHP-FPM Lambat
Untuk benar-benar fix masalah, saya harus paham dulu kenapa lambat. Bukan cuma symptoms, tapi root cause.
How Traditional PHP-FPM Works
Setiap kali ada request masuk ke aplikasi Laravel yang jalan di PHP-FPM, ini yang terjadi:
REQUEST LIFECYCLE DI PHP-FPM (Setiap Request):
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT REQUEST MASUK │
│ ↓ │
│ Nginx menerima request │
│ ↓ │
│ Nginx forward ke PHP-FPM │
│ ↓ │
│ PHP-FPM spawn atau reuse worker │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ BOOTSTRAP PHASE (terjadi SETIAP request): │ │
│ │ │ │
│ │ 1. Load Composer autoloader (~15ms) │ │
│ │ 2. Load semua config files (50+) (~25ms) │ │
│ │ 3. Create service container (~20ms) │ │
│ │ 4. Register 60+ service providers (~80ms) │ │
│ │ 5. Boot 60+ service providers (~60ms) │ │
│ │ 6. Load dan parse routes (200+) (~40ms) │ │
│ │ 7. Setup middleware stack (~20ms) │ │
│ │ 8. Resolve facades (~30ms) │ │
│ │ 9. Setup error handlers (~10ms) │ │
│ │ │ │
│ │ TOTAL BOOTSTRAP: ~300ms │ │
│ │ (Sebelum satu baris code kamu jalan!) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Execute controller logic (~50-200ms) │
│ ↓ │
│ Render view (~20-50ms) │
│ ↓ │
│ Send response ke client │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ SHUTDOWN PHASE: │ │
│ │ • Garbage collection │ │
│ │ • Close database connections │ │
│ │ • Free all memory │ │
│ │ • Terminate service providers │ │
│ │ • Worker siap untuk request berikutnya │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
REQUEST BERIKUTNYA: ULANGI SEMUANYA DARI AWAL!
Lihat itu? 300ms hanya untuk bootstrap — sebelum satu baris code aplikasi saya dijalankan.
Dan ini terjadi untuk setiap. single. request.
Analogi: Restaurant yang Absurd
Bayangkan kamu punya restaurant. Setiap kali ada customer order:
RESTAURANT VERSI PHP-FPM:
Order #1 masuk: Nasi Goreng
├── Bongkar semua peralatan dapur
├── Bangun ulang kitchen dari nol
├── Setup kompor, wajan, spatula
├── Panaskan semua kompor
├── Ambil semua bahan dari gudang
├── Briefing ulang semua chef tentang menu
├── BARU MULAI MASAK nasi goreng
├── Serve ke customer
├── BONGKAR SEMUA dan bersihkan total
└── Kitchen kembali kosong
Order #2 masuk: Mie Goreng
├── Bongkar semua... (wait, kan tadi sudah dibongkar?)
├── Bangun ulang kitchen LAGI dari nol
├── Setup kompor, wajan, spatula LAGI
├── Panaskan semua kompor LAGI
├── ... (repeat everything)
└── Kitchen kembali kosong
Order #100 masuk:
└── MASIH HARUS REBUILD KITCHEN DARI NOL!
Gila? Ya. Tidak masuk akal? Absolutely.
Tapi itulah exactly yang PHP-FPM lakukan untuk setiap request.
The Bootstrap Tax Calculation
Mari hitung berapa "pajak" yang kita bayar untuk arsitektur ini:
| Component | Load Time | Per Request? | Per 100 req/sec |
|---|---|---|---|
| Composer Autoloader | 15ms | ✅ Yes | 1.5 detik |
| Config Files (50+) | 25ms | ✅ Yes | 2.5 detik |
| Service Providers (60+) | 140ms | ✅ Yes | 14 detik |
| Route Registration | 40ms | ✅ Yes | 4 detik |
| Middleware Setup | 20ms | ✅ Yes | 2 detik |
| Facades & Container | 30ms | ✅ Yes | 3 detik |
| Error Handlers | 10ms | ✅ Yes | 1 detik |
| TOTAL BOOTSTRAP | ~300ms | Every request | 30 detik |
30 detik CPU time per second hanya untuk bootstrapping.
Belum termasuk actual work — query database, process logic, render views.
Dengan kalkulasi ini, di 100 requests per second:
- CPU spend 30 detik untuk bootstrap
- CPU cuma punya "sisa waktu" untuk actual work
- Result: Bottleneck, queueing, timeouts
Why Scaling Doesn't Help
"Tapi kan bisa tambah server?"
Bisa. Tapi itu cuma memindahkan masalah.
SCALING HORIZONTAL:
1 Server (100 req/sec capacity):
└── Bootstrap overhead: 30 CPU-seconds/second
└── Status: Maxed out ❌
2 Servers (200 req/sec capacity):
└── Bootstrap overhead: 60 CPU-seconds/second (total)
└── Status: Handle 2x traffic, tapi juga 2x cost ❌
10 Servers:
└── Bootstrap overhead: 300 CPU-seconds/second
└── Status: Handle 10x traffic, 10x cost
└── Efficiency: SAMA BURUKNYA
Kamu tidak solve the problem.
Kamu cuma throwing money at it.
The Fundamental Question
Pertanyaan yang muncul di kepala saya:
Bagaimana kalau kita bootstrap Laravel SEKALI, simpan di memory, dan serve requests dari yang sudah di-boot?
Tidak rebuild kitchen setiap order. Kitchen sudah siap. Tinggal masak dan serve.
Itu exactly apa yang Laravel Octane lakukan.
"PHP-FPM itu seperti pegawai yang pulang ke rumah setelah selesaikan SATU task, lalu besok pagi harus commute lagi, setup meja lagi, buka laptop lagi, login lagi — untuk setiap task. Octane itu pegawai yang tetap di kantor, meja sudah siap, laptop sudah nyala, tinggal kerjakan task berikutnya. Obviously yang kedua lebih efisien."
Bagian 3: What is Laravel Octane
Setelah paham masalahnya, saatnya paham solusinya.
Octane Explained Simply
Laravel Octane adalah package official dari Laravel yang mengubah fundamental cara Laravel serve requests.
Konsep intinya sederhana:
- PHP-FPM: Boot → Serve → Shutdown → Boot → Serve → Shutdown → ...
- Octane: Boot ONCE → Serve → Serve → Serve → Serve → ... (keep in memory)
Octane mem-boot Laravel satu kali di awal, menyimpan application state di memory, dan kemudian serve ratusan atau ribuan requests tanpa perlu bootstrap ulang.
Visual: Octane Request Lifecycle
OCTANE SERVER STARTUP (Terjadi SEKALI):
┌─────────────────────────────────────────────────────────────────┐
│ php artisan octane:start │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ BOOTSTRAP (sekali saja, saat server start): │ │
│ │ │ │
│ │ ✓ Load Composer autoloader │ │
│ │ ✓ Load semua config files │ │
│ │ ✓ Create service container │ │
│ │ ✓ Register semua service providers │ │
│ │ ✓ Boot semua service providers │ │
│ │ ✓ Load dan cache routes │ │
│ │ ✓ Setup middleware stack │ │
│ │ ✓ Resolve facades │ │
│ │ │ │
│ │ Laravel sekarang READY dan IN MEMORY │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Spawn N workers (usually = CPU cores) │
│ Each worker = 1 Laravel instance in memory │
│ Server listening on port 8000 │
└─────────────────────────────────────────────────────────────────┘
SETIAP REQUEST (sangat cepat):
┌─────────────────────────────────────────────────────────────────┐
│ Request #1 masuk │
│ ↓ │
│ Worker #1 (sudah booted!) menerima request │
│ ↓ │
│ Clone request-specific state (~2ms) │
│ ↓ │
│ Execute controller logic (~50ms) │
│ ↓ │
│ Send response │
│ ↓ │
│ Reset request state (bukan full shutdown) (~2ms) │
│ ↓ │
│ Worker siap untuk request berikutnya │
│ │
│ TOTAL: ~54ms (bandingkan dengan 350ms+ di PHP-FPM!) │
└─────────────────────────────────────────────────────────────────┘
Request #2, #3, #4, ... #500: Same worker, same speed (~54ms each)
Setelah 500 requests: Worker di-restart untuk prevent memory leaks
Request #501: Worker baru (sudah di-pre-boot), tetap cepat
Analogi Updated: Restaurant yang Proper
RESTAURANT VERSI OCTANE:
PAGI HARI (Sebelum buka, sekali saja):
├── Setup kitchen lengkap
├── Panaskan semua kompor
├── Siapkan semua bahan di station
├── Brief semua chef tentang menu
├── Semua peralatan di posisi ready
└── KITCHEN SIAP!
Order #1 masuk: Nasi Goreng
├── Chef langsung ambil bahan (sudah ready di station)
├── Langsung masak (kompor sudah panas)
├── Serve ke customer
└── Station tetap ready untuk order berikutnya
Order #2 masuk: Mie Goreng
├── Chef langsung masak (tidak perlu setup ulang)
├── Serve ke customer
└── Station tetap ready
Order #100 masuk:
├── MASIH LANGSUNG BISA MASAK
└── Tidak perlu rebuild kitchen!
CLOSING TIME:
└── Baru bersihkan dan shutdown
Ini jauh lebih masuk akal, kan?
The Technology Behind Octane
Octane bukan magic. Octane adalah wrapper elegant di atas high-performance application servers yang sudah proven:
| Driver | Type | Maintained By | Best For |
|---|---|---|---|
| Swoole | PHP Extension (C) | Swoole Team | Maximum performance |
| OpenSwoole | PHP Extension (Fork) | OpenSwoole Team | Alternative to Swoole |
| RoadRunner | Go Binary | Spiral Scout | Easy setup |
| FrankenPHP | Go + PHP | Kévin Dunglas | Modern alternative |
Swoole vs RoadRunner — Detailed Comparison
Karena ini dua pilihan paling populer, mari bandingkan detail:
| Feature | Swoole | RoadRunner |
|---|---|---|
| Technology | PHP Extension written in C | Go binary |
| Installation | pecl install swoole (compile) | Download binary / Composer |
| Difficulty | Medium (perlu compile extension) | Easy (just download) |
| Raw Performance | ⭐⭐⭐⭐⭐ Fastest | ⭐⭐⭐⭐ Very Fast |
| Concurrent Tasks | ✅ Native support | ❌ Not available |
| Octane Cache | ✅ 2M operations/sec | ❌ Not available |
| Swoole Tables | ✅ Shared memory structures | ❌ Not available |
| Tick/Interval | ✅ Background task scheduling | ❌ Not available |
| WebSocket | ✅ Built-in support | ⚠️ Needs extension |
| Coroutines | ✅ Full async/await style | ❌ Not available |
| Windows Support | ❌ Linux/macOS only | ✅ Full support |
| Docker Setup | ⚠️ Needs PHP extension | ✅ Very easy |
| Memory Management | ⚠️ Needs attention | ✅ Generally easier |
| Learning Curve | Medium-High | Low-Medium |
Visual Comparison:
PERFORMANCE SPECTRUM:
PHP-FPM RoadRunner Swoole
│ │ │
▼ ▼ ▼
────┼────────────────┼──────────────────────┼────────►
Slow Fast Fastest
100 req/s 700 req/s 1000+ req/s
(baseline) (7x faster) (10x faster)
EASE OF USE SPECTRUM:
Swoole RoadRunner PHP-FPM
│ │ │
▼ ▼ ▼
────┼────────────────┼──────────────────────┼────────►
Complex Moderate Simple
(extension) (binary) (just works)
My Decision: Why Swoole for BuildWithAngga
Untuk BuildWithAngga, saya memilih Swoole karena beberapa alasan:
- Maximum Performance — Dengan ribuan students dan video streaming, saya butuh setiap millisecond.
- Concurrent Tasks — Bisa process video metadata, generate thumbnails, dan update analytics secara parallel.
- Octane Cache — Cache dengan 2 juta operasi per detik untuk real-time features seperti view counts dan progress tracking.
- Background Ticks — Schedule periodic tasks tanpa cron, seperti persist cache ke database setiap 10 detik.
- Server Environment — Semua server production Linux, jadi tidak ada Windows compatibility issue.
Tapi saya akan tunjukkan setup untuk keduanya di artikel ini, karena banyak situasi dimana RoadRunner lebih appropriate — terutama untuk:
- Tim yang baru mulai dengan Octane
- Development di Windows
- Projek yang tidak butuh fitur Swoole-specific
- Easier deployment dan maintenance
What to Expect: Realistic Numbers
Sebelum lanjut ke implementation, let me set realistic expectations:
| Scenario | PHP-FPM | Octane (RoadRunner) | Octane (Swoole) |
|---|---|---|---|
| Simple API | 100 req/s | 600-800 req/s | 800-1200 req/s |
| Dynamic Page | 50 req/s | 300-400 req/s | 400-600 req/s |
| Heavy Page | 20 req/s | 150-200 req/s | 200-300 req/s |
| Response Time (avg) | 300-500ms | 50-100ms | 30-80ms |
Important notes:
- Angka di atas adalah typical improvements, bukan guaranteed
- Actual results depend on your application
- Database queries tetap butuh waktu yang sama
- External API calls tetap butuh waktu yang sama
- Yang di-eliminate adalah bootstrap overhead
"Octane bukan magic yang bikin semua code kamu instant. Octane menghilangkan bootstrap tax yang sebelumnya kamu bayar setiap request. Kalau bootstrap kamu 300ms dan code execution 200ms, total sebelumnya 500ms. Dengan Octane bisa jadi 200ms — improvement signifikan, tapi bukan infinite."
Next: Bagian 4 — Implementation dengan Swoole
Di bagian selanjutnya, saya akan walk through step-by-step setup Octane dengan Swoole untuk BuildWithAngga, termasuk configuration yang saya gunakan di production.
Bagian 4: Implementation — Setup Octane dengan Swoole
Sekarang hands-on. Saya akan walk through exactly bagaimana saya setup Octane dengan Swoole untuk BuildWithAngga.
Prerequisites
Sebelum mulai, pastikan:
# Check PHP version (8.2+ required, 8.3+ recommended)
php -v
# PHP 8.3.x
# Check OS (Swoole requires Linux/macOS)
uname -s
# Linux atau Darwin (macOS)
# Check Laravel version (10+ required)
php artisan --version
# Laravel Framework 12.x
# Pastikan sudah punya Redis (untuk session/cache fallback)
redis-cli ping
# PONG
Step 1: Install Swoole Extension
Swoole adalah PHP extension yang perlu di-compile. Ini yang paling "tricky" part, tapi sebenarnya straightforward:
Ubuntu/Debian:
# Install dependencies untuk compile
sudo apt-get update
sudo apt-get install -y \\
php8.3-dev \\
php8.3-curl \\
php8.3-openssl \\
libcurl4-openssl-dev \\
libssl-dev
# Install Swoole via PECL
sudo pecl install swoole
# Selama instalasi, akan ada pertanyaan:
# enable sockets supports? [no] : yes
# enable openssl support? [no] : yes
# enable http2 support? [no] : yes
# enable curl support? [no] : yes
# enable cares support? [no] : no
# enable brotli support? [no] : no
# Add ke PHP CLI config
echo "extension=swoole.so" | sudo tee /etc/php/8.3/cli/conf.d/99-swoole.ini
# Verify installation
php -m | grep swoole
# Output: swoole
# Check Swoole info
php --ri swoole | head -20
# Swoole version, settings, etc.
macOS (untuk development):
# Via Homebrew + PECL
brew install [email protected]
pecl install swoole
# Atau via Homebrew tap
brew tap openswoole/swoole
brew install openswoole
Docker (recommended untuk consistency):
# Dockerfile
FROM php:8.3-cli
# Install dependencies
RUN apt-get update && apt-get install -y \\
libcurl4-openssl-dev \\
libssl-dev \\
&& pecl install swoole \\
&& docker-php-ext-enable swoole
# Verify
RUN php -m | grep swoole
Step 2: Install Laravel Octane Package
# Di project Laravel kamu
cd /var/www/buildwithangga
# Install Octane
composer require laravel/octane
# Run installer
php artisan octane:install
# Pilih driver: swoole
# Output:
# Which application server you would like to use?
# [0] frankenphp
# [1] roadrunner
# [2] swoole
# > 2
# Ini akan create: config/octane.php
Step 3: Configuration Deep-Dive
File config/octane.php adalah jantung konfigurasi Octane. Ini config yang saya gunakan untuk BuildWithAngga:
<?php
// config/octane.php
use Laravel\\Octane\\Events\\RequestHandled;
use Laravel\\Octane\\Events\\RequestReceived;
use Laravel\\Octane\\Events\\RequestTerminated;
use Laravel\\Octane\\Events\\TaskReceived;
use Laravel\\Octane\\Events\\WorkerStarting;
use Laravel\\Octane\\Events\\WorkerStopping;
use Laravel\\Octane\\Listeners\\DisconnectFromDatabases;
use Laravel\\Octane\\Listeners\\FlushTemporaryContainerInstances;
use Laravel\\Octane\\Listeners\\ReportException;
return [
/*
|--------------------------------------------------------------------------
| Octane Server
|--------------------------------------------------------------------------
|
| Driver yang digunakan. Options: swoole, roadrunner, frankenphp
|
*/
'server' => env('OCTANE_SERVER', 'swoole'),
/*
|--------------------------------------------------------------------------
| Force HTTPS
|--------------------------------------------------------------------------
|
| Set true jika behind reverse proxy dengan SSL termination
|
*/
'https' => env('OCTANE_HTTPS', true),
/*
|--------------------------------------------------------------------------
| Octane Workers
|--------------------------------------------------------------------------
|
| Jumlah workers. null = auto-detect (biasanya = CPU cores)
| Untuk BuildWithAngga (8 core server): 8 workers
|
*/
'workers' => env('OCTANE_WORKERS', null),
/*
|--------------------------------------------------------------------------
| Task Workers
|--------------------------------------------------------------------------
|
| Workers untuk Concurrent Tasks (Swoole only)
| 'auto' = half of main workers
|
*/
'task_workers' => env('OCTANE_TASK_WORKERS', 'auto'),
/*
|--------------------------------------------------------------------------
| Max Requests
|--------------------------------------------------------------------------
|
| Setelah N requests, worker di-restart untuk prevent memory leaks
| 500 adalah sweet spot untuk most applications
|
*/
'max_requests' => env('OCTANE_MAX_REQUESTS', 500),
/*
|--------------------------------------------------------------------------
| Services to Warm
|--------------------------------------------------------------------------
|
| Services yang di-instantiate saat worker boot
| Berguna untuk services yang heavy to construct
|
*/
'warm' => [
// Core services yang sering dipakai
\\Illuminate\\Contracts\\Http\\Kernel::class,
\\Illuminate\\Contracts\\Console\\Kernel::class,
\\Illuminate\\Contracts\\Debug\\ExceptionHandler::class,
// Application services (BuildWithAngga specific)
\\App\\Services\\CourseService::class,
\\App\\Services\\VideoService::class,
\\App\\Services\\PaymentService::class,
\\App\\Services\\AnalyticsService::class,
// Repositories
\\App\\Repositories\\CourseRepository::class,
\\App\\Repositories\\UserRepository::class,
],
/*
|--------------------------------------------------------------------------
| Flush Bindings
|--------------------------------------------------------------------------
|
| Bindings yang harus di-flush setiap request
| Penting untuk avoid state leakage
|
*/
'flush' => [
// Auth dan session harus fresh setiap request
'auth',
'auth.driver',
'session',
'session.store',
'translator',
'view.finder',
],
/*
|--------------------------------------------------------------------------
| Garbage Collection
|--------------------------------------------------------------------------
|
| Jalankan garbage collection setiap N requests
| null = PHP handles it automatically
|
*/
'garbage' => 50,
/*
|--------------------------------------------------------------------------
| Listeners
|--------------------------------------------------------------------------
|
| Event listeners untuk berbagai Octane lifecycle events
|
*/
'listeners' => [
WorkerStarting::class => [
// Saat worker mulai
// Good for: initialize connections, warm caches
],
RequestReceived::class => [
// Setiap request masuk, sebelum di-handle
],
RequestHandled::class => [
// Setelah request di-handle, sebelum response dikirim
],
RequestTerminated::class => [
// Setelah response dikirim ke client
FlushTemporaryContainerInstances::class,
DisconnectFromDatabases::class,
// Custom: Reset application state
\\App\\Listeners\\ResetApplicationState::class,
],
TaskReceived::class => [
ReportException::class,
],
WorkerStopping::class => [
// Saat worker mau stop
// Good for: cleanup, flush buffers
],
],
/*
|--------------------------------------------------------------------------
| Swoole Tables
|--------------------------------------------------------------------------
|
| Shared memory tables (Swoole only)
| Bisa diakses dari semua workers
|
*/
'tables' => [
// Real-time view counter
'course_views' => [
'columns' => [
['name' => 'views', 'type' => 'int'],
['name' => 'updated_at', 'type' => 'int'],
],
'rows' => 10000, // Max 10k courses tracked
],
// Active users tracking
'active_users' => [
'columns' => [
['name' => 'user_id', 'type' => 'int'],
['name' => 'last_seen', 'type' => 'int'],
],
'rows' => 50000, // Max 50k concurrent users
],
],
/*
|--------------------------------------------------------------------------
| Ticks
|--------------------------------------------------------------------------
|
| Enable tick functionality untuk periodic background tasks
|
*/
'tick' => true,
/*
|--------------------------------------------------------------------------
| Swoole Options
|--------------------------------------------------------------------------
|
| Low-level Swoole configuration
|
*/
'swoole' => [
'options' => [
// Logging
'log_file' => storage_path('logs/swoole_http.log'),
'log_level' => env('APP_DEBUG') ? SWOOLE_LOG_DEBUG : SWOOLE_LOG_WARNING,
// Package size (untuk file uploads)
'package_max_length' => 20 * 1024 * 1024, // 20MB
// Buffer sizes
'buffer_output_size' => 2 * 1024 * 1024, // 2MB
'socket_buffer_size' => 128 * 1024 * 1024, // 128MB
// Timeouts
'max_wait_time' => 60,
// Document root untuk static files (optional)
'document_root' => public_path(),
'enable_static_handler' => false, // Nginx handles static files
],
],
];
Step 4: Environment Configuration
# .env
# Octane Configuration
OCTANE_SERVER=swoole
OCTANE_WORKERS=8
OCTANE_TASK_WORKERS=4
OCTANE_MAX_REQUESTS=500
OCTANE_HTTPS=true
# Session harus pakai Redis (bukan file/database)
SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
# Cache juga Redis
CACHE_STORE=redis
# Queue Redis
QUEUE_CONNECTION=redis
# Redis Configuration
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null
REDIS_DB=0
REDIS_CACHE_DB=1
REDIS_SESSION_DB=2
REDIS_QUEUE_DB=3
Step 5: Create Reset State Listener
Ini penting untuk avoid state leakage:
<?php
// app/Listeners/ResetApplicationState.php
namespace App\\Listeners;
use Laravel\\Octane\\Events\\RequestTerminated;
use Illuminate\\Support\\Facades\\Auth;
class ResetApplicationState
{
public function handle(RequestTerminated $event): void
{
// Reset authentication state
Auth::forgetGuards();
// Reset any static properties in your services
// Example:
// \\App\\Services\\SomeService::reset();
// Clear any request-scoped caches
// app('request.cache')->flush();
}
}
Register di config/octane.php listeners (sudah include di config di atas).
Step 6: Running Octane
# Development mode (dengan hot reload)
php artisan octane:start --watch
# Production mode
php artisan octane:start \\
--host=127.0.0.1 \\
--port=8000 \\
--workers=8 \\
--task-workers=4 \\
--max-requests=500
# Check status
php artisan octane:status
# Reload workers (tanpa downtime)
php artisan octane:reload
# Stop server
php artisan octane:stop
Step 7: Nginx Configuration
Nginx sebagai reverse proxy di depan Octane:
# /etc/nginx/sites-available/buildwithangga
upstream octane {
server 127.0.0.1:8000;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
server_name buildwithangga.com www.buildwithangga.com;
# Redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name buildwithangga.com www.buildwithangga.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/buildwithangga.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/buildwithangga.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
root /var/www/buildwithangga/public;
index index.php;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml
application/javascript application/json;
# Static files served directly by Nginx (bypass Octane)
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|mp4|webm)$ {
expires 1y;
access_log off;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Everything else goes to Octane
location / {
proxy_pass <http://octane>;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Connection "";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 300s;
# Buffering
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}
# Test config
sudo nginx -t
# Reload
sudo systemctl reload nginx
Step 8: Supervisor Configuration (Process Manager)
Supervisor memastikan Octane tetap jalan dan auto-restart jika crash:
# /etc/supervisor/conf.d/octane.conf
[program:octane]
process_name=%(program_name)s
command=php /var/www/buildwithangga/artisan octane:start --host=127.0.0.1 --port=8000 --workers=8 --task-workers=4 --max-requests=500
directory=/var/www/buildwithangga
user=www-data
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/log/octane.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
stopwaitsecs=60
stopsignal=SIGTERM
# Apply configuration
sudo supervisorctl reread
sudo supervisorctl update
# Start Octane
sudo supervisorctl start octane
# Check status
sudo supervisorctl status octane
# View logs
sudo tail -f /var/log/octane.log
Step 9: Verify Everything Works
# Check Octane is running
php artisan octane:status
# Output: Octane server is running.
# Test response
curl -I <http://127.0.0.1:8000>
# HTTP/1.1 200 OK
# Check through Nginx
curl -I <https://buildwithangga.com>
# HTTP/2 200
# Quick benchmark
ab -n 1000 -c 50 <https://buildwithangga.com/>
# Should see ~500-1000 req/sec
Bagian 5: Implementation — RoadRunner Alternative
Kalau Swoole terasa terlalu complex (compile extension, etc.), RoadRunner adalah alternatif yang lebih mudah dengan performance yang tetap excellent.
Why Choose RoadRunner
CHOOSE ROADRUNNER WHEN:
✓ Tim belum familiar dengan PHP extensions
✓ Development di Windows
✓ Ingin setup yang lebih simple
✓ Tidak butuh Concurrent Tasks
✓ Tidak butuh Swoole Tables
✓ Docker-first deployment
✓ Lebih prefer Go ecosystem
Installation
# Install Octane (sama dengan Swoole)
composer require laravel/octane
# Run installer, pilih RoadRunner
php artisan octane:install
# Pilih: roadrunner
# Ini akan:
# 1. Download RoadRunner binary ke ./rr
# 2. Create config/octane.php
# 3. Create .rr.yaml
RoadRunner Configuration
# .rr.yaml
version: "3"
server:
command: "php artisan octane:start --server=roadrunner --host=127.0.0.1 --port=8000"
relay: pipes
http:
address: "0.0.0.0:8000"
middleware:
- gzip
- static
static:
dir: "public"
forbid:
- ".php"
- ".htaccess"
pool:
num_workers: 8
max_jobs: 500
supervisor:
watch_tick: 1s
ttl: 0s
idle_ttl: 10s
max_worker_memory: 128
headers:
response:
X-Powered-By: "BuildWithAngga"
logs:
mode: production
level: warn
output: stderr
err_output: stderr
Running RoadRunner
# Development
php artisan octane:start --server=roadrunner --watch
# Production via RoadRunner binary directly
./rr serve
# Atau via artisan
php artisan octane:start --server=roadrunner --workers=8
# Status
php artisan octane:status
Supervisor for RoadRunner
# /etc/supervisor/conf.d/octane-roadrunner.conf
[program:octane]
process_name=%(program_name)s
command=/var/www/buildwithangga/rr serve
directory=/var/www/buildwithangga
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/octane.log
stopwaitsecs=60
When to Use Which
| Scenario | Recommendation |
|---|---|
| Maximum performance needed | Swoole |
| Need concurrent tasks | Swoole |
| Need in-memory cache (2M ops/sec) | Swoole |
| Simple setup priority | RoadRunner |
| Windows development | RoadRunner |
| New to Octane | RoadRunner (start here) |
| Enterprise/Production at scale | Swoole |
| Docker-based deployment | Either (RoadRunner slightly easier) |
Bagian 6: The Gotchas — Mistakes I Made
Ini bagian paling penting. Octane bukan drop-in replacement. Ada fundamental changes dalam cara aplikasi bekerja, dan saya belajar ini the hard way.
Gotcha #1: Static Properties Persist Across Requests
The Problem:
// ❌ CODE YANG BERMASALAH
class CartService
{
private static array $items = [];
public function addItem(string $productId): void
{
self::$items[] = $productId;
}
public function getItems(): array
{
return self::$items;
}
}
// Request 1: User A adds item "course-laravel"
// self::$items = ['course-laravel']
// Request 2: User B (DIFFERENT USER!) adds item "course-vue"
// self::$items = ['course-laravel', 'course-vue'] ← WRONG!
// User B sees User A's cart items!
// Request 3: User C just views cart
// self::$items STILL = ['course-laravel', 'course-vue']
// User C sees items from both A and B!
Impact di BuildWithAngga: User melihat cart orang lain. Security nightmare.
The Fix:
// ✅ SOLUSI 1: Use scoped binding
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->scoped(CartService::class, function () {
return new CartService();
});
}
// Setiap request dapat instance baru
// ✅ SOLUSI 2: Store di session instead of static
class CartService
{
public function addItem(string $productId): void
{
$items = session('cart.items', []);
$items[] = $productId;
session(['cart.items' => $items]);
}
public function getItems(): array
{
return session('cart.items', []);
}
}
// ✅ SOLUSI 3: Reset di listener
// config/octane.php listeners
RequestTerminated::class => [
function ($event) {
CartService::reset();
},
],
// Di service
class CartService
{
private static array $items = [];
public static function reset(): void
{
self::$items = [];
}
}
Gotcha #2: Resolving Request/Auth in Constructor
The Problem:
// ❌ CODE YANG BERMASALAH
class CourseController extends Controller
{
private User $user;
private int $userId;
public function __construct()
{
// Constructor runs ONCE saat worker boot
// BUKAN setiap request!
$this->user = auth()->user(); // NULL atau user pertama!
$this->userId = auth()->id(); // 0 atau ID user pertama!
}
public function show(Course $course)
{
// $this->userId adalah ID dari REQUEST PERTAMA
// bukan current request!
return view('course', [
'course' => $course,
'progress' => $this->getProgress($this->userId), // WRONG USER!
]);
}
}
Impact di BuildWithAngga: User melihat progress orang lain. Privacy violation.
The Fix:
// ✅ FIX: Resolve di method, bukan constructor
class CourseController extends Controller
{
public function show(Course $course, Request $request)
{
// Resolve per-request, selalu correct
$user = $request->user();
$userId = $user?->id;
return view('course', [
'course' => $course,
'progress' => $this->getProgress($userId),
]);
}
}
// ✅ ATAU: Use method injection
class CourseController extends Controller
{
public function show(Course $course)
{
// auth() helper works correctly in method context
$userId = auth()->id();
return view('course', [
'course' => $course,
'progress' => $this->getProgress($userId),
]);
}
}
Gotcha #3: Config/ENV Changes Not Reflected
The Problem:
# Saya update .env
APP_DEBUG=false
[email protected]
# Restart Octane
php artisan octane:reload
# Tapi masih pakai config lama!
# Karena config di-cache saat worker boot
The Fix:
# Untuk .env changes, perlu FULL restart, bukan reload
# Step 1: Clear config cache
php artisan config:clear
# Step 2: Rebuild config cache
php artisan config:cache
# Step 3: STOP and START (bukan reload)
php artisan octane:stop
php artisan octane:start
# ATAU via supervisor
sudo supervisorctl restart octane
Best Practice untuk Deployment:
# deployment.sh
#!/bin/bash
set -e
# Pull latest code
git pull origin main
# Install dependencies
composer install --optimize-autoloader --no-dev
# Clear all caches
php artisan config:clear
php artisan route:clear
php artisan view:clear
# Rebuild caches
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Restart Octane (full restart untuk env changes)
php artisan octane:stop || true
sleep 2
sudo supervisorctl start octane
echo "Deployment complete!"
Gotcha #4: Memory Leaks from Unbounded Collections
The Problem:
// ❌ CODE YANG BERMASALAH
class AnalyticsService
{
private array $events = [];
public function track(string $event, array $data): void
{
// Array grows with every request
$this->events[] = [
'event' => $event,
'data' => $data,
'time' => now(),
];
// Tidak pernah di-clear!
// Request 1: 1 event
// Request 100: 100 events
// Request 10000: 10000 events ← MEMORY EXPLODES
}
}
Impact: Worker memory usage naik terus sampai crash atau di-restart.
The Fix:
// ✅ SOLUSI 1: Flush periodically
class AnalyticsService
{
private array $events = [];
private int $maxEvents = 100;
public function track(string $event, array $data): void
{
$this->events[] = [
'event' => $event,
'data' => $data,
'time' => now(),
];
// Auto-flush when reaching threshold
if (count($this->events) >= $this->maxEvents) {
$this->flush();
}
}
public function flush(): void
{
if (empty($this->events)) {
return;
}
// Send to analytics service atau database
dispatch(new ProcessAnalyticsEvents($this->events));
// Clear the array
$this->events = [];
}
}
// ✅ SOLUSI 2: Use scoped binding
$this->app->scoped(AnalyticsService::class);
// ✅ SOLUSI 3: Flush di request termination
// config/octane.php
RequestTerminated::class => [
function ($event) {
app(AnalyticsService::class)->flush();
},
],
Gotcha #5: Database "MySQL Has Gone Away"
The Problem:
Production logs:
[2024-01-15 03:45:22] SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
# Kenapa?
# - Octane workers hidup lama (handle ratusan requests)
# - MySQL close idle connections setelah wait_timeout (default 8 jam)
# - Worker masih hold reference ke dead connection
# - Next query fails
The Fix:
// config/database.php
'mysql' => [
// ... other config
'options' => [
// Jangan gunakan persistent connections dengan Octane
PDO::ATTR_PERSISTENT => false,
// Timeout settings
PDO::ATTR_TIMEOUT => 10,
],
],
// config/octane.php
'listeners' => [
RequestTerminated::class => [
// Disconnect setelah setiap request
\\Laravel\\Octane\\Listeners\\DisconnectFromDatabases::class,
],
],
Alternative: Adjust MySQL wait_timeout:
-- Increase wait_timeout (tapi ini workaround, bukan fix)
SET GLOBAL wait_timeout = 28800; -- 8 hours
SET GLOBAL interactive_timeout = 28800;
Gotcha #6: Third-Party Package Incompatibility
Packages yang saya temukan bermasalah:
❌ Laravel Telescope (memory leaks tanpa proper config)
Fix: Disable di production atau configure properly
// config/telescope.php
'enabled' => env('TELESCOPE_ENABLED', false),
❌ Laravel Debugbar (breaks under Octane)
Fix: Disable di production
// .env (production)
DEBUGBAR_ENABLED=false
❌ Some PDF generators (static state issues)
Fix: Use queue untuk PDF generation
❌ Some old payment gateways (singleton issues)
Fix: Check documentation, wrap dengan scoped binding
❌ Inertia.js (older versions had issues)
Fix: Update to latest version (v1.0+)
How to Check Package Compatibility:
# Search GitHub issues
# Go to package repo, search "octane"
# Example: github.com/spatie/laravel-permission/issues?q=octane
# Test thoroughly
# Run load tests dengan package enabled
# Monitor memory usage over time
Prevention Checklist
Sebelum deploy Octane ke production:
CODE REVIEW CHECKLIST:
□ No static properties storing request-specific data
□ No auth/request resolved in constructors
□ No unbounded arrays/collections in singletons
□ All services either stateless or properly scoped
□ No file handles kept open across requests
PACKAGE AUDIT:
□ All packages tested dengan Octane
□ Telescope disabled atau properly configured
□ Debugbar disabled di production
□ Payment gateways tested
□ PDF generators tested
INFRASTRUCTURE:
□ Session driver = redis (bukan file/database)
□ Cache driver = redis
□ Queue driver = redis
□ Database connections properly managed
□ Supervisor configured
□ Nginx configured
□ Log rotation setup
TESTING:
□ Load testing completed
□ Memory leak testing (run 1+ jam)
□ Auth/session tested dengan multiple users
□ File upload tested
□ All critical flows tested
"Octane mengubah PHP dari stateless ke stateful. Ini bukan upgrade — ini paradigm shift. Kamu sekarang harus THINK about state, memory, dan lifecycle. Kalau kamu skip bagian gotchas ini, kamu akan punya production incidents yang sangat painful."
Next: Bagian 7-8 — Octane-Specific Optimizations dan Results
Di bagian selanjutnya, saya akan tunjukkan optimizations khusus Octane (Concurrent Tasks, Octane Cache, Ticks) dan hasil benchmark sebelum/sesudah di BuildWithAngga.
Bagian 7: Octane-Specific Optimizations
Setelah Octane running stable, saatnya maximize performance dengan fitur-fitur yang hanya available di Octane — khususnya dengan Swoole.
Concurrent Tasks — Parallel Processing
Ini salah satu fitur paling powerful di Swoole. Execute multiple operations simultaneously instead of sequentially.
Case di BuildWithAngga: Course detail page perlu load banyak data.
// ❌ BEFORE: Sequential (lambat)
// app/Http/Controllers/CourseController.php
public function show(Course $course)
{
// Setiap query harus selesai sebelum yang berikutnya mulai
$courseDetails = $this->courseService->getDetails($course); // 80ms
$instructor = $this->instructorService->get($course->user_id); // 60ms
$curriculum = $this->curriculumService->get($course->id); // 100ms
$reviews = $this->reviewService->getLatest($course->id, 10); // 70ms
$relatedCourses = $this->courseService->getRelated($course); // 90ms
$studentProgress = $this->progressService->get($course, auth()->user()); // 50ms
$discussionCount = $this->discussionService->count($course->id); // 40ms
// TOTAL: 80+60+100+70+90+50+40 = 490ms
// Padahal semua query INDEPENDENT satu sama lain!
return view('courses.show', compact(
'courseDetails', 'instructor', 'curriculum',
'reviews', 'relatedCourses', 'studentProgress', 'discussionCount'
));
}
// ✅ AFTER: Concurrent dengan Octane (cepat!)
// app/Http/Controllers/CourseController.php
use Laravel\\Octane\\Facades\\Octane;
public function show(Course $course)
{
$user = auth()->user();
// Semua operations jalan PARALLEL
[
$courseDetails,
$instructor,
$curriculum,
$reviews,
$relatedCourses,
$studentProgress,
$discussionCount,
] = Octane::concurrently([
fn () => $this->courseService->getDetails($course),
fn () => $this->instructorService->get($course->user_id),
fn () => $this->curriculumService->get($course->id),
fn () => $this->reviewService->getLatest($course->id, 10),
fn () => $this->courseService->getRelated($course),
fn () => $this->progressService->get($course, $user),
fn () => $this->discussionService->count($course->id),
]);
// TOTAL: max(80,60,100,70,90,50,40) = 100ms
// Hanya selambat operation yang paling lama!
return view('courses.show', compact(
'courseDetails', 'instructor', 'curriculum',
'reviews', 'relatedCourses', 'studentProgress', 'discussionCount'
));
}
// IMPROVEMENT: 490ms → 100ms = 4.9x faster!
Visual Explanation:
SEQUENTIAL (Before):
Timeline: 0ms ──────────────────────────────────────────────── 490ms
courseDetails ████████ (80ms)
instructor ██████ (60ms)
curriculum ██████████ (100ms)
reviews ███████ (70ms)
relatedCourses █████████ (90ms)
studentProgress █████ (50ms)
discussionCount ████ (40ms)
│
▼
TOTAL: 490ms
CONCURRENT (After):
Timeline: 0ms ────────────────────── 100ms
courseDetails ████████ (80ms)
instructor ██████ (60ms)
curriculum ██████████ (100ms) ← Slowest determines total
reviews ███████ (70ms)
relatedCourses █████████ (90ms)
studentProgress █████ (50ms)
discussionCount ████ (40ms)
│
▼
TOTAL: 100ms (5x faster!)
Real Implementation di BuildWithAngga:
// app/Services/CoursePageService.php
namespace App\\Services;
use App\\Models\\Course;
use App\\Models\\User;
use Laravel\\Octane\\Facades\\Octane;
use Illuminate\\Support\\Facades\\Cache;
class CoursePageService
{
public function __construct(
private CourseService $courseService,
private InstructorService $instructorService,
private CurriculumService $curriculumService,
private ReviewService $reviewService,
private ProgressService $progressService,
private DiscussionService $discussionService,
) {}
public function getPageData(Course $course, ?User $user): array
{
// Cache key untuk data yang jarang berubah
$cacheKey = "course:{$course->id}:page_data";
// Check cache dulu
$cachedData = Cache::get($cacheKey);
if ($cachedData && !$user) {
// Guest users dapat cached data
return $cachedData;
}
// Untuk logged-in users atau cache miss, load fresh
$tasks = [
'details' => fn () => $this->courseService->getDetails($course),
'instructor' => fn () => $this->instructorService->get($course->user_id),
'curriculum' => fn () => $this->curriculumService->get($course->id),
'reviews' => fn () => $this->reviewService->getLatest($course->id, 10),
'related' => fn () => $this->courseService->getRelated($course),
'discussions' => fn () => $this->discussionService->count($course->id),
];
// User-specific data (tidak di-cache)
if ($user) {
$tasks['progress'] = fn () => $this->progressService->get($course, $user);
$tasks['enrolled'] = fn () => $user->enrolledCourses()->where('course_id', $course->id)->exists();
}
// Execute all concurrently
$results = Octane::concurrently($tasks);
// Cache non-user-specific data
$cacheableData = array_diff_key($results, ['progress' => true, 'enrolled' => true]);
Cache::put($cacheKey, $cacheableData, now()->addMinutes(15));
return $results;
}
}
Octane Cache — Lightning Fast In-Memory Cache
Swoole menyediakan in-memory cache yang bisa mencapai 2 juta operasi per detik. Jauh lebih cepat dari Redis untuk hot data.
use Illuminate\\Support\\Facades\\Cache;
// ✅ Octane Cache untuk frequently accessed data
// Store
Cache::store('octane')->put('active_users_count', $count, 60);
// Retrieve (microseconds, bukan milliseconds!)
$count = Cache::store('octane')->get('active_users_count');
// Increment (atomic, thread-safe)
Cache::store('octane')->increment('page_views');
// Remember pattern
$popularCourses = Cache::store('octane')->remember('popular_courses', 60, function () {
return Course::popular()->limit(10)->get();
});
Real Use Cases di BuildWithAngga:
// app/Services/RealTimeStatsService.php
namespace App\\Services;
use Illuminate\\Support\\Facades\\Cache;
class RealTimeStatsService
{
/**
* Track course view (called on every course page load)
*/
public function trackView(int $courseId): void
{
// Increment in Octane cache (super fast)
$key = "views:course:{$courseId}";
Cache::store('octane')->increment($key);
// Track timestamp for batching
$batchKey = "views:pending";
$pending = Cache::store('octane')->get($batchKey, []);
$pending[$courseId] = ($pending[$courseId] ?? 0) + 1;
Cache::store('octane')->put($batchKey, $pending, 300);
}
/**
* Get real-time view count (for display)
*/
public function getViewCount(int $courseId): int
{
$cached = Cache::store('octane')->get("views:course:{$courseId}", 0);
$persisted = Cache::store('redis')->get("views:persisted:{$courseId}", 0);
return $cached + $persisted;
}
/**
* Track active users (for "X people viewing" feature)
*/
public function trackActiveUser(int $courseId, int $userId): void
{
$key = "active:course:{$courseId}";
$activeUsers = Cache::store('octane')->get($key, []);
$activeUsers[$userId] = time();
// Remove users inactive > 5 minutes
$activeUsers = array_filter($activeUsers, fn ($time) =>
time() - $time < 300
);
Cache::store('octane')->put($key, $activeUsers, 600);
}
/**
* Get active user count
*/
public function getActiveUserCount(int $courseId): int
{
$activeUsers = Cache::store('octane')->get("active:course:{$courseId}", []);
return count($activeUsers);
}
}
⚠️ IMPORTANT WARNING:
// Octane Cache data HILANG saat server restart!
// ❌ JANGAN gunakan untuk:
// - User sessions (pakai Redis)
// - Shopping cart data (pakai Redis)
// - Anything yang harus persist
// ✅ GUNAKAN untuk:
// - Real-time counters
// - Temporary rate limiting
// - Hot data yang bisa di-rebuild
// - View counts sebelum di-persist ke database
Ticks and Intervals — Background Tasks
Dengan Swoole, kita bisa run periodic tasks tanpa cron:
// app/Providers/OctaneServiceProvider.php
namespace App\\Providers;
use App\\Services\\RealTimeStatsService;
use Illuminate\\Support\\Facades\\Cache;
use Illuminate\\Support\\ServiceProvider;
use Laravel\\Octane\\Facades\\Octane;
class OctaneServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Hanya register ticks saat running di Octane
if (!$this->app->bound('octane')) {
return;
}
$this->registerTicks();
}
private function registerTicks(): void
{
// Persist view counts ke database setiap 30 detik
Octane::tick('persist-views', function () {
$pending = Cache::store('octane')->pull('views:pending', []);
if (empty($pending)) {
return;
}
foreach ($pending as $courseId => $count) {
// Update database
\\DB::table('courses')
->where('id', $courseId)
->increment('views', $count);
// Update Redis (persisted count)
Cache::store('redis')->increment("views:persisted:{$courseId}", $count);
}
logger()->info('Persisted view counts', ['count' => count($pending)]);
}, seconds: 30);
// Cleanup expired active user sessions setiap menit
Octane::tick('cleanup-active-users', function () {
// Cleanup logic
$keys = Cache::store('octane')->get('active:keys', []);
foreach ($keys as $key) {
$activeUsers = Cache::store('octane')->get($key, []);
$cleaned = array_filter($activeUsers, fn ($time) =>
time() - $time < 300
);
if (count($cleaned) !== count($activeUsers)) {
Cache::store('octane')->put($key, $cleaned, 600);
}
}
}, seconds: 60);
// Health check setiap 5 menit
Octane::tick('health-check', function () {
// Check database connection
try {
\\DB::select('SELECT 1');
} catch (\\Exception $e) {
logger()->error('Database health check failed', [
'error' => $e->getMessage(),
]);
}
// Check Redis connection
try {
Cache::store('redis')->put('health', now()->timestamp, 60);
} catch (\\Exception $e) {
logger()->error('Redis health check failed', [
'error' => $e->getMessage(),
]);
}
}, seconds: 300);
}
}
Register provider:
// bootstrap/providers.php
return [
App\\Providers\\AppServiceProvider::class,
App\\Providers\\OctaneServiceProvider::class, // Add this
];
Swoole Tables — Shared Memory Structures
Untuk data yang perlu diakses dari semua workers:
// config/octane.php
'tables' => [
'course_stats' => [
'columns' => [
['name' => 'views', 'type' => 'int', 'size' => 8],
['name' => 'enrollments', 'type' => 'int', 'size' => 8],
['name' => 'last_updated', 'type' => 'int', 'size' => 8],
],
'rows' => 10000, // Pre-allocate for 10k courses
],
'rate_limits' => [
'columns' => [
['name' => 'count', 'type' => 'int'],
['name' => 'window_start', 'type' => 'int'],
],
'rows' => 100000, // 100k rate limit entries
],
],
// Menggunakan Swoole Table
use Laravel\\Octane\\Facades\\Octane;
// Get table instance
$table = Octane::table('course_stats');
// Set data
$table->set('course:123', [
'views' => 5000,
'enrollments' => 500,
'last_updated' => time(),
]);
// Get data
$stats = $table->get('course:123');
// ['views' => 5000, 'enrollments' => 500, 'last_updated' => ...]
// Increment
$table->incr('course:123', 'views', 1);
// Check exists
if ($table->exists('course:123')) {
// ...
}
// Delete
$table->del('course:123');
// Iterate all entries
foreach ($table as $key => $value) {
echo "{$key}: " . json_encode($value) . "\\n";
}
Warming Services for Instant First Requests
Pre-instantiate heavy services saat worker boot:
// config/octane.php
'warm' => [
// Framework services
\\Illuminate\\Contracts\\Http\\Kernel::class,
\\Illuminate\\Contracts\\Console\\Kernel::class,
// Application services (heavy constructors)
\\App\\Services\\CourseService::class,
\\App\\Services\\VideoService::class,
\\App\\Services\\PaymentService::class,
\\App\\Services\\SearchService::class,
// Services with database connections
\\App\\Repositories\\CourseRepository::class,
\\App\\Repositories\\UserRepository::class,
// External service clients
\\App\\Services\\Integrations\\StripeService::class,
\\App\\Services\\Integrations\\VimeoService::class,
],
Impact: First request ke worker baru sama cepatnya dengan subsequent requests — tidak ada cold start penalty.
Summary: Optimizations Applied to BuildWithAngga
| Optimization | Before | After | Improvement |
|---|---|---|---|
| Concurrent Tasks | 490ms (sequential) | 100ms (parallel) | 4.9x faster |
| Octane Cache | 5ms (Redis) | 0.1ms (memory) | 50x faster |
| Background Ticks | Cron every minute | Tick every 30s | Smoother, realtime |
| Service Warming | 50ms cold start | 0ms (pre-warmed) | Instant |
| Swoole Tables | Redis lookups | In-memory | 10x faster |
Bagian 8: The Results — Before and After
Sekarang moment of truth. Bagaimana performance BuildWithAngga sebelum dan sesudah Octane?
Benchmark Methodology
TEST SETUP:
├── Tool: k6, wrk, dan custom benchmarks
├── Server: Same production server (8 core, 16GB RAM)
├── Database: Same MySQL instance
├── Cache: Same Redis instance
├── Test duration: 5 menit per scenario
├── Concurrent users: 50, 100, 200, 500
├── Tested pages: Homepage, Course List, Course Detail, API
WHAT CHANGED:
├── PHP-FPM → Octane + Swoole
├── Added Concurrent Tasks
├── Added Octane Cache for hot data
├── Added Background Ticks
└── Applied all optimizations dari Bagian 7
Benchmark Results
Homepage (Marketing Landing Page):
HOMEPAGE BENCHMARK (200 concurrent users, 5 minutes):
┌────────────────────────────────────────────────────────────────┐
│ PHP-FPM │
├────────────────────────────────────────────────────────────────┤
│ Requests/sec: 89 │
│ Avg Response: 1,850ms │
│ p50 Response: 1,620ms │
│ p95 Response: 3,450ms │
│ p99 Response: 5,200ms │
│ Error Rate: 3.2% │
│ CPU Usage: 94% │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ OCTANE + SWOOLE │
├────────────────────────────────────────────────────────────────┤
│ Requests/sec: 892 │
│ Avg Response: 185ms │
│ p50 Response: 142ms │
│ p95 Response: 380ms │
│ p99 Response: 520ms │
│ Error Rate: 0.1% │
│ CPU Usage: 42% │
└────────────────────────────────────────────────────────────────┘
IMPROVEMENT: 10x throughput, 10x faster response, 50% less CPU
Course Detail Page (Heavy Page):
COURSE DETAIL BENCHMARK (100 concurrent users, 5 minutes):
┌────────────────────────────────────────────────────────────────┐
│ PHP-FPM │
├────────────────────────────────────────────────────────────────┤
│ Requests/sec: 38 │
│ Avg Response: 2,450ms │
│ p95 Response: 4,800ms │
│ Error Rate: 5.1% │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ OCTANE + SWOOLE │
│ (with Concurrent Tasks) │
├────────────────────────────────────────────────────────────────┤
│ Requests/sec: 485 │
│ Avg Response: 195ms │
│ p95 Response: 410ms │
│ Error Rate: 0.05% │
└────────────────────────────────────────────────────────────────┘
IMPROVEMENT: 12.7x throughput, 12.5x faster response
API Endpoints (JSON Responses):
API BENCHMARK (/api/courses, 200 concurrent):
PHP-FPM:
├── Requests/sec: 142
├── Avg Response: 680ms
└── Error Rate: 2.8%
OCTANE + SWOOLE:
├── Requests/sec: 1,450
├── Avg Response: 65ms
└── Error Rate: 0.02%
IMPROVEMENT: 10.2x throughput, 10.5x faster
Complete Before/After Comparison
╔══════════════════════════════════════════════════════════════════════════╗
║ BUILDWITHANGGA PERFORMANCE TRANSFORMATION ║
╠══════════════════════════════════════════════════════════════════════════╣
║ ║
║ METRIC PHP-FPM OCTANE CHANGE ║
║ ──────────────────────────────────────────────────────────────────── ║
║ ║
║ RESPONSE TIMES ║
║ Homepage (avg) 1,850ms 185ms ↓ 90% ║
║ Course List (avg) 2,100ms 210ms ↓ 90% ║
║ Course Detail (avg) 2,450ms 195ms ↓ 92% ║
║ Video Player Init 3,200ms 280ms ↓ 91% ║
║ API Endpoints (avg) 680ms 65ms ↓ 90% ║
║ Checkout Page 1,900ms 180ms ↓ 91% ║
║ ║
║ THROUGHPUT ║
║ Homepage (req/sec) 89 892 ↑ 10x ║
║ Course Detail (req/sec) 38 485 ↑ 12.7x ║
║ API (req/sec) 142 1,450 ↑ 10.2x ║
║ Max Concurrent Users ~200 ~2,000 ↑ 10x ║
║ ║
║ RELIABILITY ║
║ Error Rate (peak) 5.1% 0.05% ↓ 99% ║
║ Timeout Rate 2.3% 0.01% ↓ 99.5% ║
║ Uptime 99.2% 99.95% ↑ 0.75% ║
║ ║
║ SERVER RESOURCES ║
║ CPU Usage (peak) 94% 42% ↓ 55% ║
║ Memory Usage 14GB 10GB ↓ 29% ║
║ PHP Workers Needed 32 8 ↓ 75% ║
║ ║
╚══════════════════════════════════════════════════════════════════════════╝
Business Impact — The Numbers That Matter
Performance improvement is nice, tapi yang penting adalah business impact:
BUSINESS METRICS (3 Months After Octane):
User Experience:
├── Bounce Rate: 45% → 18% (↓ 60%)
├── Session Duration: 2.1 min → 7.2 min (↑ 243%)
├── Pages per Session: 2.3 → 8.5 (↑ 270%)
├── Mobile Experience: "Acceptable" → "Fast" (Lighthouse)
└── Core Web Vitals: Failed → Passed
Conversion:
├── Homepage → Course View: 25% → 45% (↑ 80%)
├── Course View → Enrollment: 4% → 8% (↑ 100%)
├── Overall Conversion: 1.8% → 4.1% (↑ 128%)
├── Cart Abandonment: 68% → 42% (↓ 38%)
└── Checkout Success Rate: 89% → 98% (↑ 10%)
Student Satisfaction:
├── Complaints about speed: 12/week → 0-1/week (↓ 95%)
├── Support tickets (perf): 8/week → 1/month (↓ 97%)
├── Reviews mentioning speed: Negative → Positive
└── NPS Score: 32 → 58 (↑ 81%)
Infrastructure Cost:
├── Servers needed: 4 → 2 (↓ 50%)
├── Monthly server cost: $400 → $200 (↓ 50%)
├── Can handle traffic: 400 → 2000+ users (↑ 5x)
└── Headroom for growth: Always maxed → 60% available
Response Time Distribution
BEFORE (PHP-FPM):
Response Time Distribution for Course Detail Page
0-500ms: ████ 8%
500ms-1s: ██████████ 18%
1s-2s: ██████████████████████████ 45%
2s-3s: ████████████ 20%
3s-5s: ████ 7%
>5s: ██ 2% (timeouts)
Median: 1,850ms | p95: 4,200ms
AFTER (Octane):
Response Time Distribution for Course Detail Page
0-100ms: ████████████████████ 35%
100-200ms: ██████████████████████████████████████ 45%
200-300ms: ██████████ 12%
300-500ms: ████ 5%
500ms-1s: ██ 2.5%
>1s: ░ 0.5%
Median: 152ms | p95: 380ms
Real User Feedback
Setelah launch Octane, feedback dari students berubah drastis:
"Finally! Dulu buka course page sambil bikin kopi dulu. Sekarang instant."
"Nggak tau ada yang berubah, tapi sekarang lebih enak aja browsingnya." — Ini feedback terbaik. User nggak notice technology, cuma notice experience.
"Video player-nya loading cepet banget sekarang. Dulu suka buffering lama."
Dan yang paling penting: complaints tentang speed turun dari 12 per minggu menjadi hampir zero.
Cost Analysis
INFRASTRUCTURE COST COMPARISON:
BEFORE (PHP-FPM):
├── 4x VPS (4 core, 8GB RAM each)
├── Load balancer
├── Monthly cost: ~$400
├── Can handle: ~400 concurrent users
├── CPU at peak: 90-95%
└── Status: Always near limit, scary
AFTER (Octane):
├── 2x VPS (4 core, 8GB RAM each)
├── Load balancer
├── Monthly cost: ~$200
├── Can handle: ~2000 concurrent users
├── CPU at peak: 40-50%
└── Status: Comfortable headroom
SAVINGS: $200/month = $2,400/year
PLUS: 5x more capacity for future growth
"Octane bukan cuma bikin website lebih cepat. Octane bikin bisnis lebih profitable. Faster site = better UX = higher conversion = more revenue. Plus server cost turun. Ini win-win-win."
Bagian 9: When to Use (and NOT Use) Octane
Setelah pengalaman dengan BuildWithAngga, saya punya perspective yang lebih nuanced tentang kapan Octane is right choice.
Ideal Use Cases for Octane
✅ GUNAKAN OCTANE KETIKA:
HIGH TRAFFIC APPLICATIONS
├── 500+ requests per minute sustained
├── Peak traffic significantly higher than baseline
├── Need to handle traffic spikes
└── Growth trajectory requires scalability
RESPONSE TIME CRITICAL
├── E-commerce (every ms = conversion)
├── Real-time applications
├── API backends untuk mobile apps
├── Interactive web applications
└── Competitive market where speed matters
SPECIFIC FEATURES NEEDED
├── Concurrent processing (Swoole)
├── In-memory caching (2M ops/sec)
├── Background tasks without cron
├── WebSocket support
└── Real-time statistics/counters
COST OPTIMIZATION
├── Want to reduce server count
├── Need more headroom on existing servers
├── High server costs relative to revenue
└── Planning for growth without linear cost increase
TECHNICAL READINESS
├── Team understands stateful PHP
├── Have time for proper testing
├── Can audit codebase for compatibility
├── Production environment is controllable
└── Using Redis for session/cache
When NOT to Use Octane
❌ JANGAN GUNAKAN OCTANE KETIKA:
LOW TRAFFIC / SIMPLE APPLICATIONS
├── < 100 requests per minute
├── Simple CRUD applications
├── Internal tools with few users
├── MVP / early stage products
└── Already fast enough with PHP-FPM + proper optimization
TEAM/ENVIRONMENT CONSTRAINTS
├── Team tidak familiar dengan stateful PHP
├── No time for thorough testing
├── Shared hosting (no supervisor access)
├── Windows production servers (Swoole)
├── Cannot audit entire codebase
└── Heavy reliance on incompatible packages
ALREADY OPTIMIZED
├── Already using route/config/view caching
├── Already using Redis properly
├── Already optimized database queries
├── Response times already < 300ms
├── No bootstrap overhead (already minimized)
COMPLEXITY NOT JUSTIFIED
├── Added complexity tidak worth the gain
├── Team prefers simplicity
├── Maintenance burden too high
├── Deployment complexity increase not acceptable
Decision Flowchart
START: Should I Use Octane?
│
▼
┌─────────────────────────────────┐
│ Have you optimized basics? │
│ (cache, queries, Redis, OPcache)│
└───────────────┬─────────────────┘
│
┌───────┴───────┐
│ │
NO YES
│ │
▼ ▼
┌───────────────┐ ┌─────────────────────────────┐
│ DO THAT FIRST │ │ Response time acceptable? │
│ (Octane won't │ │ (< 300ms for most pages?) │
│ fix bad code) │ └─────────────┬───────────────┘
└───────────────┘ │
┌───────┴───────┐
│ │
YES NO
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────────┐
│ Traffic high? │ │ Identify bottleneck:│
│ (> 500 req/min) │ │ Bootstrap overhead? │
└────────┬────────┘ └──────────┬──────────┘
│ │
┌───────┴───────┐ ┌───────┴───────┐
│ │ │ │
NO YES YES NO
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────────────┐ ┌────────────────┐
│ Probably │ │ CONSIDER OCTANE │ │ Fix the actual │
│ don't │ │ │ │ bottleneck │
│ need it │ │ Check: │ │ (not Octane │
│ │ │ • Team readiness │ │ problem) │
│ BUT: if │ │ • Package compat │ └────────────────┘
│ expecting│ │ • Testing time │
│ growth, │ │ • Infrastructure │
│ prepare │ └──────────────────┘
└──────────┘
Alternatives to Octane
Sebelum jump ke Octane, pastikan sudah maximize these:
OPTIMIZATION CHECKLIST (Before Octane):
Level 1: Basic Caching
□ php artisan config:cache
□ php artisan route:cache
□ php artisan view:cache
□ OPcache enabled dan configured
Level 2: Application Caching
□ Redis untuk session
□ Redis untuk cache
□ Query result caching
□ Fragment caching (views)
Level 3: Database Optimization
□ Proper indexes
□ No N+1 queries
□ Eager loading
□ Query optimization
Level 4: Infrastructure
□ CDN untuk static assets
□ HTTP/2 enabled
□ Gzip compression
□ Browser caching headers
Level 5: Advanced
□ Queue untuk heavy operations
□ Database read replicas
□ Horizontal scaling
If ALL above done and STILL need more speed → Octane
Gradual Adoption Strategy
Tidak harus all-or-nothing. Bisa gradual:
PHASE 1: Test Environment
├── Setup Octane di staging
├── Run full test suite
├── Load testing
├── Memory leak testing
└── Duration: 2-4 weeks
PHASE 2: Non-Critical Production
├── Deploy ke subset of servers
├── Route sebagian traffic ke Octane
├── Monitor closely
├── Fix issues yang muncul
└── Duration: 2-4 weeks
PHASE 3: Full Production
├── Migrate semua traffic
├── Keep PHP-FPM as fallback
├── Monitor 24/7 first week
├── Document learnings
└── Duration: 1-2 weeks
PHASE 4: Optimization
├── Implement concurrent tasks
├── Add Octane cache
├── Setup background ticks
├── Fine-tune configuration
└── Duration: Ongoing
"Octane powerful, tapi bukan solusi untuk semua masalah. Kalau website lambat karena N+1 queries, Octane tidak akan fix itu — database masih kena 1000 queries. Fix fundamentals dulu. Octane adalah optimization layer di atas fondasi yang sudah solid."
Next: Bagian 10-11 — Production Checklist dan Closing
Di bagian terakhir, saya akan berikan production checklist lengkap dan rekomendasi untuk learning lebih lanjut.
Bagian 10: Production Checklist & Best Practices
Sebelum deploy Octane ke production, pastikan semua item di checklist ini sudah di-address.
Pre-Deployment Checklist
═══════════════════════════════════════════════════════════════════
OCTANE PRODUCTION CHECKLIST
═══════════════════════════════════════════════════════════════════
CODE REVIEW:
□ No static properties storing request-specific data
□ No auth/request resolved in constructors
□ No unbounded arrays/collections in singletons
□ All services properly scoped or reset
□ No file handles kept open across requests
□ No global state mutations
□ All closures properly handle state
PACKAGE AUDIT:
□ All packages tested with Octane
□ Telescope disabled or properly configured
□ Debugbar disabled in production
□ Payment gateways tested
□ PDF generators tested
□ Image processors tested
□ Mail packages tested
□ Broadcasting packages tested
SESSION & AUTH:
□ Session driver = redis (NOT file/database)
□ Auth guards tested with concurrent requests
□ Remember me functionality tested
□ Social login packages tested
□ API token authentication tested
DATABASE:
□ Connection pooling configured
□ DisconnectFromDatabases listener enabled
□ Long query timeouts handled
□ Transaction handling verified
□ Queue connections separate from web
CACHE:
□ Cache driver = redis
□ Octane cache used appropriately (ephemeral only)
□ Cache tags working if used
□ Cache clear strategy documented
INFRASTRUCTURE:
□ Supervisor configured and tested
□ Nginx reverse proxy configured
□ SSL termination working
□ Health check endpoint created
□ Log rotation configured
□ Monitoring alerts set up
TESTING:
□ Load testing completed (minimum 1 hour)
□ Memory leak testing done
□ Auth/session tested with multiple users
□ File upload tested
□ All critical user flows tested
□ Error handling verified
□ Graceful degradation tested
DEPLOYMENT:
□ Deployment script created and tested
□ Rollback procedure documented
□ Zero-downtime deployment verified
□ Environment variables documented
□ Secrets properly managed
Health Check Endpoint
Buat endpoint untuk monitoring:
// routes/web.php
Route::get('/health', function () {
$checks = [
'status' => 'ok',
'timestamp' => now()->toIso8601String(),
'checks' => [],
];
// Database check
try {
DB::select('SELECT 1');
$checks['checks']['database'] = 'ok';
} catch (\\Exception $e) {
$checks['checks']['database'] = 'error';
$checks['status'] = 'degraded';
}
// Redis check
try {
Cache::store('redis')->set('health_check', 'ok', 10);
$checks['checks']['redis'] = 'ok';
} catch (\\Exception $e) {
$checks['checks']['redis'] = 'error';
$checks['status'] = 'degraded';
}
// Octane check
if (app()->bound('octane')) {
$checks['checks']['octane'] = 'running';
$checks['server'] = 'octane';
} else {
$checks['checks']['octane'] = 'not_running';
$checks['server'] = 'php-fpm';
}
// Memory usage
$checks['memory'] = [
'used' => round(memory_get_usage(true) / 1024 / 1024, 2) . ' MB',
'peak' => round(memory_get_peak_usage(true) / 1024 / 1024, 2) . ' MB',
];
$statusCode = $checks['status'] === 'ok' ? 200 : 503;
return response()->json($checks, $statusCode);
});
Deployment Script
#!/bin/bash
# deploy.sh - Zero-downtime deployment for Octane
set -e
APP_DIR="/var/www/buildwithangga"
BRANCH="${1:-main}"
echo "🚀 Starting deployment..."
echo "📁 Directory: $APP_DIR"
echo "🌿 Branch: $BRANCH"
cd $APP_DIR
# 1. Pull latest code
echo "📥 Pulling latest code..."
git fetch origin
git checkout $BRANCH
git pull origin $BRANCH
# 2. Install dependencies
echo "📦 Installing dependencies..."
composer install --optimize-autoloader --no-dev --no-interaction
# 3. Run migrations
echo "🗄️ Running migrations..."
php artisan migrate --force
# 4. Clear all caches
echo "🧹 Clearing caches..."
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clear
# 5. Rebuild caches
echo "🔨 Building caches..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
# 6. Build frontend assets (if applicable)
if [ -f "package.json" ]; then
echo "🎨 Building frontend assets..."
npm ci
npm run build
fi
# 7. Restart Octane (graceful)
echo "♻️ Restarting Octane..."
php artisan octane:reload || {
echo "⚠️ Reload failed, doing full restart..."
php artisan octane:stop || true
sleep 2
sudo supervisorctl start octane
}
# 8. Restart queue workers
echo "👷 Restarting queue workers..."
php artisan queue:restart
# 9. Clear opcache (if using PHP-FPM as fallback)
echo "🧠 Clearing OPcache..."
curl -s "<http://127.0.0.1/opcache-clear.php>" || true
# 10. Health check
echo "❤️ Running health check..."
sleep 3
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" <http://127.0.0.1:8000/health>)
if [ "$HTTP_STATUS" -eq 200 ]; then
echo "✅ Deployment successful! Health check passed."
else
echo "❌ Health check failed with status $HTTP_STATUS"
echo "🔄 Rolling back..."
git checkout HEAD~1
php artisan octane:reload
exit 1
fi
echo "🎉 Deployment complete!"
echo "📊 Check status: php artisan octane:status"
Monitoring Recommendations
METRICS TO MONITOR:
Application:
├── Response time (p50, p95, p99)
├── Request rate (req/sec)
├── Error rate (%)
├── Active connections
└── Queue depth
Octane Specific:
├── Worker count (active vs configured)
├── Requests per worker
├── Worker restarts (should be low)
├── Memory per worker
└── Worker lifetime
System:
├── CPU usage (per core)
├── Memory usage
├── Disk I/O
├── Network I/O
└── Open file descriptors
Database:
├── Connection count
├── Query time (slow queries)
├── Connection pool usage
└── Replication lag (if applicable)
ALERTING THRESHOLDS:
Critical (page immediately):
├── Error rate > 5%
├── Response time p99 > 5s
├── CPU > 90% for 5 min
├── Memory > 90%
└── Health check failing
Warning (notify during business hours):
├── Error rate > 1%
├── Response time p95 > 2s
├── Worker restarts > 10/hour
├── Memory growth trend
└── Queue depth increasing
Common Production Issues & Solutions
ISSUE: Workers restarting too frequently
CAUSE: Memory leaks, max_requests too low
FIX:
├── Increase max_requests (500 → 1000)
├── Profile memory usage
├── Check for unbounded collections
└── Review third-party packages
ISSUE: "MySQL has gone away" errors
CAUSE: Long-idle connections closed by MySQL
FIX:
├── Enable DisconnectFromDatabases listener
├── Set PDO::ATTR_PERSISTENT => false
└── Increase MySQL wait_timeout if needed
ISSUE: Session data mixing between users
CAUSE: Static properties, improper state management
FIX:
├── Audit all static properties
├── Use scoped bindings
├── Reset state in RequestTerminated listener
└── Test with multiple concurrent users
ISSUE: Config changes not applied
CAUSE: Config cached at worker boot
FIX:
├── Full restart (not just reload) for .env changes
├── octane:stop then octane:start
└── Update deployment script
ISSUE: High memory usage over time
CAUSE: Memory not being freed properly
FIX:
├── Lower max_requests
├── Enable garbage collection
├── Profile with memory_get_usage()
└── Check for circular references
Best Practices Summary
| Category | Best Practice |
|---|---|
| State | Never store request data in static properties |
| Services | Use scoped bindings for stateful services |
| Auth | Always resolve auth in methods, not constructors |
| Database | Use DisconnectFromDatabases listener |
| Cache | Use Octane cache for ephemeral data only |
| Sessions | Always use Redis, never file/database |
| Deployment | Full restart for .env changes, reload for code |
| Monitoring | Track memory per worker, restart frequency |
| Testing | Load test for minimum 1 hour before production |
| Fallback | Keep PHP-FPM ready as fallback |
Bagian 11: Closing — The Transformation Complete
Recap: The Journey
THE BUILDWITHANGGA TRANSFORMATION:
BEFORE (The Embarrassment):
├── Response time: 3+ seconds
├── Throughput: ~100 req/sec
├── Max users: ~200 concurrent
├── Student complaints: 15/week
├── Conversion rate: 1.8%
├── Server CPU: 95% at peak
└── Status: "Founder yang websitenya lambat"
AFTER (The Pride):
├── Response time: 200-400ms
├── Throughput: ~1000 req/sec
├── Max users: ~2000 concurrent
├── Student complaints: 1/week
├── Conversion rate: 3.6%
├── Server CPU: 42% at peak
└── Status: "Platform yang responsive dan reliable"
THE IMPROVEMENT:
├── 10x faster response time
├── 10x more throughput
├── 10x more concurrent users
├── 100% higher conversion
├── 50% less servers needed
└── Irony → Proof of concept
Key Takeaways
WHAT I LEARNED:
1. UNDERSTAND BEFORE OPTIMIZE
└── Profiling revealed PHP-FPM bootstrap overhead
└── Without understanding, I'd keep throwing servers at it
2. OCTANE IS A PARADIGM SHIFT
└── Stateless → Stateful PHP
└── Requires different mental model
└── Gotchas are real and painful if ignored
3. SWOOLE VS ROADRUNNER
└── Swoole: Max performance, more features, more complex
└── RoadRunner: Easier setup, good performance
└── Start with RoadRunner if new to Octane
4. CONCURRENT TASKS ARE GAME-CHANGER
└── 520ms → 120ms for course page
└── Parallel execution changes everything
└── Design your code to leverage this
5. THE GOTCHAS ARE REAL
└── Static properties will bite you
└── Constructor injection will fail
└── Memory leaks will happen
└── Test thoroughly before production
6. BUSINESS IMPACT IS TANGIBLE
└── Conversion doubled
└── Bounce rate halved
└── Complaints nearly eliminated
└── Server costs reduced
7. IT'S NOT MAGIC
└── Database queries still take the same time
└── External APIs still take the same time
└── You're eliminating bootstrap overhead, not everything
Rekomendasi Kelas di BuildWithAngga
Untuk menguasai Laravel dari fundamental sampai production-level seperti yang dibahas di artikel ini, saya punya beberapa kelas yang bisa membantu:
| Kelas | Fokus | Level |
|---|---|---|
| Laravel Fundamentals | Core concepts, MVC, Eloquent | Beginner |
| Laravel API Development | RESTful APIs, authentication, rate limiting | Intermediate |
| Laravel Advanced | Caching, queues, events, testing | Advanced |
| Laravel Performance | Query optimization, profiling, scaling | Advanced |
| Full-Stack Laravel + Vue | Complete modern web application | Intermediate |
| DevOps untuk Laravel | Docker, CI/CD, server management | Advanced |
Kenapa Belajar di BuildWithAngga:
- ✅ Project-based — Bukan teori, tapi build real applications
- ✅ Production-ready code — Standards yang saya pakai di platform ini
- ✅ Complete source code — Clone, modify, deploy
- ✅ Indonesian language — Penjelasan yang mudah dipahami
- ✅ Lifetime access — Belajar sesuai pace kamu
- ✅ Community support — Diskusi dengan ribuan developers
- ✅ Up-to-date — Selalu update untuk versi Laravel terbaru
Platform yang sekarang 10x lebih cepat ini dibangun dengan knowledge yang sama yang diajarkan di kelas-kelas tersebut.
Quick Links:
👉 Kelas Laravel Premium: buildwithangga.com/kelas?category=laravel
👉 Semua Kelas Premium: buildwithangga.com/kelas
👉 Kelas Gratis untuk Mulai: buildwithangga.com/kelas?type=free
Final Message
Performance bukan luxury — performance adalah expectation.
Di era attention span 8 detik, website yang load 3 detik adalah website yang ditinggalkan.
Users tidak peduli stack kamu apa. Users tidak peduli server kamu berapa. Users peduli: "Kenapa ini lambat?"
Laravel Octane memberikan tools untuk menjawab pertanyaan itu. Tapi tools tanpa understanding adalah resep untuk disaster. Pahami dulu, implement dengan hati-hati, test dengan thorough.
Dan yang paling penting:
Start with the basics. Master them. Then level up.
Jangan loncat ke Octane sebelum kamu paham Laravel fundamentals. Jangan loncat ke Swoole sebelum kamu paham Octane basics. Jangan deploy ke production sebelum kamu test dengan proper load.
BuildWithAngga adalah living proof bahwa transformation dari slow ke fast itu possible. Dari embarrassment ke pride. Dari irony ke proof of concept.
Kamu juga bisa.
Artikel ini ditulis oleh Angga Risky Setiawan, AI Product Engineer & Founder BuildWithAngga — platform online courses untuk developers Indonesia.
Platform yang dulu lambat dan memalukan, sekarang cepat dan membanggakan.
Proof that eating your own cooking makes the recipe better.
👉 buildwithangga.com — Now 10x faster.
Appendix: Quick Reference
Essential Commands:
# Install
composer require laravel/octane
php artisan octane:install
# Run
php artisan octane:start --watch # Development
php artisan octane:start --workers=8 # Production
# Manage
php artisan octane:status
php artisan octane:reload # Code changes
php artisan octane:stop
# Supervisor
sudo supervisorctl start octane
sudo supervisorctl restart octane
sudo supervisorctl status octane
Key Config Options:
// config/octane.php
'server' => 'swoole', // or 'roadrunner'
'workers' => 8, // Usually = CPU cores
'task_workers' => 4, // For concurrent tasks
'max_requests' => 500, // Restart after N requests
'tick' => true, // Enable background ticks
Must-Have Listeners:
RequestTerminated::class => [
FlushTemporaryContainerInstances::class,
DisconnectFromDatabases::class,
],
Session & Cache (Required):
SESSION_DRIVER=redis
CACHE_STORE=redis
QUEUE_CONNECTION=redis