Apa itu Swoole dan Cara Pakai Pada Laravel 12 Projek Web Movie Streaming

Pelajari apa itu Swoole dan bagaimana mengintegrasikannya dengan Laravel 12 untuk membangun website movie streaming dengan performa tinggi. Artikel ini membahas benefit utama Swoole (10x lebih cepat), pros dan cons, langkah integrasi dengan Laravel Octane, poin penting yang perlu diperhatikan, dan common mistakes yang harus dihindari. Lengkap dengan contoh koding real-world untuk fitur streaming, concurrent requests, dan WebSocket untuk live chat.


Bagian 1: Masalah Performa di Aplikasi Streaming

Sebagai founder BuildWithAngga dengan lebih dari 900.000 students, saya sering melihat pola yang sama berulang: developer membangun aplikasi yang berjalan lancar di development, tapi struggle di production.

Bayangkan skenario ini:

Kamu sudah menghabiskan 3 bulan membangun website movie streaming dengan Laravel. Fiturnya lengkap — video streaming, user authentication, watchlist, ratings, comments, bahkan live chat saat menonton. Di local development, semuanya berjalan mulus. Testing dengan 10-20 users simultan? Lancar.

Lalu kamu deploy ke production.

Minggu pertama, 500 users. Masih oke.

Minggu kedua, campaign marketing jalan, users naik ke 2.000. Mulai ada keluhan "loading lama".

Minggu ketiga, viral di social media, 5.000 concurrent users. Server mulai timeout. CPU usage 100%. Response time melonjak dari 200ms ke 3-5 detik.

Apa yang terjadi?

Kenapa PHP/Laravel Bisa Lambat di High Traffic?

Untuk memahami masalahnya, kita perlu lihat bagaimana PHP tradisional (PHP-FPM) menangani requests:

TRADITIONAL PHP-FPM REQUEST CYCLE:

Request 1 masuk:
┌─────────────────────────────────────────────────────┐
│  1. Start PHP process                               │
│  2. Load Composer autoloader                        │
│  3. Bootstrap Laravel framework (~100-300ms)        │
│     ├── Load semua config files                     │
│     ├── Register service providers (50+ providers)  │
│     ├── Boot service providers                      │
│     └── Setup facades, middleware, routes           │
│  4. Create new database connection                  │
│  5. Process business logic                          │
│  6. Return response                                 │
│  7. Kill process, free memory                       │
└─────────────────────────────────────────────────────┘

Request 2 masuk:
┌─────────────────────────────────────────────────────┐
│  1. Start PHP process ← ULANG DARI NOL!            │
│  2. Load Composer autoloader ← LAGI!               │
│  3. Bootstrap Laravel framework ← 100-300ms LAGI!  │
│  ... dan seterusnya                                 │
└─────────────────────────────────────────────────────┘

Lihat masalahnya?

Setiap request harus bootstrap ulang Laravel dari nol. Framework yang sama di-load ribuan kali per menit. Database connection dibuat dan diputus ribuan kali. Memory dialokasi dan di-free ribuan kali.

Ini seperti setiap kali kamu mau masak nasi, kamu harus:

  1. Beli rice cooker baru
  2. Pasang instalasinya
  3. Masak nasi
  4. Buang rice cooker
  5. Repeat untuk nasi berikutnya

Tidak efisien, kan?

Konsekuensi untuk Aplikasi Streaming

Untuk aplikasi movie streaming, masalah ini lebih parah karena:

STREAMING APP CHARACTERISTICS:

1. CONCURRENT CONNECTIONS TINGGI
   ├── Ribuan users menonton bersamaan
   ├── Setiap user = 1 persistent connection
   └── Peak hours = semua nonton Netflix-an

2. REAL-TIME REQUIREMENTS
   ├── Live chat harus instant
   ├── Watch progress sync real-time
   ├── Notifications tidak boleh delay
   └── Viewer count harus live

3. HEAVY DATA PROCESSING
   ├── Video metadata loading
   ├── Recommendations calculation
   ├── Watch history tracking
   └── Analytics processing

4. LONG-LIVED CONNECTIONS
   ├── Users nonton 2 jam per session
   ├── WebSocket untuk chat
   └── Heartbeat untuk presence

PHP-FPM tidak didesain untuk ini. Ia didesain untuk request-response cycle yang cepat — terima request, proses, response, selesai.

Solusi: Swoole + Laravel Octane

Di sinilah Swoole masuk.

Swoole adalah PHP extension yang mengubah cara PHP bekerja secara fundamental. Daripada model "spawn process per request", Swoole menggunakan model "long-running process dengan event loop" — mirip seperti Node.js.

SWOOLE MODEL:

Application Start:
┌─────────────────────────────────────────────────────┐
│  1. Start Swoole server                             │
│  2. Bootstrap Laravel SEKALI                        │
│  3. Create database connection pool                 │
│  4. Load semua ke memory                            │
│  5. READY untuk ribuan requests!                    │
└─────────────────────────────────────────────────────┘

Request 1, 2, 3, ... 10.000:
┌─────────────────────────────────────────────────────┐
│  Laravel sudah di memory ✓                          │
│  DB connections ready ✓                             │
│  Tinggal process logic saja!                        │
│  Response time: 10-50ms                             │
└─────────────────────────────────────────────────────┘

Dengan analogi rice cooker tadi: Swoole seperti punya rice cooker yang selalu ON dan ready. Mau masak nasi? Tinggal masukkan beras, tidak perlu setup ulang.

Quick Preview: Sebelum vs Sesudah Swoole

Berikut benchmark comparison yang bisa kamu expect:

┌─────────────────────────┬─────────────────┬─────────────────┬─────────────┐
│ Metric                  │ PHP-FPM         │ Swoole          │ Improvement │
├─────────────────────────┼─────────────────┼─────────────────┼─────────────┤
│ Requests/second         │ 500 - 1,500     │ 5,000 - 15,000  │ 10x         │
│ Average Response Time   │ 150 - 300ms     │ 15 - 50ms       │ 6-10x       │
│ Memory per Worker       │ 30 - 50 MB      │ 10 - 20 MB      │ 50-60%      │
│ Max Concurrent Users    │ 500 - 1,000     │ 5,000 - 10,000  │ 10x         │
│ Bootstrap Time          │ Per request     │ Once            │ ∞           │
│ Database Connections    │ Per request     │ Pooled          │ Efficient   │
│ WebSocket Support       │ External needed │ Built-in        │ Native      │
└─────────────────────────┴─────────────────┴─────────────────┴─────────────┘

Apa yang Akan Kamu Pelajari

Dalam artikel ini, kita akan deep dive ke:

  1. Apa itu Swoole — Cara kerja, konsep dasar, perbandingan dengan alternatif
  2. Benefit Utama — 6 keuntungan konkret dengan contoh kode
  3. Pros dan Cons — Fair assessment, kapan pakai dan kapan hindari
  4. Cara Integrasi — Step-by-step setup dengan Laravel 12 untuk movie streaming
  5. Poin Penting — 7 hal yang HARUS diperhatikan (ini yang sering bikin gagal)
  6. Common Mistakes — 8 kesalahan umum dan cara menghindarinya
  7. Production Deployment — Supervisor, Docker, monitoring

Setiap bagian akan disertai code examples yang production-ready — bukan contoh "hello world", tapi implementasi nyata untuk movie streaming platform.

Let's fix that performance problem.

Bagian 2: Apa itu Swoole? Deep Dive

Definisi Swoole

Swoole adalah PHP extension yang ditulis dalam C/C++ yang menyediakan kemampuan asynchronous, concurrent, dan coroutine-based programming untuk PHP.

Dalam bahasa sederhana: Swoole mengubah PHP dari bahasa yang "satu request satu proses" menjadi bahasa yang bisa handle ribuan koneksi dalam satu proses — seperti Node.js atau Go.

SWOOLE AT A GLANCE:

├── Nama: Swoole (发音: /swuːl/)
├── Bahasa: C/C++ extension untuk PHP
├── Lahir: 2012 (production-ready bertahun-tahun)
├── Paradigm: Event-driven, non-blocking I/O
├── Inspirasi: Node.js, Go, Erlang
├── Use Case: High-performance PHP applications
└── License: Apache 2.0 (Open Source)

FITUR UTAMA:
├── Async I/O operations
├── Coroutines (lightweight threads)
├── WebSocket server built-in
├── HTTP/HTTP2 server built-in
├── TCP/UDP server support
├── Connection pooling
├── Timer dan intervals
└── Process management

Cara Kerja: PHP-FPM vs Swoole

Untuk benar-benar memahami Swoole, kita perlu bandingkan dengan PHP-FPM.

PHP-FPM (Traditional):

                         NGINX
                           │
           ┌───────────────┼───────────────┐
           ▼               ▼               ▼
    ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
    │  PHP-FPM    │ │  PHP-FPM    │ │  PHP-FPM    │
    │  Worker 1   │ │  Worker 2   │ │  Worker 3   │
    │             │ │             │ │             │
    │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │
    │ │ Laravel │ │ │ │ Laravel │ │ │ │ Laravel │ │
    │ │Bootstrap│ │ │ │Bootstrap│ │ │ │Bootstrap│ │
    │ │ (300ms) │ │ │ │ (300ms) │ │ │ │ (300ms) │ │
    │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │
    │      │      │ │      │      │ │      │      │
    │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │
    │ │ New DB  │ │ │ │ New DB  │ │ │ │ New DB  │ │
    │ │  Conn   │ │ │ │  Conn   │ │ │ │  Conn   │ │
    │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │
    │             │ │             │ │             │
    │ Memory:     │ │ Memory:     │ │ Memory:     │
    │ 30-50MB     │ │ 30-50MB     │ │ 30-50MB     │
    └─────────────┘ └─────────────┘ └─────────────┘

    LIFECYCLE:
    Request → Start Process → Bootstrap → Query → Response → Kill Process

    CHARACTERISTICS:
    ├── Stateless (shared-nothing)
    ├── Bootstrap SETIAP request
    ├── Database connection SETIAP request
    ├── Memory freed SETIAP request
    └── 1 worker = 1 concurrent request

Swoole (Event-driven):

                     SWOOLE HTTP SERVER
    ┌─────────────────────────────────────────────────────┐
    │                   MASTER PROCESS                     │
    │  ┌───────────────────────────────────────────────┐  │
    │  │              MANAGER PROCESS                   │  │
    │  │                                                │  │
    │  │  ┌──────────┐ ┌──────────┐ ┌──────────┐      │  │
    │  │  │ Worker 1 │ │ Worker 2 │ │ Worker 3 │      │  │
    │  │  │          │ │          │ │          │      │  │
    │  │  │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │      │  │
    │  │  │ │LARAVEL│ │ │ │LARAVEL│ │ │ │LARAVEL│ │      │  │
    │  │  │ │  IN   │ │ │ │  IN   │ │ │ │  IN   │ │      │  │
    │  │  │ │MEMORY!│ │ │ │MEMORY!│ │ │ │MEMORY!│ │      │  │
    │  │  │ └──────┘ │ │ └──────┘ │ │ └──────┘ │      │  │
    │  │  │          │ │          │ │          │      │  │
    │  │  │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │      │  │
    │  │  │ │ DB   │ │ │ │ DB   │ │ │ │ DB   │ │      │  │
    │  │  │ │ Pool │ │ │ │ Pool │ │ │ │ Pool │ │      │  │
    │  │  │ └──────┘ │ │ └──────┘ │ │ └──────┘ │      │  │
    │  │  │          │ │          │ │          │      │  │
    │  │  │ Memory:  │ │ Memory:  │ │ Memory:  │      │  │
    │  │  │ 10-20MB  │ │ 10-20MB  │ │ 10-20MB  │      │  │
    │  │  └──────────┘ └──────────┘ └──────────┘      │  │
    │  └───────────────────────────────────────────────┘  │
    └─────────────────────────────────────────────────────┘

    LIFECYCLE:
    Start Server → Bootstrap ONCE → [Request → Process → Response] × ∞

    CHARACTERISTICS:
    ├── Stateful (application persists in memory)
    ├── Bootstrap SEKALI saat start
    ├── Database connections POOLED dan reused
    ├── Memory persists (hati-hati leak!)
    ├── 1 worker = BANYAK concurrent requests via coroutines
    └── Event loop handles I/O efficiently

Key Concepts Swoole

Ada beberapa konsep penting yang membuat Swoole powerful:

1. COROUTINES (Lightweight Threads)

Coroutine adalah "lightweight thread" yang sangat murah untuk dibuat. Sementara OS thread membutuhkan ~1MB memory, coroutine hanya butuh ~2KB.

<?php
// COROUTINE EXAMPLE

use Swoole\\Coroutine;

// Tanpa coroutine (blocking, sequential)
function fetchSequential() {
    $result1 = file_get_contents('<https://api1.com>'); // 200ms wait
    $result2 = file_get_contents('<https://api2.com>'); // 200ms wait
    $result3 = file_get_contents('<https://api3.com>'); // 200ms wait
    // Total: 600ms
}

// Dengan coroutine (non-blocking, concurrent)
function fetchConcurrent() {
    $results = [];

    Coroutine::create(function() use (&$results) {
        $results['api1'] = file_get_contents('<https://api1.com>');
    });

    Coroutine::create(function() use (&$results) {
        $results['api2'] = file_get_contents('<https://api2.com>');
    });

    Coroutine::create(function() use (&$results) {
        $results['api3'] = file_get_contents('<https://api3.com>');
    });
    // Total: ~200ms (parallel!)
}

// Di Laravel Octane, ini jadi lebih simple:
use Laravel\\Octane\\Facades\\Octane;

[$api1, $api2, $api3] = Octane::concurrently([
    fn() => Http::get('<https://api1.com>'),
    fn() => Http::get('<https://api2.com>'),
    fn() => Http::get('<https://api3.com>'),
]);

Analogi: Bayangkan kamu punya 3 tugas yang masing-masing butuh "menunggu" (misal: menunggu air mendidih, menunggu oven panas, menunggu blender selesai).

  • Sequential (PHP-FPM): Tunggu air mendidih → selesai → tunggu oven panas → selesai → tunggu blender → selesai. Total waktu = jumlah semua waktu tunggu.
  • Concurrent (Swoole): Nyalakan kompor, nyalakan oven, nyalakan blender, lalu tunggu semuanya bersamaan. Total waktu = waktu tunggu terlama saja.

2. EVENT LOOP

Event loop adalah mekanisme yang memungkinkan satu thread menangani banyak operasi I/O secara efisien.

<?php
// SIMPLIFIED EVENT LOOP CONCEPT

// PHP-FPM: Blocking
$data = $database->query("SELECT * FROM movies"); // Thread BLOCKED sampai selesai
// Selama menunggu, thread tidak bisa handle request lain

// Swoole: Non-blocking dengan Event Loop
/*
┌─────────────────────────────────────────────────────┐
│                    EVENT LOOP                        │
│                                                      │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐             │
│  │ Request │  │ DB Wait │  │ File    │             │
│  │    1    │  │   for   │  │  Read   │             │
│  └────┬────┘  │ Req #1  │  │ Req #3  │             │
│       │       └────┬────┘  └────┬────┘             │
│       ▼            │            │                   │
│  Process ──────────┘            │                   │
│       │                         │                   │
│       ▼                         │                   │
│  ┌─────────┐                    │                   │
│  │ Request │◄───────────────────┘                   │
│  │    2    │                                        │
│  └─────────┘                                        │
│                                                      │
│  Event loop terus berputar, tidak pernah blocked    │
│  Saat satu operasi I/O waiting, handle yang lain    │
└─────────────────────────────────────────────────────┘
*/

3. CONNECTION POOLING

Database connections adalah expensive operation. Swoole menyimpan pool of connections yang bisa direuse.

<?php
// CONNECTION POOLING

// PHP-FPM: Setiap request = new connection
// Request 1: Connect → Query → Disconnect (30ms overhead)
// Request 2: Connect → Query → Disconnect (30ms overhead)
// Request 3: Connect → Query → Disconnect (30ms overhead)
// 1000 requests = 1000 × 30ms = 30 detik wasted

// Swoole: Pool of connections
/*
┌─────────────────────────────────────────────┐
│           CONNECTION POOL (10 conns)        │
│  ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐        │
│  │Conn│ │Conn│ │Conn│ │Conn│ │Conn│ ...    │
│  │ 1  │ │ 2  │ │ 3  │ │ 4  │ │ 5  │        │
│  └──┬─┘ └──┬─┘ └──┬─┘ └──┬─┘ └──┬─┘        │
│     │      │      │      │      │           │
│  Request  Req    Req    Req    Req          │
│    1       2      3      4      5           │
│                                              │
│  Connections reused, no connect/disconnect  │
│  overhead per request!                       │
└─────────────────────────────────────────────┘
*/

// Request 1: Ambil conn dari pool → Query → Return conn to pool (0ms overhead)
// Request 2: Ambil conn dari pool → Query → Return conn to pool (0ms overhead)
// 1000 requests = minimal overhead

Swoole vs Alternatives

Swoole bukan satu-satunya solusi. Berikut perbandingan dengan alternatif:

┌─────────────────┬───────────────┬───────────────┬───────────────┐
│ Feature         │ Swoole        │ RoadRunner    │ FrankenPHP    │
├─────────────────┼───────────────┼───────────────┼───────────────┤
│ Written In      │ C/C++         │ Go            │ Go            │
│ Type            │ PHP Extension │ Application   │ Application   │
│ Performance     │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐⭐       │ ⭐⭐⭐⭐       │
│ Coroutines      │ ✅ Built-in   │ ❌ No         │ ❌ No         │
│ WebSocket       │ ✅ Built-in   │ 🔌 Plugin     │ ⚠️ Limited    │
│ HTTP/2          │ ✅ Yes        │ ✅ Yes        │ ✅ Yes        │
│ Learning Curve  │ Steeper       │ Easier        │ Easiest       │
│ Laravel Support │ Octane        │ Octane        │ Octane        │
│ Maturity        │ 12+ years     │ 6+ years      │ 2+ years      │
│ Community       │ Large         │ Growing       │ New           │
│ Windows Support │ ❌ No         │ ✅ Yes        │ ✅ Yes        │
│ Best For        │ Max perf,     │ General use,  │ Quick start,  │
│                 │ WebSockets    │ simplicity    │ Docker        │
└─────────────────┴───────────────┴───────────────┴───────────────┘

Kapan pilih Swoole:

  • Butuh performa maksimal
  • WebSocket adalah requirement utama
  • Concurrent operations heavy
  • Tim siap dengan learning curve

Kapan pilih RoadRunner:

  • Butuh Windows support
  • Prefer simpler mental model
  • Tidak butuh WebSocket built-in
  • Middle ground antara perf dan simplicity

Kapan pilih FrankenPHP:

  • Baru mulai dengan high-perf PHP
  • Prefer Docker-first deployment
  • Butuh quick setup
  • Tidak butuh advanced features

Kenapa Swoole untuk Movie Streaming?

Untuk use case movie streaming, Swoole adalah pilihan ideal karena:

MOVIE STREAMING REQUIREMENTS vs SWOOLE FEATURES:

┌────────────────────────────┬─────────────────────────────────┐
│ Requirement                │ Swoole Solution                 │
├────────────────────────────┼─────────────────────────────────┤
│ Ribuan concurrent viewers  │ Coroutines (lightweight,        │
│                            │ bisa spawn ribuan)              │
├────────────────────────────┼─────────────────────────────────┤
│ Live chat real-time        │ Built-in WebSocket server       │
│                            │ (tidak perlu Pusher/Soketi)     │
├────────────────────────────┼─────────────────────────────────┤
│ Video streaming            │ Async I/O untuk byte-range      │
│                            │ requests tanpa blocking         │
├────────────────────────────┼─────────────────────────────────┤
│ Watch progress sync        │ Event-driven updates,           │
│                            │ efficient untuk frequent writes │
├────────────────────────────┼─────────────────────────────────┤
│ Recommendations loading    │ Concurrent fetching (parallel   │
│                            │ queries untuk movie + reviews   │
│                            │ + cast + recommendations)       │
├────────────────────────────┼─────────────────────────────────┤
│ Real-time viewer count     │ Swoole Table (shared memory)    │
│                            │ untuk atomic counters           │
├────────────────────────────┼─────────────────────────────────┤
│ Cost efficiency            │ Lebih sedikit servers needed    │
│                            │ (handle more dengan less)       │
└────────────────────────────┴─────────────────────────────────┘

Swoole bukan silver bullet, tapi untuk aplikasi dengan karakteristik seperti streaming platform, ia memberikan keuntungan signifikan dibanding PHP tradisional.

Di bagian selanjutnya, kita akan lihat secara detail benefit apa saja yang bisa kamu dapatkan — lengkap dengan contoh kode.

Bagian 3: Benefit Utama Swoole untuk Laravel

Sekarang kita masuk ke bagian yang paling menarik — apa yang sebenarnya kamu dapatkan dari Swoole? Berikut 6 benefit utama dengan contoh kode konkret untuk movie streaming platform.

BENEFIT #1: 10x Performance Improvement

Ini bukan exaggeration. Benchmark menunjukkan peningkatan signifikan:

BENCHMARK RESULTS (Laravel 11 Application):

Test Environment:
├── Server: 4 CPU, 8GB RAM
├── Database: MySQL 8
├── Test Tool: wrk (HTTP benchmarking)
└── Duration: 30 seconds per test

┌───────────────────────────┬──────────────┬──────────────┬─────────────┐
│ Test Scenario             │ PHP-FPM      │ Swoole       │ Improvement │
├───────────────────────────┼──────────────┼──────────────┼─────────────┤
│ Simple JSON response      │ 1,200 req/s  │ 12,500 req/s │ 10.4x       │
│ Single DB query           │ 850 req/s    │ 8,200 req/s  │ 9.6x        │
│ Complex query (5 joins)   │ 320 req/s    │ 3,100 req/s  │ 9.7x        │
│ API with auth middleware  │ 480 req/s    │ 5,800 req/s  │ 12.1x       │
│ File read (1MB)           │ 180 req/s    │ 2,400 req/s  │ 13.3x       │
└───────────────────────────┴──────────────┴──────────────┴─────────────┘

Response Time (p99):
├── PHP-FPM: 180-350ms
└── Swoole: 15-45ms

Kenapa bisa secepat ini?

<?php
// PHP-FPM: Setiap request
// Total overhead: 100-300ms SEBELUM logic kamu berjalan

// Request masuk:
// 1. Nginx terima request
// 2. Forward ke PHP-FPM
// 3. PHP-FPM spawn/assign worker
// 4. Load vendor/autoload.php (10-30ms)
// 5. Create Application instance
// 6. Load semua config files (20-50ms)
// 7. Register 50+ service providers (30-80ms)
// 8. Boot service providers (20-50ms)
// 9. Resolve route
// 10. Run middleware stack
// 11. BARU jalankan controller logic kamu
// 12. Response
// 13. Cleanup dan kill process

// Swoole: Setelah server start
// Total overhead: ~1-5ms

// Server sudah running dengan:
// ✓ Autoloader loaded
// ✓ Application bootstrapped
// ✓ Config cached in memory
// ✓ Service providers booted
// ✓ Routes compiled
// ✓ DB connections ready di pool

// Request masuk:
// 1. Swoole terima request (langsung, no Nginx needed)
// 2. Resolve route (dari memory)
// 3. Run middleware (minimal overhead)
// 4. Jalankan controller logic
// 5. Response
// (No cleanup, app tetap di memory)

BENEFIT #2: Concurrent Task Execution

Ini game-changer untuk aplikasi yang butuh fetch data dari multiple sources.

Skenario Movie Streaming: User buka halaman detail film. Kamu perlu load:

  • Movie details
  • Recommendations (10 similar movies)
  • Reviews (50 reviews)
  • Cast & crew
  • User's watch history
  • Subscription status
<?php
// ❌ TRADITIONAL: Sequential (LAMBAT)
// app/Http/Controllers/MovieController.php

class MovieController extends Controller
{
    public function show(Movie $movie)
    {
        // Setiap query harus selesai sebelum yang berikutnya
        $recommendations = $this->movieService->getRecommendations($movie); // 80ms
        $reviews = $movie->reviews()->with('user')->latest()->limit(50)->get(); // 60ms
        $cast = $movie->cast()->with('actor')->get(); // 40ms
        $watchHistory = auth()->user()?->watchHistory()
            ->where('movie_id', $movie->id)->first(); // 30ms
        $subscription = auth()->user()?->activeSubscription(); // 25ms

        // Total: 80 + 60 + 40 + 30 + 25 = 235ms (sequential)

        return response()->json([
            'movie' => $movie,
            'recommendations' => $recommendations,
            'reviews' => $reviews,
            'cast' => $cast,
            'watch_history' => $watchHistory,
            'subscription' => $subscription,
        ]);
    }
}

<?php
// ✅ SWOOLE + OCTANE: Concurrent (CEPAT)
// app/Http/Controllers/MovieController.php

use Laravel\\Octane\\Facades\\Octane;

class MovieController extends Controller
{
    public function show(Movie $movie)
    {
        // Semua queries berjalan PARALLEL
        [
            $recommendations,
            $reviews,
            $cast,
            $watchHistory,
            $subscription
        ] = Octane::concurrently([
            fn() => $this->movieService->getRecommendations($movie),
            fn() => $movie->reviews()->with('user')->latest()->limit(50)->get(),
            fn() => $movie->cast()->with('actor')->get(),
            fn() => auth()->user()?->watchHistory()->where('movie_id', $movie->id)->first(),
            fn() => auth()->user()?->activeSubscription(),
        ]);

        // Total: max(80, 60, 40, 30, 25) = ~80ms (parallel)
        // Savings: 235ms - 80ms = 155ms (66% faster!)

        return response()->json([
            'movie' => $movie,
            'recommendations' => $recommendations,
            'reviews' => $reviews,
            'cast' => $cast,
            'watch_history' => $watchHistory,
            'subscription' => $subscription,
        ]);
    }
}

Real-world impact:

PAGE LOAD TIME COMPARISON:

┌─────────────────────────┬────────────────┬────────────────┐
│ Page                    │ Sequential     │ Concurrent     │
├─────────────────────────┼────────────────┼────────────────┤
│ Movie Detail            │ 235ms          │ 80ms           │
│ Homepage (10 sections)  │ 450ms          │ 120ms          │
│ Search Results          │ 180ms          │ 60ms           │
│ User Dashboard          │ 320ms          │ 95ms           │
└─────────────────────────┴────────────────┴────────────────┘

BENEFIT #3: Built-in WebSocket Server

Untuk movie streaming, real-time features sangat penting. Swoole menyediakan WebSocket server built-in — tidak perlu Pusher, Soketi, atau service external.

<?php
// app/WebSocket/MovieChatHandler.php

use Swoole\\WebSocket\\Server;
use Swoole\\WebSocket\\Frame;

class MovieChatHandler
{
    // Rooms: movieId => [fd => userInfo]
    private array $rooms = [];

    // Viewer counts per movie
    private array $viewerCounts = [];

    /**
     * Handle new WebSocket connection
     */
    public function onOpen(Server $server, $request): void
    {
        $movieId = $request->get['movie_id'] ?? null;
        $userId = $request->get['user_id'] ?? null;
        $userName = $request->get['user_name'] ?? 'Anonymous';

        if (!$movieId) {
            $server->close($request->fd);
            return;
        }

        // Add user to room
        $this->rooms[$movieId][$request->fd] = [
            'fd' => $request->fd,
            'user_id' => $userId,
            'user_name' => $userName,
            'joined_at' => time(),
        ];

        // Update viewer count
        $this->viewerCounts[$movieId] = count($this->rooms[$movieId]);

        // Broadcast user joined
        $this->broadcastToRoom($server, $movieId, [
            'type' => 'user_joined',
            'user_name' => $userName,
            'viewer_count' => $this->viewerCounts[$movieId],
            'timestamp' => time(),
        ], excludeFd: $request->fd);

        // Send current state to new user
        $server->push($request->fd, json_encode([
            'type' => 'welcome',
            'viewer_count' => $this->viewerCounts[$movieId],
            'message' => "Welcome to the chat! {$this->viewerCounts[$movieId]} viewers watching.",
        ]));
    }

    /**
     * Handle incoming messages
     */
    public function onMessage(Server $server, Frame $frame): void
    {
        $data = json_decode($frame->data, true);

        if (!$data || !isset($data['movie_id'])) {
            return;
        }

        $movieId = $data['movie_id'];
        $user = $this->rooms[$movieId][$frame->fd] ?? null;

        if (!$user) {
            return;
        }

        switch ($data['type'] ?? 'chat') {
            case 'chat':
                $this->handleChatMessage($server, $movieId, $user, $data);
                break;

            case 'reaction':
                $this->handleReaction($server, $movieId, $user, $data);
                break;

            case 'watch_progress':
                $this->handleWatchProgress($server, $movieId, $user, $data);
                break;
        }
    }

    /**
     * Handle chat message
     */
    private function handleChatMessage(Server $server, string $movieId, array $user, array $data): void
    {
        // Basic moderation - filter bad words
        $message = $this->filterMessage($data['message'] ?? '');

        if (empty($message)) {
            return;
        }

        $this->broadcastToRoom($server, $movieId, [
            'type' => 'chat_message',
            'user_id' => $user['user_id'],
            'user_name' => $user['user_name'],
            'message' => $message,
            'timestamp' => time(),
        ]);
    }

    /**
     * Handle emoji reactions (like Netflix reactions)
     */
    private function handleReaction(Server $server, string $movieId, array $user, array $data): void
    {
        $reaction = $data['reaction'] ?? null;
        $allowedReactions = ['😂', '😮', '😢', '😍', '🔥', '👏'];

        if (!in_array($reaction, $allowedReactions)) {
            return;
        }

        $this->broadcastToRoom($server, $movieId, [
            'type' => 'reaction',
            'user_name' => $user['user_name'],
            'reaction' => $reaction,
            'timestamp' => time(),
        ]);
    }

    /**
     * Handle connection close
     */
    public function onClose(Server $server, int $fd): void
    {
        foreach ($this->rooms as $movieId => &$users) {
            if (isset($users[$fd])) {
                $userName = $users[$fd]['user_name'];
                unset($users[$fd]);

                $this->viewerCounts[$movieId] = count($users);

                // Broadcast user left
                $this->broadcastToRoom($server, $movieId, [
                    'type' => 'user_left',
                    'user_name' => $userName,
                    'viewer_count' => $this->viewerCounts[$movieId],
                    'timestamp' => time(),
                ]);

                // Cleanup empty rooms
                if (empty($users)) {
                    unset($this->rooms[$movieId]);
                    unset($this->viewerCounts[$movieId]);
                }

                break;
            }
        }
    }

    /**
     * Broadcast message to all users in a room
     */
    private function broadcastToRoom(Server $server, string $movieId, array $message, ?int $excludeFd = null): void
    {
        $encoded = json_encode($message);

        foreach ($this->rooms[$movieId] ?? [] as $fd => $user) {
            if ($fd !== $excludeFd && $server->isEstablished($fd)) {
                $server->push($fd, $encoded);
            }
        }
    }

    /**
     * Simple message filter
     */
    private function filterMessage(string $message): string
    {
        $message = trim($message);
        $message = substr($message, 0, 500); // Max length
        $message = htmlspecialchars($message);

        // Add bad words filter here if needed

        return $message;
    }
}

Frontend integration:

// resources/js/movie-chat.js

class MovieChat {
    constructor(movieId, userId, userName) {
        this.movieId = movieId;
        this.userId = userId;
        this.userName = userName;
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
    }

    connect() {
        const wsUrl = `wss://your-domain.com/ws?movie_id=${this.movieId}&user_id=${this.userId}&user_name=${encodeURIComponent(this.userName)}`;

        this.ws = new WebSocket(wsUrl);

        this.ws.onopen = () => {
            console.log('Connected to chat');
            this.reconnectAttempts = 0;
        };

        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleMessage(data);
        };

        this.ws.onclose = () => {
            console.log('Disconnected from chat');
            this.attemptReconnect();
        };
    }

    handleMessage(data) {
        switch (data.type) {
            case 'welcome':
                this.updateViewerCount(data.viewer_count);
                break;
            case 'chat_message':
                this.displayMessage(data);
                break;
            case 'user_joined':
            case 'user_left':
                this.updateViewerCount(data.viewer_count);
                this.displaySystemMessage(data);
                break;
            case 'reaction':
                this.showReaction(data);
                break;
        }
    }

    sendMessage(message) {
        if (this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify({
                type: 'chat',
                movie_id: this.movieId,
                message: message
            }));
        }
    }

    sendReaction(emoji) {
        if (this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify({
                type: 'reaction',
                movie_id: this.movieId,
                reaction: emoji
            }));
        }
    }
}

BENEFIT #4: Reduced Memory Usage

Swoole menggunakan memory lebih efisien:

MEMORY COMPARISON (100 concurrent users):

PHP-FPM:
├── 100 workers × 40MB average = 4,000 MB (4 GB)
├── Each worker: full Laravel instance
├── Each worker: separate DB connection
└── Memory freed setiap request = GC overhead

Swoole:
├── 4 workers × 50MB (dengan app in memory) = 200 MB
├── Shared application state
├── Connection pooling (10 connections shared)
└── Memory persists = no GC overhead per request

SAVINGS: 4,000 MB - 200 MB = 3,800 MB (95% reduction!)

<?php
// config/octane.php - Optimize memory usage

return [
    'swoole' => [
        'options' => [
            // Number of workers (biasanya = CPU cores)
            'worker_num' => env('OCTANE_WORKERS', 4),

            // Task workers untuk background jobs
            'task_worker_num' => env('OCTANE_TASK_WORKERS', 4),

            // Restart worker setelah X requests (prevent memory leaks)
            'max_request' => 10000,

            // Garbage collection tiap X requests
            'max_request_grace' => 100,
        ],
    ],
];

BENEFIT #5: Swoole Tables (Shared Memory)

Swoole Tables adalah high-performance shared memory yang bisa diakses oleh semua workers. Perfect untuk real-time data seperti viewer counts.

<?php
// config/octane.php

return [
    'tables' => [
        // Real-time viewer count per movie
        'movie_viewers' => [
            'columns' => [
                ['name' => 'count', 'type' => 'int'],
                ['name' => 'last_updated', 'type' => 'int'],
            ],
            'rows' => 10000, // Support 10K movies concurrent
        ],

        // Active user sessions
        'active_sessions' => [
            'columns' => [
                ['name' => 'user_id', 'type' => 'int'],
                ['name' => 'movie_id', 'type' => 'int'],
                ['name' => 'started_at', 'type' => 'int'],
            ],
            'rows' => 50000, // Support 50K concurrent sessions
        ],
    ],
];

<?php
// app/Services/ViewerCountService.php

use Laravel\\Octane\\Facades\\Octane;

class ViewerCountService
{
    /**
     * Increment viewer count untuk movie
     */
    public function incrementViewer(int $movieId): int
    {
        $table = Octane::table('movie_viewers');

        $current = $table->get("movie_{$movieId}");
        $newCount = ($current['count'] ?? 0) + 1;

        $table->set("movie_{$movieId}", [
            'count' => $newCount,
            'last_updated' => time(),
        ]);

        return $newCount;
    }

    /**
     * Decrement viewer count
     */
    public function decrementViewer(int $movieId): int
    {
        $table = Octane::table('movie_viewers');

        $current = $table->get("movie_{$movieId}");
        $newCount = max(0, ($current['count'] ?? 0) - 1);

        $table->set("movie_{$movieId}", [
            'count' => $newCount,
            'last_updated' => time(),
        ]);

        return $newCount;
    }

    /**
     * Get current viewer count
     */
    public function getViewerCount(int $movieId): int
    {
        $table = Octane::table('movie_viewers');
        $data = $table->get("movie_{$movieId}");

        return $data['count'] ?? 0;
    }

    /**
     * Get top movies by viewer count
     */
    public function getTopMovies(int $limit = 10): array
    {
        $table = Octane::table('movie_viewers');
        $movies = [];

        foreach ($table as $key => $value) {
            if (str_starts_with($key, 'movie_')) {
                $movieId = (int) str_replace('movie_', '', $key);
                $movies[$movieId] = $value['count'];
            }
        }

        arsort($movies);
        return array_slice($movies, 0, $limit, true);
    }
}

BENEFIT #6: Background Tasks dengan Ticks

Swoole memungkinkan running background tasks tanpa queue system:

<?php
// app/Providers/OctaneServiceProvider.php

use Laravel\\Octane\\Facades\\Octane;

class OctaneServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Update trending movies setiap 5 menit
        Octane::tick('update-trending', function () {
            $this->updateTrendingMovies();
        })->seconds(300);

        // Cleanup expired sessions setiap 1 menit
        Octane::tick('cleanup-sessions', function () {
            $this->cleanupExpiredSessions();
        })->seconds(60);

        // Sync viewer counts ke database setiap 30 detik
        Octane::tick('sync-viewer-counts', function () {
            $this->syncViewerCountsToDatabase();
        })->seconds(30);

        // Health check setiap 10 detik
        Octane::tick('health-check', function () {
            $this->performHealthCheck();
        })->seconds(10);
    }

    private function updateTrendingMovies(): void
    {
        $viewerService = app(ViewerCountService::class);
        $topMovies = $viewerService->getTopMovies(20);

        // Cache trending movies
        Cache::put('trending_movies', $topMovies, now()->addMinutes(10));

        Log::info('Trending movies updated', ['count' => count($topMovies)]);
    }

    private function cleanupExpiredSessions(): void
    {
        $table = Octane::table('active_sessions');
        $expiredThreshold = time() - 3600; // 1 hour
        $cleaned = 0;

        foreach ($table as $key => $session) {
            if ($session['started_at'] < $expiredThreshold) {
                $table->del($key);
                $cleaned++;
            }
        }

        if ($cleaned > 0) {
            Log::info('Cleaned expired sessions', ['count' => $cleaned]);
        }
    }

    private function syncViewerCountsToDatabase(): void
    {
        $table = Octane::table('movie_viewers');
        $updates = [];

        foreach ($table as $key => $value) {
            if (str_starts_with($key, 'movie_')) {
                $movieId = (int) str_replace('movie_', '', $key);
                $updates[$movieId] = $value['count'];
            }
        }

        if (!empty($updates)) {
            // Bulk update untuk efficiency
            foreach ($updates as $movieId => $count) {
                Movie::where('id', $movieId)->update([
                    'current_viewers' => $count,
                ]);
            }
        }
    }
}

Summary: Benefits Overview

┌────────────────────────────┬─────────────────────────────────────────┐
│ Benefit                    │ Impact untuk Movie Streaming            │
├────────────────────────────┼─────────────────────────────────────────┤
│ 10x Performance            │ Handle 10K+ concurrent viewers          │
│                            │ Response time < 50ms                    │
├────────────────────────────┼─────────────────────────────────────────┤
│ Concurrent Execution       │ Page load 66% faster                    │
│                            │ Parallel data fetching                  │
├────────────────────────────┼─────────────────────────────────────────┤
│ Built-in WebSocket         │ Live chat tanpa Pusher/external service │
│                            │ Real-time viewer counts                 │
├────────────────────────────┼─────────────────────────────────────────┤
│ Memory Efficiency          │ 95% less memory                         │
│                            │ Fewer servers needed                    │
├────────────────────────────┼─────────────────────────────────────────┤
│ Swoole Tables              │ Ultra-fast shared state                 │
│                            │ Real-time analytics                     │
├────────────────────────────┼─────────────────────────────────────────┤
│ Background Ticks           │ No queue overhead untuk simple tasks    │
│                            │ Periodic updates built-in               │
└────────────────────────────┴─────────────────────────────────────────┘

Dengan semua benefit ini, aplikasi movie streaming kamu bisa handle traffic yang jauh lebih besar dengan infrastructure yang lebih kecil.

Tapi... ada trade-off. Di bagian selanjutnya, kita bahas Pros dan Cons secara fair.

Bagian 4: Pros dan Cons Swoole — Fair Assessment

Setelah melihat benefit-nya, kita perlu jujur tentang trade-offs. Swoole bukan magic solution — ada situasi di mana ia sangat berguna, dan ada situasi di mana ia overkill atau bahkan problematic.

PROS (Kelebihan)

✅ PRO #1: Exceptional Performance

PERFORMANCE GAINS:

├── 10x faster response times
│   └── Dari 200ms → 20ms untuk typical API endpoint
│
├── 10x higher throughput
│   └── Dari 1,000 req/s → 10,000 req/s dengan hardware sama
│
├── Lower latency (p99)
│   └── Dari 350ms → 45ms di high load
│
└── Better under pressure
    └── Degradasi graceful, bukan cliff drop

Ini bukan marketing speak — benchmark numbers yang bisa diverifikasi.

✅ PRO #2: Rich Built-in Features

FEATURES INCLUDED:

├── WebSocket Server
│   ├── Full duplex communication
│   ├── Broadcast capability
│   ├── Room/channel management
│   └── Tidak perlu Pusher ($) atau Soketi (setup)
│
├── HTTP/HTTP2 Server
│   ├── Tidak perlu Nginx untuk simple setups
│   ├── Built-in static file serving
│   └── SSL/TLS support
│
├── Coroutines
│   ├── Concurrent operations native
│   ├── Async I/O tanpa callback hell
│   └── Channel-based communication
│
├── Timer & Intervals
│   ├── Background tasks tanpa cron
│   ├── Periodic cleanup
│   └── Real-time updates
│
└── Swoole Tables
    ├── Shared memory antar workers
    ├── Atomic operations
    └── Ultra-fast data sharing

✅ PRO #3: Significant Cost Reduction

INFRASTRUCTURE COST COMPARISON:
(Untuk handle 5,000 concurrent users)

PHP-FPM Setup:
├── 4x Web Servers (4 CPU, 8GB each) = $160/month
├── Queue Workers (2 CPU, 4GB) = $40/month
├── Load Balancer = $20/month
├── Pusher (for WebSocket) = $49/month
└── Total: ~$270/month

Swoole Setup:
├── 2x Servers (4 CPU, 8GB each) = $80/month
├── Load Balancer = $20/month
├── (WebSocket built-in) = $0
└── Total: ~$100/month

MONTHLY SAVINGS: $170 (63% reduction)
ANNUAL SAVINGS: $2,040

✅ PRO #4: Great Laravel Integration via Octane

<?php
// Octane membuat Swoole terasa "Laravel-native"

// Familiar syntax untuk concurrent operations
[$users, $movies] = Octane::concurrently([
    fn() => User::active()->get(),
    fn() => Movie::trending()->get(),
]);

// Easy configuration
// config/octane.php - semua opsi documented

// Artisan commands yang familiar
// php artisan octane:start
// php artisan octane:reload
// php artisan octane:status

// Hot reload untuk development
// php artisan octane:start --watch

✅ PRO #5: Native Real-time Capabilities

Untuk aplikasi yang butuh real-time features, Swoole adalah natural fit:

REAL-TIME USE CASES:

├── Live notifications
│   └── Instant, tanpa polling
│
├── Chat applications
│   └── Low latency, scalable
│
├── Live dashboards
│   └── Real-time metrics updates
│
├── Collaborative features
│   └── Multi-user editing
│
├── Gaming backends
│   └── State synchronization
│
└── Streaming platforms (our use case!)
    ├── Viewer counts
    ├── Live chat
    └── Watch party sync


CONS (Kekurangan)

❌ CON #1: Steeper Learning Curve

NEW CONCEPTS TO LEARN:

├── Stateful vs Stateless mindset
│   ├── Variables persist antar requests
│   ├── Memory management penting
│   └── Different debugging approach
│
├── Coroutine programming
│   ├── Concurrent execution patterns
│   ├── Channel communication
│   └── Deadlock awareness
│
├── Memory leak prevention
│   ├── Accumulating arrays
│   ├── Static property misuse
│   └── Circular references
│
└── Different testing strategies
    ├── Stateful test isolation
    └── Concurrent test scenarios

Ini bukan sesuatu yang bisa dipelajari dalam sehari. Tim perlu invest waktu untuk benar-benar memahami paradigm shift ini.

❌ CON #2: Compatibility Issues dengan Some Packages

PACKAGES YANG BISA PROBLEMATIC:

⚠️ HIGH RISK:
├── Laravel Debugbar
│   └── Memory leak di production
│
├── Packages dengan static state
│   └── State bleeding antar requests
│
├── Packages yang assume fresh process
│   └── Initialization issues
│
└── Some payment gateways
    └── Singleton connection issues

⚠️ NEED TESTING:
├── Laravel Telescope
├── Spatie packages (mostly OK, tapi test)
├── Third-party API SDKs
└── Image processing libraries

✅ GENERALLY SAFE:
├── Laravel core packages
├── Most Eloquent operations
├── Laravel Sanctum/Passport
└── Laravel Queue (work as backup)

<?php
// Contoh masalah: Package dengan static property

class ProblematicPackage
{
    private static array $cache = [];

    public static function process($data)
    {
        // Di Swoole, $cache TIDAK di-reset antar requests
        // Memory akan terus grow!
        self::$cache[] = $data;

        return self::doSomething($data);
    }
}

// Solusi: Pastikan reset atau jangan gunakan package ini

❌ CON #3: Development Workflow Changes

DEVELOPMENT DIFFERENCES:

Traditional PHP:
├── Edit file → Refresh browser → See changes
├── Simple, instant feedback
└── No server restart needed

Swoole:
├── Edit file → Restart server → See changes
│   (atau gunakan --watch flag)
├── Hot reload not always perfect
├── Some changes need full restart
└── More complex debugging

# Development dengan Swoole

# Option 1: Manual restart setiap perubahan (tedious)
php artisan octane:stop
php artisan octane:start

# Option 2: Watch mode (recommended)
php artisan octane:start --watch
# Tapi kadang masih perlu manual restart untuk:
# - Config changes
# - Service provider changes
# - Middleware changes
# - Route changes

# Option 3: Gunakan PHP-FPM untuk development, Swoole untuk production
# (ini yang banyak tim lakukan)

❌ CON #4: Limited Hosting Options

HOSTING COMPATIBILITY:

❌ NOT SUPPORTED:
├── Shared hosting (cPanel, etc)
├── Traditional PHP hosting
├── Most managed WordPress hosts
└── Platform tanpa root access

✅ SUPPORTED:
├── VPS (DigitalOcean, Linode, Vultr)
├── Cloud (AWS EC2, GCP, Azure)
├── Containers (Docker, Kubernetes)
├── Laravel Forge
├── Laravel Vapor (limited)
└── Self-managed servers

Jika tim kamu terbiasa dengan shared hosting, ini adalah barrier significant.

❌ CON #5: More Complex Debugging

DEBUGGING CHALLENGES:

Traditional PHP:
├── Request fails → Check logs → Stack trace jelas
├── Memory issue → Not really (freed setiap request)
├── State issue → Not really (fresh setiap request)
└── Tools: Xdebug, Laravel Telescope, dd()

Swoole:
├── Request fails → Which worker? Which request?
├── Memory leak → Perlu profiling khusus
├── State bleeding → Hard to reproduce
├── Coroutine issues → Complex stack traces
└── Tools: Swoole Tracker, custom logging, profilers

<?php
// Debugging tip: Extensive logging

class MovieController extends Controller
{
    public function show(Movie $movie)
    {
        $requestId = uniqid('req_');
        $workerId = getenv('SWOOLE_WORKER_ID') ?: 'unknown';

        Log::info("[$requestId] Request started", [
            'worker_id' => $workerId,
            'movie_id' => $movie->id,
            'memory_usage' => memory_get_usage(true),
        ]);

        try {
            $result = $this->processMovie($movie);

            Log::info("[$requestId] Request completed", [
                'worker_id' => $workerId,
                'memory_usage' => memory_get_usage(true),
            ]);

            return $result;

        } catch (\\Exception $e) {
            Log::error("[$requestId] Request failed", [
                'worker_id' => $workerId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }
}

❌ CON #6: Windows Not Supported

PLATFORM SUPPORT:

✅ Linux (semua distro major)
✅ macOS (development OK)
⚠️ WSL2 (Windows Subsystem for Linux) - workaround
❌ Windows native - NOT SUPPORTED

Ini bisa jadi blocker jika:
├── Tim development pakai Windows
├── CI/CD di Windows
└── Deployment target Windows Server


Decision Matrix: Kapan Use dan Kapan Avoid

┌─────────────────────────────────────────────────────────────────────┐
│                    SWOOLE DECISION MATRIX                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ✅ GUNAKAN SWOOLE JIKA:                                            │
│  ──────────────────────                                             │
│  ├── Traffic tinggi (> 1,000 req/sec expected)                      │
│  ├── Real-time features adalah core requirement                     │
│  ├── WebSocket/live chat/notifications needed                       │
│  ├── Performance adalah critical business metric                    │
│  ├── Infrastructure cost perlu dioptimize                          │
│  ├── Tim siap invest waktu learning                                │
│  ├── Deployment ke VPS/Cloud/Containers                            │
│  └── Concurrent data fetching akan bermanfaat                      │
│                                                                      │
│  ❌ HINDARI SWOOLE JIKA:                                            │
│  ─────────────────────                                              │
│  ├── Simple CRUD application                                        │
│  ├── Low traffic (< 100 req/sec)                                   │
│  ├── Tim belum experienced dengan PHP advanced                      │
│  ├── Banyak packages yang belum ditest compatibility               │
│  ├── Shared hosting adalah requirement                              │
│  ├── Windows-only development environment                          │
│  ├── Tight deadline tanpa waktu learning                           │
│  └── MVP/Prototype yang butuh speed of development                 │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Star Rating Comparison

┌─────────────────────────┬───────────────┬───────────────┐
│ Aspect                  │ PHP-FPM       │ Swoole        │
├─────────────────────────┼───────────────┼───────────────┤
│ Raw Performance         │ ⭐⭐          │ ⭐⭐⭐⭐⭐     │
│ Ease of Learning        │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐        │
│ Package Compatibility   │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐        │
│ WebSocket Support       │ ⭐ (external) │ ⭐⭐⭐⭐⭐     │
│ Memory Efficiency       │ ⭐⭐⭐        │ ⭐⭐⭐⭐⭐     │
│ Debugging Experience    │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐        │
│ Hosting Flexibility     │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐        │
│ Real-time Features      │ ⭐⭐          │ ⭐⭐⭐⭐⭐     │
│ Development Speed       │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐⭐      │
│ Concurrent Operations   │ ⭐⭐          │ ⭐⭐⭐⭐⭐     │
│ Cost Efficiency         │ ⭐⭐⭐        │ ⭐⭐⭐⭐⭐     │
│ Community/Resources     │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐⭐      │
├─────────────────────────┼───────────────┼───────────────┤
│ OVERALL                 │ ⭐⭐⭐⭐      │ ⭐⭐⭐⭐      │
└─────────────────────────┴───────────────┴───────────────┘

Both are excellent - pilih berdasarkan USE CASE, bukan "mana yang lebih bagus"

Untuk Movie Streaming: Verdict

Untuk use case movie streaming platform yang kita bahas:

MOVIE STREAMING REQUIREMENTS CHECK:

✓ Ribuan concurrent viewers           → Swoole: PERFECT
✓ Real-time chat                      → Swoole: BUILT-IN
✓ Live viewer counts                  → Swoole Tables: IDEAL
✓ Video streaming (byte-range)        → Swoole: EFFICIENT
✓ Recommendations loading             → Octane::concurrently: GREAT
✓ Watch progress sync                 → Swoole: LOW LATENCY
✓ Cost efficiency                     → Swoole: SIGNIFICANT SAVINGS

VERDICT: Swoole adalah EXCELLENT CHOICE untuk movie streaming platform

Dengan pemahaman pros dan cons ini, kamu bisa membuat keputusan yang informed. Swoole bukan untuk semua aplikasi, tapi untuk aplikasi dengan karakteristik seperti streaming platform, keuntungannya sangat signifikan.

Di bagian selanjutnya, kita akan masuk ke hands-on: cara integrasi step-by-step dengan Laravel 12.

Bagian 5: Cara Integrasi Swoole dengan Laravel 12 — Step by Step

Sekarang kita masuk ke bagian hands-on. Kita akan setup Swoole + Laravel Octane untuk movie streaming platform dari nol.

Prerequisites

Sebelum mulai, pastikan environment kamu sudah siap:

REQUIREMENTS:

├── PHP 8.2+ (recommended: PHP 8.3)
├── Laravel 11 atau 12
├── Composer 2.x
├── Linux atau macOS (Windows via WSL2)
├── MySQL/PostgreSQL
├── Redis (recommended untuk cache/session)
└── Minimal 2GB RAM untuk development

Step 1: Install Swoole Extension

Option A: Via PECL (Recommended untuk production)

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y php8.3-dev php-pear

# Install Swoole dengan semua features
sudo pecl install swoole

# Saat ditanya options, jawab:
# enable sockets supports? [no] : yes
# enable openssl support? [no] : yes
# enable http2 support? [no] : yes
# enable curl support? [no] : yes
# enable postgres support? [no] : no (atau yes jika pakai PostgreSQL)

# Enable extension
echo "extension=swoole.so" | sudo tee /etc/php/8.3/cli/conf.d/99-swoole.ini

# Verify installation
php -m | grep swoole
php --ri swoole

Option B: Via Docker (Recommended untuk development consistency)

# Dockerfile
FROM php:8.3-cli

# Install dependencies
RUN apt-get update && apt-get install -y \\
    libpq-dev \\
    libzip-dev \\
    libcurl4-openssl-dev \\
    libssl-dev \\
    unzip \\
    git \\
    && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install pdo pdo_mysql pdo_pgsql zip pcntl

# Install Swoole
RUN pecl install swoole && docker-php-ext-enable swoole

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis

WORKDIR /var/www/html

# Verify Swoole
RUN php -m | grep swoole

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/var/www/html
    depends_on:
      - mysql
      - redis
    command: php artisan octane:start --host=0.0.0.0 --port=8000
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis

  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      MYSQL_DATABASE: movie_streaming
      MYSQL_ROOT_PASSWORD: secret
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"

volumes:
  mysql_data:

Step 2: Create Laravel Project & Install Octane

# Create new Laravel project
composer create-project laravel/laravel movie-streaming
cd movie-streaming

# Install Laravel Octane
composer require laravel/octane

# Install Octane dengan Swoole
php artisan octane:install

# Pilih: [0] swoole
# Output: Octane scaffolding installed successfully.

Octane akan generate:

  • config/octane.php — Configuration file
  • Update composer.json dengan Swoole suggestion

Step 3: Configure Environment

# .env

APP_NAME="Movie Streaming"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8000

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=movie_streaming
DB_USERNAME=root
DB_PASSWORD=secret

# PENTING: Gunakan Redis untuk session dan cache
SESSION_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

# Octane Configuration
OCTANE_SERVER=swoole
OCTANE_HTTPS=false
OCTANE_WORKERS=4
OCTANE_TASK_WORKERS=4
OCTANE_MAX_REQUESTS=10000

Step 4: Configure Octane untuk Movie Streaming

<?php
// config/octane.php

return [
    /*
    |--------------------------------------------------------------------------
    | Octane Server
    |--------------------------------------------------------------------------
    */
    'server' => env('OCTANE_SERVER', 'swoole'),

    /*
    |--------------------------------------------------------------------------
    | Force HTTPS
    |--------------------------------------------------------------------------
    */
    'https' => env('OCTANE_HTTPS', false),

    /*
    |--------------------------------------------------------------------------
    | Octane Listeners
    |--------------------------------------------------------------------------
    */
    'listeners' => [
        WorkerStarting::class => [
            EnsureUploadedFilesAreValid::class,
            EnsureUploadedFilesCanBeMoved::class,
        ],

        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
        ],

        RequestHandled::class => [
            // Custom logging jika perlu
        ],

        RequestTerminated::class => [
            // FlushTemporaryContainerInstances::class,
        ],

        TaskReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
        ],

        TickReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
        ],

        OperationTerminated::class => [
            FlushOnce::class,
            // FlushTemporaryContainerInstances::class,
        ],

        WorkerErrorOccurred::class => [
            ReportException::class,
            StopWorkerIfNecessary::class,
        ],

        WorkerStopping::class => [
            // Cleanup tasks
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Warm / Flush
    |--------------------------------------------------------------------------
    */

    // Services yang di-resolve saat worker start (preload)
    'warm' => [
        ...Octane::defaultServicesToWarm(),
        // Custom services untuk movie streaming
        // App\\Services\\MovieService::class,
        // App\\Services\\StreamingService::class,
    ],

    // Services yang di-flush setiap request
    'flush' => [
        // Tambahkan services yang perlu fresh setiap request
    ],

    /*
    |--------------------------------------------------------------------------
    | Octane Cache
    |--------------------------------------------------------------------------
    */
    'cache' => [
        'rows' => 1000,
        'bytes' => 10000,
    ],

    /*
    |--------------------------------------------------------------------------
    | Swoole Tables
    |--------------------------------------------------------------------------
    */
    'tables' => [
        // Real-time viewer count per movie
        'movie_viewers' => [
            'columns' => [
                ['name' => 'count', 'type' => 'int'],
                ['name' => 'updated_at', 'type' => 'int'],
            ],
            'rows' => 10000,
        ],

        // Active streaming sessions
        'streaming_sessions' => [
            'columns' => [
                ['name' => 'user_id', 'type' => 'int'],
                ['name' => 'movie_id', 'type' => 'int'],
                ['name' => 'progress', 'type' => 'int'],
                ['name' => 'started_at', 'type' => 'int'],
            ],
            'rows' => 50000,
        ],

        // Online users
        'online_users' => [
            'columns' => [
                ['name' => 'last_seen', 'type' => 'int'],
            ],
            'rows' => 100000,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Swoole Configuration
    |--------------------------------------------------------------------------
    */
    'swoole' => [
        'options' => [
            // Worker configuration
            'worker_num' => env('OCTANE_WORKERS', 4),
            'task_worker_num' => env('OCTANE_TASK_WORKERS', 4),

            // Request limits
            'max_request' => env('OCTANE_MAX_REQUESTS', 10000),
            'max_request_grace' => env('OCTANE_MAX_REQUEST_GRACE', 128),

            // Connection settings
            'max_connection' => env('OCTANE_MAX_CONNECTIONS', 10000),
            'max_coroutine' => env('OCTANE_MAX_COROUTINES', 100000),

            // Buffer sizes (untuk streaming)
            'buffer_output_size' => 32 * 1024 * 1024, // 32MB
            'socket_buffer_size' => 128 * 1024 * 1024, // 128MB
            'package_max_length' => 50 * 1024 * 1024, // 50MB untuk video chunks

            // Static file handling
            'enable_static_handler' => true,
            'document_root' => public_path(),
            'static_handler_locations' => ['/storage', '/images', '/videos'],

            // Logging
            'log_level' => env('OCTANE_LOG_LEVEL', 4), // Warning
            'log_file' => storage_path('logs/swoole.log'),

            // SSL (jika HTTPS)
            // 'ssl_cert_file' => '/path/to/cert.pem',
            // 'ssl_key_file' => '/path/to/key.pem',
        ],
    ],
];

Step 5: Database Schema untuk Movie Streaming

# Generate migrations
php artisan make:model Movie -m
php artisan make:model Genre -m
php artisan make:model WatchHistory -m
php artisan make:model Review -m
php artisan make:model Subscription -m

<?php
// database/migrations/xxxx_create_movies_table.php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('movies', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('description');
            $table->string('poster_url');
            $table->string('backdrop_url')->nullable();
            $table->string('video_url');
            $table->string('trailer_url')->nullable();
            $table->integer('duration_minutes');
            $table->year('release_year');
            $table->decimal('rating', 3, 1)->default(0);
            $table->unsignedInteger('rating_count')->default(0);
            $table->unsignedBigInteger('view_count')->default(0);
            $table->unsignedInteger('current_viewers')->default(0);
            $table->boolean('is_premium')->default(false);
            $table->boolean('is_featured')->default(false);
            $table->boolean('is_active')->default(true);
            $table->json('metadata')->nullable(); // director, cast, etc
            $table->timestamps();

            $table->index(['is_active', 'is_featured']);
            $table->index(['release_year']);
            $table->index(['view_count']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('movies');
    }
};

<?php
// database/migrations/xxxx_create_genres_table.php

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('genres', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->timestamps();
        });

        Schema::create('genre_movie', function (Blueprint $table) {
            $table->foreignId('genre_id')->constrained()->cascadeOnDelete();
            $table->foreignId('movie_id')->constrained()->cascadeOnDelete();
            $table->primary(['genre_id', 'movie_id']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('genre_movie');
        Schema::dropIfExists('genres');
    }
};

<?php
// database/migrations/xxxx_create_watch_histories_table.php

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('watch_histories', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->foreignId('movie_id')->constrained()->cascadeOnDelete();
            $table->unsignedInteger('progress_seconds')->default(0);
            $table->unsignedInteger('duration_seconds');
            $table->boolean('completed')->default(false);
            $table->timestamp('last_watched_at');
            $table->timestamps();

            $table->unique(['user_id', 'movie_id']);
            $table->index(['user_id', 'last_watched_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('watch_histories');
    }
};

# Run migrations
php artisan migrate

Step 6: Create Models

<?php
// app/Models/Movie.php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Factories\\HasFactory;
use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;
use Illuminate\\Database\\Eloquent\\Relations\\HasMany;

class Movie extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'slug',
        'description',
        'poster_url',
        'backdrop_url',
        'video_url',
        'trailer_url',
        'duration_minutes',
        'release_year',
        'rating',
        'rating_count',
        'view_count',
        'current_viewers',
        'is_premium',
        'is_featured',
        'is_active',
        'metadata',
    ];

    protected $casts = [
        'rating' => 'decimal:1',
        'is_premium' => 'boolean',
        'is_featured' => 'boolean',
        'is_active' => 'boolean',
        'metadata' => 'array',
    ];

    // Relationships
    public function genres(): BelongsToMany
    {
        return $this->belongsToMany(Genre::class);
    }

    public function reviews(): HasMany
    {
        return $this->hasMany(Review::class);
    }

    public function watchHistories(): HasMany
    {
        return $this->hasMany(WatchHistory::class);
    }

    // Scopes
    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopeFeatured($query)
    {
        return $query->where('is_featured', true);
    }

    public function scopeTrending($query)
    {
        return $query->orderByDesc('current_viewers')
                     ->orderByDesc('view_count');
    }

    // Helpers
    public function getDurationFormattedAttribute(): string
    {
        $hours = floor($this->duration_minutes / 60);
        $minutes = $this->duration_minutes % 60;

        return $hours > 0
            ? "{$hours}h {$minutes}m"
            : "{$minutes}m";
    }
}

<?php
// app/Models/WatchHistory.php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;

class WatchHistory extends Model
{
    protected $fillable = [
        'user_id',
        'movie_id',
        'progress_seconds',
        'duration_seconds',
        'completed',
        'last_watched_at',
    ];

    protected $casts = [
        'completed' => 'boolean',
        'last_watched_at' => 'datetime',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function movie(): BelongsTo
    {
        return $this->belongsTo(Movie::class);
    }

    public function getProgressPercentageAttribute(): float
    {
        if ($this->duration_seconds === 0) {
            return 0;
        }

        return round(($this->progress_seconds / $this->duration_seconds) * 100, 1);
    }
}

Step 7: Create Services dengan Octane Features

<?php
// app/Services/MovieService.php

namespace App\\Services;

use App\\Models\\Movie;
use Illuminate\\Support\\Collection;
use Illuminate\\Support\\Facades\\Cache;
use Laravel\\Octane\\Facades\\Octane;

class MovieService
{
    /**
     * Get movie details dengan semua related data (concurrent)
     */
    public function getMovieDetails(Movie $movie, ?int $userId = null): array
    {
        // Fetch semua data secara PARALLEL
        [
            $recommendations,
            $reviews,
            $watchHistory,
            $viewerCount
        ] = Octane::concurrently([
            fn() => $this->getRecommendations($movie, 10),
            fn() => $movie->reviews()
                         ->with('user:id,name,avatar')
                         ->latest()
                         ->limit(20)
                         ->get(),
            fn() => $userId
                ? WatchHistory::where('user_id', $userId)
                              ->where('movie_id', $movie->id)
                              ->first()
                : null,
            fn() => $this->getViewerCount($movie->id),
        ]);

        return [
            'movie' => $movie->load('genres'),
            'recommendations' => $recommendations,
            'reviews' => $reviews,
            'watch_history' => $watchHistory,
            'viewer_count' => $viewerCount,
        ];
    }

    /**
     * Get recommendations based on genres
     */
    public function getRecommendations(Movie $movie, int $limit = 10): Collection
    {
        $cacheKey = "recommendations:{$movie->id}";

        return Cache::remember($cacheKey, now()->addHours(1), function () use ($movie, $limit) {
            $genreIds = $movie->genres->pluck('id');

            return Movie::query()
                ->active()
                ->where('id', '!=', $movie->id)
                ->whereHas('genres', fn($q) => $q->whereIn('genres.id', $genreIds))
                ->orderByDesc('rating')
                ->limit($limit)
                ->get(['id', 'title', 'slug', 'poster_url', 'rating', 'release_year']);
        });
    }

    /**
     * Get trending movies (dari Swoole Table)
     */
    public function getTrendingMovies(int $limit = 20): Collection
    {
        $table = Octane::table('movie_viewers');
        $movieViewers = [];

        foreach ($table as $key => $value) {
            if (str_starts_with($key, 'movie:')) {
                $movieId = (int) str_replace('movie:', '', $key);
                $movieViewers[$movieId] = $value['count'];
            }
        }

        // Sort by viewer count
        arsort($movieViewers);
        $topMovieIds = array_slice(array_keys($movieViewers), 0, $limit);

        if (empty($topMovieIds)) {
            // Fallback ke database
            return Movie::active()
                        ->trending()
                        ->limit($limit)
                        ->get();
        }

        return Movie::whereIn('id', $topMovieIds)
                    ->get()
                    ->sortBy(fn($movie) => array_search($movie->id, $topMovieIds));
    }

    /**
     * Get viewer count dari Swoole Table
     */
    public function getViewerCount(int $movieId): int
    {
        $table = Octane::table('movie_viewers');
        $data = $table->get("movie:{$movieId}");

        return $data['count'] ?? 0;
    }

    /**
     * Increment viewer count
     */
    public function incrementViewer(int $movieId): int
    {
        $table = Octane::table('movie_viewers');
        $key = "movie:{$movieId}";

        $current = $table->get($key);
        $newCount = ($current['count'] ?? 0) + 1;

        $table->set($key, [
            'count' => $newCount,
            'updated_at' => time(),
        ]);

        return $newCount;
    }

    /**
     * Decrement viewer count
     */
    public function decrementViewer(int $movieId): int
    {
        $table = Octane::table('movie_viewers');
        $key = "movie:{$movieId}";

        $current = $table->get($key);
        $newCount = max(0, ($current['count'] ?? 0) - 1);

        $table->set($key, [
            'count' => $newCount,
            'updated_at' => time(),
        ]);

        return $newCount;
    }

    /**
     * Get homepage data (concurrent)
     */
    public function getHomepageData(?int $userId = null): array
    {
        [
            $featured,
            $trending,
            $newReleases,
            $topRated,
            $continueWatching
        ] = Octane::concurrently([
            fn() => Movie::active()->featured()->limit(5)->get(),
            fn() => $this->getTrendingMovies(10),
            fn() => Movie::active()
                         ->orderByDesc('created_at')
                         ->limit(10)
                         ->get(),
            fn() => Movie::active()
                         ->orderByDesc('rating')
                         ->where('rating_count', '>=', 10)
                         ->limit(10)
                         ->get(),
            fn() => $userId
                ? $this->getContinueWatching($userId, 10)
                : collect(),
        ]);

        return [
            'featured' => $featured,
            'trending' => $trending,
            'new_releases' => $newReleases,
            'top_rated' => $topRated,
            'continue_watching' => $continueWatching,
        ];
    }

    /**
     * Get continue watching list
     */
    public function getContinueWatching(int $userId, int $limit = 10): Collection
    {
        return WatchHistory::with('movie:id,title,slug,poster_url,duration_minutes')
            ->where('user_id', $userId)
            ->where('completed', false)
            ->where('progress_seconds', '>', 0)
            ->orderByDesc('last_watched_at')
            ->limit($limit)
            ->get();
    }
}

Step 8: Create Controllers

<?php
// app/Http/Controllers/MovieController.php

namespace App\\Http\\Controllers;

use App\\Models\\Movie;
use App\\Services\\MovieService;
use Illuminate\\Http\\JsonResponse;
use Illuminate\\Http\\Request;

class MovieController extends Controller
{
    public function __construct(
        private MovieService $movieService
    ) {}

    /**
     * Homepage data
     */
    public function home(Request $request): JsonResponse
    {
        $data = $this->movieService->getHomepageData(
            $request->user()?->id
        );

        return response()->json($data);
    }

    /**
     * Movie detail
     */
    public function show(Movie $movie, Request $request): JsonResponse
    {
        $data = $this->movieService->getMovieDetails(
            $movie,
            $request->user()?->id
        );

        return response()->json($data);
    }

    /**
     * Start watching - increment viewer
     */
    public function startWatching(Movie $movie, Request $request): JsonResponse
    {
        $viewerCount = $this->movieService->incrementViewer($movie->id);

        // Track in streaming sessions table
        $table = \\Laravel\\Octane\\Facades\\Octane::table('streaming_sessions');
        $sessionKey = "session:{$request->user()->id}:{$movie->id}";

        $table->set($sessionKey, [
            'user_id' => $request->user()->id,
            'movie_id' => $movie->id,
            'progress' => 0,
            'started_at' => time(),
        ]);

        return response()->json([
            'message' => 'Streaming started',
            'viewer_count' => $viewerCount,
        ]);
    }

    /**
     * Stop watching - decrement viewer
     */
    public function stopWatching(Movie $movie, Request $request): JsonResponse
    {
        $viewerCount = $this->movieService->decrementViewer($movie->id);

        // Remove from streaming sessions
        $table = \\Laravel\\Octane\\Facades\\Octane::table('streaming_sessions');
        $sessionKey = "session:{$request->user()->id}:{$movie->id}";
        $table->del($sessionKey);

        return response()->json([
            'message' => 'Streaming stopped',
            'viewer_count' => $viewerCount,
        ]);
    }

    /**
     * Update watch progress
     */
    public function updateProgress(Movie $movie, Request $request): JsonResponse
    {
        $validated = $request->validate([
            'progress_seconds' => 'required|integer|min:0',
        ]);

        $watchHistory = $movie->watchHistories()
            ->updateOrCreate(
                ['user_id' => $request->user()->id],
                [
                    'progress_seconds' => $validated['progress_seconds'],
                    'duration_seconds' => $movie->duration_minutes * 60,
                    'completed' => $validated['progress_seconds'] >= ($movie->duration_minutes * 60 * 0.9),
                    'last_watched_at' => now(),
                ]
            );

        return response()->json([
            'progress' => $watchHistory->progress_percentage,
            'completed' => $watchHistory->completed,
        ]);
    }

    /**
     * Get trending movies
     */
    public function trending(): JsonResponse
    {
        $movies = $this->movieService->getTrendingMovies(20);

        return response()->json(['movies' => $movies]);
    }
}

Step 9: Define Routes

<?php
// routes/api.php

use App\\Http\\Controllers\\MovieController;
use Illuminate\\Support\\Facades\\Route;

// Public routes
Route::get('/home', [MovieController::class, 'home']);
Route::get('/movies/trending', [MovieController::class, 'trending']);
Route::get('/movies/{movie:slug}', [MovieController::class, 'show']);

// Protected routes
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/movies/{movie}/start', [MovieController::class, 'startWatching']);
    Route::post('/movies/{movie}/stop', [MovieController::class, 'stopWatching']);
    Route::post('/movies/{movie}/progress', [MovieController::class, 'updateProgress']);
});

Step 10: Run dan Test

# Development dengan hot reload
php artisan octane:start --watch

# Atau dengan options lengkap
php artisan octane:start --host=0.0.0.0 --port=8000 --workers=4 --task-workers=4 --watch

# Production
php artisan octane:start --host=0.0.0.0 --port=8000 --workers=4 --task-workers=4 --max-requests=10000

# Test endpoints
curl <http://localhost:8000/api/home>
curl <http://localhost:8000/api/movies/trending>
curl <http://localhost:8000/api/movies/inception>

# Benchmark dengan wrk
wrk -t4 -c100 -d30s <http://localhost:8000/api/home>

Step 11: Production Setup dengan Supervisor

# /etc/supervisor/conf.d/octane.conf

[program:octane]
process_name=%(program_name)s
command=php /var/www/movie-streaming/artisan octane:start --host=0.0.0.0 --port=8000 --workers=4 --task-workers=4 --max-requests=10000
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/octane.log
stopwaitsecs=60

# Reload supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start octane

# Check status
sudo supervisorctl status octane

Sekarang aplikasi movie streaming kamu sudah berjalan dengan Swoole! Di bagian selanjutnya, kita bahas hal-hal penting yang HARUS diperhatikan agar tidak terjebak masalah.

Bagian 6: Poin Penting yang HARUS Diperhatikan

Ini adalah bagian yang paling krusial. Banyak developer gagal dengan Swoole bukan karena teknologinya, tapi karena tidak memahami fundamental differences dari traditional PHP.

POIN #1: Stateful Nature — Memory Persists

Ini perbedaan paling fundamental. Di PHP-FPM, setiap request adalah "fresh start". Di Swoole, aplikasi tetap hidup di memory.

<?php
// ⚠️ BAHAYA: Class property yang accumulate

class MovieAnalyticsService
{
    // JANGAN seperti ini!
    private array $processedMovies = [];

    public function trackView(int $movieId): void
    {
        $this->processedMovies[] = $movieId;

        // Array ini TIDAK PERNAH di-reset!
        // Request 1: [1]
        // Request 2: [1, 2]
        // Request 1000: [1, 2, 3, ... 1000]
        // Request 10000: MEMORY EXHAUSTED!
    }
}

<?php
// ✅ BENAR: Gunakan local variable atau reset

class MovieAnalyticsService
{
    public function trackView(int $movieId): void
    {
        // Option 1: Local variable (recommended untuk most cases)
        $processedMovies = [$movieId];
        $this->doSomething($processedMovies);
        // Setelah method selesai, variable di-garbage collect
    }

    // Option 2: Jika memang perlu class property, reset explicitly
    private array $batchQueue = [];

    public function addToBatch(int $movieId): void
    {
        $this->batchQueue[] = $movieId;

        if (count($this->batchQueue) >= 100) {
            $this->processBatch();
            $this->batchQueue = []; // RESET!
        }
    }
}

Rule of Thumb: Tanya pada diri sendiri: "Apakah data ini perlu persist antar requests?" Jika tidak, gunakan local variable.

POIN #2: Static Properties — Silent Killer

Static properties adalah sumber memory leak dan bug paling umum di Swoole.

<?php
// ⚠️ BAHAYA: Static property untuk request-specific data

class CurrentUser
{
    private static ?User $user = null;

    public static function set(User $user): void
    {
        self::$user = $user;
    }

    public static function get(): ?User
    {
        return self::$user;
    }
}

// Di middleware:
CurrentUser::set($request->user());

// Di controller (request berbeda, user berbeda):
$user = CurrentUser::get();
// BUG: Bisa dapat user dari request SEBELUMNYA!
// User A login, lalu User B request → User B dapat data User A!

<?php
// ✅ BENAR: Gunakan Request atau Octane Context

use Laravel\\Octane\\Facades\\Octane;

class CurrentUser
{
    public static function set(User $user): void
    {
        // Octane context scoped per request
        Octane::requestContext()->set('current_user', $user);
    }

    public static function get(): ?User
    {
        return Octane::requestContext()->get('current_user');
    }
}

// Atau lebih simple: gunakan auth() helper
$user = auth()->user(); // Ini SAFE di Octane

POIN #3: Singletons yang Menyimpan State

Laravel menggunakan singleton pattern untuk banyak services. Di Swoole, singleton persist antar requests.

<?php
// ⚠️ PROBLEM: Singleton dengan request-specific state

class ShoppingCart
{
    private array $items = [];

    public function add(int $productId): void
    {
        $this->items[] = $productId;
    }

    public function getItems(): array
    {
        return $this->items;
    }
}

// AppServiceProvider:
$this->app->singleton(ShoppingCart::class);

// Request 1 (User A): Add item 1, 2
// Request 2 (User B): getItems() → [1, 2] ← DATA LEAK!

<?php
// ✅ SOLUSI 1: Jangan pakai singleton untuk request-specific services

// AppServiceProvider:
$this->app->bind(ShoppingCart::class); // bind, bukan singleton

// ✅ SOLUSI 2: Flush service setiap request

// config/octane.php
'flush' => [
    App\\Services\\ShoppingCart::class,
],

// ✅ SOLUSI 3: Store state di session/cache, bukan class property

class ShoppingCart
{
    public function add(int $userId, int $productId): void
    {
        $key = "cart:{$userId}";
        $items = Cache::get($key, []);
        $items[] = $productId;
        Cache::put($key, $items, now()->addHours(24));
    }

    public function getItems(int $userId): array
    {
        return Cache::get("cart:{$userId}", []);
    }
}

POIN #4: Database Connections

Swoole menggunakan connection pooling. Ini bagus untuk performance, tapi ada hal yang perlu diperhatikan.

<?php
// ⚠️ MASALAH: Connection limits

// Jika worker_num = 8 dan setiap worker buka 10 connections
// Total connections = 80
// MySQL default max_connections = 151
// Masih aman, tapi hati-hati!

// config/database.php
'mysql' => [
    // ...
    'pool' => [
        'min_connections' => 1,
        'max_connections' => 10, // Per worker!
    ],
],

// Hitung total max connections:
// workers (4) × max_connections (10) = 40 connections
// Pastikan MySQL max_connections > 40

<?php
// ⚠️ MASALAH: Long-running transactions

class MovieService
{
    public function processLargeUpdate(): void
    {
        DB::beginTransaction();

        try {
            // 10.000 movies di-update satu per satu
            foreach (Movie::cursor() as $movie) {
                $movie->update(['processed' => true]);
                sleep(0.1); // Simulate slow processing
            }

            // Transaction berjalan selama 1000 detik!
            // Connection di-hold, tidak bisa dipakai request lain

            DB::commit();
        } catch (\\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
}

// ✅ SOLUSI: Chunk atau queue

class MovieService
{
    public function processLargeUpdate(): void
    {
        // Option 1: Chunk dengan transaction per batch
        Movie::chunk(100, function ($movies) {
            DB::transaction(function () use ($movies) {
                foreach ($movies as $movie) {
                    $movie->update(['processed' => true]);
                }
            });
        });

        // Option 2: Queue untuk background processing
        Movie::chunk(100, function ($movies) {
            ProcessMoviesBatch::dispatch($movies->pluck('id'));
        });
    }
}

POIN #5: File Uploads

File uploads di Swoole di-handle berbeda. Temporary files perlu dipindah segera.

<?php
// ⚠️ MASALAH: Temporary file bisa hilang

class UploadController
{
    public function store(Request $request)
    {
        $file = $request->file('video');

        // JANGAN delay processing!
        // Temporary file di-cleanup setelah request selesai

        // ❌ SALAH
        ProcessVideoJob::dispatch($file->path()); // Path mungkin sudah tidak ada!

        return response()->json(['status' => 'queued']);
    }
}

// ✅ BENAR: Pindahkan file segera

class UploadController
{
    public function store(Request $request)
    {
        $file = $request->file('video');

        // Pindahkan ke permanent storage SEGERA
        $path = $file->store('videos', 's3');
        // atau
        $path = $file->store('videos', 'local');

        // Sekarang dispatch dengan path permanent
        ProcessVideoJob::dispatch($path);

        return response()->json([
            'status' => 'queued',
            'path' => $path,
        ]);
    }
}

POIN #6: Config dan Environment

Config di-cache saat Octane start. Perubahan runtime tidak akan ter-reflect.

<?php
// ⚠️ MASALAH: Dynamic config tidak work seperti expected

// Ini tidak akan berubah setelah Octane start
config(['app.name' => 'New Name']);

// .env changes juga tidak ter-detect tanpa restart
// Ubah .env → harus restart Octane

// ✅ SOLUSI: Gunakan database atau cache untuk dynamic settings

class SettingsService
{
    public function get(string $key, mixed $default = null): mixed
    {
        return Cache::remember("settings:{$key}", 60, function () use ($key, $default) {
            return Setting::where('key', $key)->value('value') ?? $default;
        });
    }

    public function set(string $key, mixed $value): void
    {
        Setting::updateOrCreate(
            ['key' => $key],
            ['value' => $value]
        );

        Cache::forget("settings:{$key}");
    }
}

// Usage
$appName = app(SettingsService::class)->get('app.name', 'Default');

POIN #7: Third-Party Packages

Tidak semua packages compatible dengan Swoole. Test sebelum production!

<?php
// PACKAGES YANG PERLU PERHATIAN:

// ❌ Laravel Debugbar - JANGAN di production
// Memory leak karena collect data setiap request
if (app()->environment('local') && !app()->runningInConsole()) {
    // Debugbar hanya di local DAN bukan Octane
    // Sebaiknya disable entirely untuk Octane dev
}

// ⚠️ Spatie Activitylog - Test carefully
// Default menyimpan di static array sebelum flush ke DB

// ⚠️ Some Payment SDKs - Might have singleton issues

// ✅ CARA TEST COMPATIBILITY:

// 1. Jalankan Octane
php artisan octane:start

// 2. Hit endpoint yang pakai package tersebut 1000x
for i in {1..1000}; do curl <http://localhost:8000/test-endpoint>; done

// 3. Monitor memory
watch -n 1 'ps aux | grep octane'

// Jika memory terus naik → Ada leak
// Jika memory stabil → Aman

POIN #8: Error Handling

Unhandled exceptions bisa crash worker. Handle dengan proper.

<?php
// ⚠️ MASALAH: Unhandled exception bisa crash worker

class StreamingController
{
    public function stream(Movie $movie)
    {
        // Jika ini throw exception tanpa catch,
        // worker bisa dalam state tidak konsisten
        $this->dangerousOperation($movie);
    }
}

// ✅ SOLUSI: Always handle exceptions

class StreamingController
{
    public function stream(Movie $movie)
    {
        try {
            return $this->streamingService->stream($movie);
        } catch (StreamingException $e) {
            Log::error('Streaming failed', [
                'movie_id' => $movie->id,
                'error' => $e->getMessage(),
            ]);

            return response()->json([
                'error' => 'Streaming temporarily unavailable'
            ], 503);
        } catch (\\Throwable $e) {
            // Catch semua untuk safety
            Log::critical('Unexpected streaming error', [
                'movie_id' => $movie->id,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'error' => 'Internal server error'
            ], 500);
        }
    }
}

// Global error handler di OctaneServiceProvider
use Laravel\\Octane\\Events\\WorkerErrorOccurred;

class OctaneServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(WorkerErrorOccurred::class, function ($event) {
            Log::critical('Octane worker error', [
                'exception' => $event->exception->getMessage(),
                'trace' => $event->exception->getTraceAsString(),
            ]);

            // Notify team via Slack/Discord
            // $this->notifyTeam($event->exception);
        });
    }
}

Quick Reference: Do's and Don'ts

┌─────────────────────────────────────────────────────────────────┐
│                     SWOOLE BEST PRACTICES                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ✅ DO:                                                          │
│  ├── Use local variables untuk temporary data                   │
│  ├── Use Redis/Database untuk persistent state                  │
│  ├── Use auth()->user() untuk current user                      │
│  ├── Handle semua exceptions                                     │
│  ├── Test memory usage sebelum production                       │
│  ├── Set max_request untuk auto-restart workers                 │
│  ├── Use Octane::concurrently() untuk parallel ops              │
│  ├── Monitor memory dan connections                              │
│  └── Restart Octane setelah deploy                              │
│                                                                  │
│  ❌ DON'T:                                                       │
│  ├── Store request data di class properties                     │
│  ├── Use static properties untuk user-specific data             │
│  ├── Assume fresh state setiap request                          │
│  ├── Hold long transactions                                      │
│  ├── Delay file upload processing                               │
│  ├── Use debugbar di production                                  │
│  ├── Ignore memory monitoring                                    │
│  ├── Change config() runtime expecting immediate effect         │
│  └── Use untested packages tanpa monitoring                     │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Dengan memperhatikan poin-poin ini, kamu bisa menghindari 90% masalah yang biasa terjadi dengan Swoole. Di bagian selanjutnya, kita akan lihat common mistakes yang lebih spesifik dan cara menghindarinya.

Bagian 7: Common Mistakes dan Cara Menghindarinya

Dari pengalaman membantu ribuan developers, berikut adalah kesalahan yang paling sering terjadi saat menggunakan Swoole dengan Laravel.

MISTAKE #1: Array yang Terus Bertambah

Ini kesalahan paling klasik dan paling berbahaya.

<?php
// ❌ SALAH: Logger custom yang accumulate

class RequestLogger
{
    private array $logs = [];

    public function log(string $message): void
    {
        $this->logs[] = [
            'time' => now(),
            'message' => $message,
            'memory' => memory_get_usage(),
        ];

        // logs[] terus bertambah setiap request
        // Tidak pernah di-clear!
    }

    public function flush(): void
    {
        // Method ini tidak pernah dipanggil
        file_put_contents('logs.json', json_encode($this->logs));
        $this->logs = [];
    }
}

// Setelah 10.000 requests:
// Memory usage: 500MB+ hanya untuk logs
// Server crash: Out of memory

<?php
// ✅ BENAR: Langsung write atau batching dengan limit

class RequestLogger
{
    private array $buffer = [];
    private int $bufferLimit = 100;

    public function log(string $message): void
    {
        $this->buffer[] = [
            'time' => now()->toISOString(),
            'message' => $message,
        ];

        // Auto-flush saat mencapai limit
        if (count($this->buffer) >= $this->bufferLimit) {
            $this->flush();
        }
    }

    public function flush(): void
    {
        if (empty($this->buffer)) {
            return;
        }

        // Write ke file atau kirim ke logging service
        Log::channel('custom')->info('Request batch', $this->buffer);

        // PENTING: Reset buffer!
        $this->buffer = [];
    }

    public function __destruct()
    {
        // Flush remaining saat worker restart
        $this->flush();
    }
}

// Atau lebih simple: langsung log tanpa buffer
class RequestLogger
{
    public function log(string $message): void
    {
        // Langsung write, no accumulation
        Log::info($message);
    }
}

MISTAKE #2: Event Listeners yang Di-register Setiap Request

<?php
// ❌ SALAH: Register listener di controller/middleware

class MovieController
{
    public function show(Movie $movie)
    {
        // SETIAP request register listener BARU!
        Event::listen(MovieViewed::class, function ($event) {
            $this->trackAnalytics($event);
        });

        event(new MovieViewed($movie));

        // Request 1: 1 listener
        // Request 2: 2 listeners (yang lama masih ada!)
        // Request 1000: 1000 listeners!
        // Event fired 1x → handler dipanggil 1000x

        return response()->json($movie);
    }
}

<?php
// ✅ BENAR: Register listener di EventServiceProvider

// app/Providers/EventServiceProvider.php
class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        MovieViewed::class => [
            TrackMovieAnalytics::class,
        ],
    ];
}

// app/Listeners/TrackMovieAnalytics.php
class TrackMovieAnalytics
{
    public function handle(MovieViewed $event): void
    {
        // Safe - listener hanya di-register sekali saat boot
        $event->movie->increment('view_count');
    }
}

// Controller jadi clean
class MovieController
{
    public function show(Movie $movie)
    {
        event(new MovieViewed($movie));
        return response()->json($movie);
    }
}

MISTAKE #3: Closure yang Capture $this

<?php
// ❌ SALAH: Closure capture controller instance

class MovieController
{
    private array $data = [];

    public function process(Movie $movie)
    {
        // Closure capture $this
        Octane::tick('process', function () {
            // $this reference ke controller
            // Controller tidak bisa di-garbage collect
            // Memory leak!
            $this->doSomething();
        });

        return response()->json(['status' => 'ok']);
    }
}

<?php
// ✅ BENAR: Extract logic atau gunakan static method

class MovieController
{
    public function process(Movie $movie)
    {
        $movieId = $movie->id;

        // Capture hanya data yang diperlukan, bukan $this
        Octane::tick('process', function () use ($movieId) {
            app(MovieProcessor::class)->process($movieId);
        });

        return response()->json(['status' => 'ok']);
    }
}

// Atau register tick di ServiceProvider
class OctaneServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Octane::tick('process-movies', function () {
            // Tidak ada reference ke controller
            app(MovieProcessor::class)->processQueue();
        })->seconds(10);
    }
}

MISTAKE #4: Blocking Operations di Request Handler

<?php
// ❌ SALAH: Long-running operation di request

class VideoController
{
    public function transcode(Movie $movie)
    {
        // Transcode video bisa butuh 10-30 menit!
        // Selama ini, worker BLOCKED
        // Tidak bisa handle request lain
        $this->transcoder->process($movie->video_path);

        return response()->json(['status' => 'done']);
    }
}

<?php
// ✅ BENAR: Gunakan Task Worker atau Queue

class VideoController
{
    public function transcode(Movie $movie)
    {
        // Option 1: Octane Task (untuk task yang butuh app context)
        Octane::task(function () use ($movie) {
            app(VideoTranscoder::class)->process($movie->video_path);
        });

        // Option 2: Laravel Queue (recommended untuk long jobs)
        TranscodeVideoJob::dispatch($movie);

        // Option 3: Concurrent untuk parallel short tasks
        Octane::concurrently([
            fn() => $this->generateThumbnail($movie),
            fn() => $this->extractMetadata($movie),
            fn() => $this->notifyAdmin($movie),
        ]);

        return response()->json([
            'status' => 'processing',
            'message' => 'Video is being processed'
        ]);
    }
}

MISTAKE #5: Tidak Handle Graceful Shutdown

<?php
// ❌ SALAH: Data hilang saat worker restart

class AnalyticsService
{
    private array $pendingWrites = [];

    public function track(string $event, array $data): void
    {
        $this->pendingWrites[] = compact('event', 'data');

        // Worker restart karena max_request tercapai
        // atau manual restart
        // pendingWrites HILANG!
    }
}

<?php
// ✅ BENAR: Handle WorkerStopping event

use Laravel\\Octane\\Events\\WorkerStopping;

class AnalyticsService
{
    private array $pendingWrites = [];

    public function __construct()
    {
        // Listen to worker stopping
        Event::listen(WorkerStopping::class, function () {
            $this->flush();
        });
    }

    public function track(string $event, array $data): void
    {
        $this->pendingWrites[] = compact('event', 'data');

        // Also flush jika buffer penuh
        if (count($this->pendingWrites) >= 50) {
            $this->flush();
        }
    }

    public function flush(): void
    {
        if (empty($this->pendingWrites)) {
            return;
        }

        try {
            // Bulk insert ke database
            DB::table('analytics')->insert($this->pendingWrites);
            $this->pendingWrites = [];
        } catch (\\Exception $e) {
            // Log error, jangan throw (bisa infinite loop)
            Log::error('Failed to flush analytics', [
                'error' => $e->getMessage(),
                'count' => count($this->pendingWrites),
            ]);
        }
    }
}

MISTAKE #6: Session/Cache Driver File-based

<?php
// ❌ SALAH: File-based session dengan multiple workers

// .env
SESSION_DRIVER=file
CACHE_DRIVER=file

// Problem:
// Worker 1: Write session ke file A
// Worker 2: Read session, file A not found atau stale
// Session INCONSISTENT antar workers!

// Race condition:
// Worker 1: Read → Modify → Write
// Worker 2: Read → Modify → Write (overwrites Worker 1 changes)

<?php
// ✅ BENAR: Gunakan Redis atau Database

// .env
SESSION_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis

// config/database.php
'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'), // phpredis lebih cepat

    'default' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_DB', '0'),
    ],

    'cache' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_CACHE_DB', '1'),
    ],

    'session' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_SESSION_DB', '2'),
    ],
],

// Atau gunakan Swoole Table untuk ultra-fast cache
// (tapi data hilang saat restart)

MISTAKE #7: Development Config di Production

<?php
// ❌ SALAH: Copy-paste dari development

// Development command:
php artisan octane:start --watch --workers=1

// Production command (JANGAN seperti ini):
php artisan octane:start --watch --workers=1
// --watch = constant file system polling (CPU waste)
// --workers=1 = tidak utilize multi-core

<?php
// ✅ BENAR: Proper production config

// Development:
php artisan octane:start --watch --workers=1

// Production:
php artisan octane:start \\
    --workers=4 \\              // Sesuaikan dengan CPU cores
    --task-workers=4 \\         // Untuk background tasks
    --max-requests=10000 \\     // Auto-restart untuk prevent memory leak
    --host=0.0.0.0 \\          // Accept connections dari luar
    --port=8000

// Atau via environment variables (recommended)
// .env.production
OCTANE_WORKERS=4
OCTANE_TASK_WORKERS=4
OCTANE_MAX_REQUESTS=10000

// config/octane.php
'swoole' => [
    'options' => [
        'worker_num' => env('OCTANE_WORKERS', 4),
        'task_worker_num' => env('OCTANE_TASK_WORKERS', 4),
        'max_request' => env('OCTANE_MAX_REQUESTS', 10000),
    ],
],

MISTAKE #8: Tidak Monitoring Memory

<?php
// ❌ SALAH: Deploy tanpa monitoring

// Deploy dan hope for the best
// Tidak ada alerting
// Tidak ada metrics
// Tahu ada masalah setelah server crash

<?php
// ✅ BENAR: Setup comprehensive monitoring

// 1. Health endpoint
Route::get('/health', function () {
    return response()->json([
        'status' => 'ok',
        'memory_usage' => memory_get_usage(true),
        'memory_peak' => memory_get_peak_usage(true),
        'uptime' => time() - LARAVEL_START,
    ]);
});

// 2. Octane metrics endpoint
Route::get('/metrics', function () {
    $table = Octane::table('movie_viewers');
    $activeViewers = 0;

    foreach ($table as $value) {
        $activeViewers += $value['count'];
    }

    return response()->json([
        'active_viewers' => $activeViewers,
        'memory' => [
            'current' => memory_get_usage(true),
            'peak' => memory_get_peak_usage(true),
            'limit' => ini_get('memory_limit'),
        ],
        'workers' => [
            'count' => env('OCTANE_WORKERS', 4),
        ],
    ]);
});

// 3. Memory threshold alerting
class MemoryMonitorService
{
    private int $warningThreshold = 100 * 1024 * 1024; // 100MB
    private int $criticalThreshold = 200 * 1024 * 1024; // 200MB

    public function check(): void
    {
        $usage = memory_get_usage(true);

        if ($usage > $this->criticalThreshold) {
            Log::critical('Memory usage critical', [
                'usage' => $usage,
                'threshold' => $this->criticalThreshold,
            ]);
            // Send alert
            $this->notifyTeam('CRITICAL: Memory usage exceeds threshold');
        } elseif ($usage > $this->warningThreshold) {
            Log::warning('Memory usage warning', [
                'usage' => $usage,
                'threshold' => $this->warningThreshold,
            ]);
        }
    }
}

// Register sebagai tick
Octane::tick('memory-monitor', function () {
    app(MemoryMonitorService::class)->check();
})->seconds(30);

Debugging Checklist

Saat ada masalah dengan Swoole, gunakan checklist ini:

┌─────────────────────────────────────────────────────────────────┐
│                    SWOOLE DEBUGGING CHECKLIST                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  □ Memory Usage                                                  │
│    ├── Check dengan: memory_get_usage(true)                     │
│    ├── Monitor trend: naik terus atau stable?                   │
│    └── Cari: array yang accumulate, listener duplicates         │
│                                                                  │
│  □ Static Properties                                             │
│    ├── Grep codebase: "private static", "protected static"     │
│    ├── Check: apakah menyimpan request-specific data?           │
│    └── Solution: pindah ke Octane context atau request          │
│                                                                  │
│  □ Singletons                                                    │
│    ├── Check AppServiceProvider untuk singleton()               │
│    ├── Check: apakah singleton menyimpan state?                 │
│    └── Solution: tambahkan ke flush array atau ubah ke bind()   │
│                                                                  │
│  □ Third-party Packages                                          │
│    ├── Identify packages yang baru ditambah                     │
│    ├── Test satu per satu dengan load testing                   │
│    └── Check package issues di GitHub                            │
│                                                                  │
│  □ Database Connections                                          │
│    ├── Monitor: SHOW PROCESSLIST                                │
│    ├── Check: connection limits                                  │
│    └── Check: long-running queries/transactions                  │
│                                                                  │
│  □ Error Logs                                                    │
│    ├── Check: storage/logs/laravel.log                          │
│    ├── Check: storage/logs/swoole.log                           │
│    └── Look for: patterns, repeated errors                       │
│                                                                  │
│  □ Worker Status                                                 │
│    ├── Check: php artisan octane:status                         │
│    ├── Check: supervisorctl status                              │
│    └── Check: apakah workers restart terlalu sering?            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Quick Fix Commands

# Check memory usage per process
ps aux | grep octane | awk '{print $6/1024 " MB - " $11}'

# Watch memory real-time
watch -n 1 'ps aux | grep octane | grep -v grep | awk "{sum+=\\$6} END {print sum/1024 \\" MB total\\"}"'

# Check open database connections
mysql -e "SHOW PROCESSLIST" | grep movie_streaming | wc -l

# Restart Octane gracefully
php artisan octane:reload

# Restart Octane completely
php artisan octane:stop && php artisan octane:start

# Via Supervisor
sudo supervisorctl restart octane

Dengan menghindari kesalahan-kesalahan ini, aplikasi Swoole kamu akan berjalan stabil di production. Di bagian terakhir, kita akan wrap up dengan kesimpulan dan resources untuk belajar lebih lanjut.

Bagian 8: Kesimpulan dan Langkah Selanjutnya

Kita sudah membahas Swoole dari A sampai Z — mulai dari konsep dasar, benefit, pros dan cons, cara integrasi, sampai common mistakes. Sekarang waktunya wrap up.

Ringkasan: Apa yang Sudah Kita Pelajari

SWOOLE + LARAVEL OCTANE JOURNEY:

📖 BAB 1-2: Memahami Masalah & Solusi
├── PHP-FPM bootstrap ulang setiap request (100-300ms overhead)
├── Swoole keep application di memory
├── Event-driven, non-blocking I/O
├── Coroutines untuk concurrent operations
└── Connection pooling untuk efficiency

📖 BAB 3: Benefit Utama
├── 10x performance improvement
├── Concurrent task execution (Octane::concurrently)
├── Built-in WebSocket server
├── Reduced memory usage (50-60%)
├── Swoole Tables untuk shared memory
└── Background ticks tanpa cron

📖 BAB 4: Fair Assessment
├── PROS: Performance, cost, real-time features
├── CONS: Learning curve, compatibility, debugging
└── Decision matrix untuk kapan use/avoid

📖 BAB 5: Hands-on Integration
├── Install Swoole extension
├── Setup Laravel Octane
├── Configure untuk movie streaming
├── Database schema & models
├── Services dengan concurrent fetching
└── Production deployment

📖 BAB 6-7: Pitfalls & Solutions
├── Stateful nature awareness
├── Static properties danger
├── Memory leak prevention
├── Connection management
├── Error handling
└── 8 common mistakes dengan fixes

Benchmark Recap: Movie Streaming Platform

Dengan implementasi yang kita bahas, berikut expected improvement:

┌───────────────────────────────┬──────────────┬──────────────┬─────────────┐
│ Metric                        │ Before       │ After Swoole │ Improvement │
│                               │ (PHP-FPM)    │              │             │
├───────────────────────────────┼──────────────┼──────────────┼─────────────┤
│ Homepage load                 │ 450ms        │ 80ms         │ 5.6x        │
│ Movie detail page             │ 350ms        │ 60ms         │ 5.8x        │
│ API requests/second           │ 800          │ 8,000        │ 10x         │
│ Max concurrent viewers        │ 1,000        │ 10,000       │ 10x         │
│ Server memory (100 users)     │ 4 GB         │ 500 MB       │ 8x          │
│ Monthly server cost           │ $270         │ $100         │ 2.7x        │
│ WebSocket (chat) latency      │ N/A (Pusher) │ <10ms        │ Native      │
└───────────────────────────────┴──────────────┴──────────────┴─────────────┘

Kapan Swoole adalah Pilihan Tepat

✅ PERFECT FIT:

├── Streaming platforms (video, audio, live)
├── Real-time applications (chat, notifications)
├── High-traffic APIs (>1000 req/sec)
├── WebSocket-heavy applications
├── Gaming backends
├── Live dashboards & analytics
├── Concurrent data aggregation
└── Cost-sensitive high-traffic apps

⚠️ CONSIDER CAREFULLY:

├── Medium traffic with tight deadline
├── Team dengan limited async experience
├── Applications dengan banyak legacy packages
└── Mixed Windows/Linux development team

❌ PROBABLY OVERKILL:

├── Simple CRUD applications
├── Low traffic websites (<100 req/sec)
├── MVP/Prototype yang butuh speed of development
├── Shared hosting environment
└── Solo projects dengan tight timeline

Key Takeaways

Jika kamu hanya ingat beberapa hal dari artikel ini:

🔑 KEY TAKEAWAYS:

1. STATEFUL MINDSET
   "Di Swoole, variabel class TIDAK reset antar request.
   Selalu tanya: apakah data ini perlu persist?"

2. STATIC = DANGER
   "Static properties adalah source #1 memory leak dan data bleeding.
   Hindari untuk request-specific data."

3. CONCURRENT IS POWER
   "Octane::concurrently() bisa cut response time 50-70%
   untuk pages yang fetch multiple data sources."

4. MONITOR EVERYTHING
   "Memory leak tidak terlihat sampai server crash.
   Setup monitoring SEBELUM production."

5. TEST BEFORE DEPLOY
   "Setiap package, setiap feature — test dengan load testing.
   1000 requests reveal what 10 requests hide."

Production Readiness Checklist

Sebelum deploy Swoole ke production:

□ Environment & Config
  ├── SESSION_DRIVER=redis (bukan file)
  ├── CACHE_DRIVER=redis (bukan file)
  ├── QUEUE_CONNECTION=redis
  ├── max_request configured (10000)
  └── Proper worker count (= CPU cores)

□ Code Review
  ├── Grep untuk static properties dengan state
  ├── Check singletons di ServiceProvider
  ├── Review class properties yang bisa accumulate
  ├── Verify semua exceptions di-handle
  └── Test semua packages compatibility

□ Testing
  ├── Load test dengan 1000+ requests
  ├── Monitor memory selama load test
  ├── Test concurrent scenarios
  ├── Verify data tidak bleeding antar requests
  └── Test WebSocket dengan multiple connections

□ Monitoring
  ├── Health endpoint configured
  ├── Memory alerting setup
  ├── Error logging ke central service
  ├── Supervisor configured untuk auto-restart
  └── Backup restart strategy ready

□ Deployment
  ├── Blue-green atau rolling deployment ready
  ├── Rollback plan documented
  ├── Team knows how to restart Octane
  └── Post-deployment monitoring plan


Belajar Lebih Lanjut di BuildWithAngga

Artikel ini baru permukaan. Untuk benar-benar menguasai Swoole dan Laravel untuk aplikasi high-performance, butuh practice dan guidance yang proper.

Kelas yang Relevan

📚 LARAVEL PERFORMANCE & ADVANCED

├── Laravel Octane & Swoole Masterclass
│   ├── Deep dive ke Swoole internals
│   ├── Advanced coroutine patterns
│   ├── WebSocket implementation lengkap
│   ├── Production deployment strategies
│   └── Real project: Streaming platform

├── Laravel Real-time Applications
│   ├── WebSocket dari scratch
│   ├── Broadcasting & events
│   ├── Live notifications
│   └── Chat application complete

├── Laravel Performance Optimization
│   ├── Database optimization
│   ├── Caching strategies
│   ├── Queue optimization
│   └── Profiling & debugging

└── Laravel API Development
    ├── RESTful best practices
    ├── Authentication (Sanctum/Passport)
    ├── Rate limiting
    └── API versioning

📚 PROJECT-BASED LEARNING

├── Build Streaming Platform dengan Laravel
│   ├── Full stack implementation
│   ├── Video streaming dengan byte-range
│   ├── User subscriptions
│   ├── Real-time features
│   └── Deployment ke production

├── Build Real-time Chat Application
│   ├── WebSocket implementation
│   ├── Message persistence
│   ├── Online presence
│   └── File sharing

└── Build SaaS Application
    ├── Multi-tenancy
    ├── Subscription billing
    ├── Admin dashboard
    └── API for mobile apps

📚 DEVOPS & DEPLOYMENT

├── Docker untuk Laravel Developer
│   ├── Containerization basics
│   ├── Docker Compose setup
│   ├── Production-ready images
│   └── CI/CD integration

├── Laravel Deployment Best Practices
│   ├── Server provisioning
│   ├── Zero-downtime deployment
│   ├── SSL & security
│   └── Monitoring setup

└── Redis Mastery
    ├── Caching patterns
    ├── Session management
    ├── Pub/Sub for real-time
    └── Queue optimization

Kenapa Belajar di BuildWithAngga?

✅ BENEFIT:

├── Kurikulum Terstruktur
│   └── Learning path jelas dari pemula ke advanced

├── Bahasa Indonesia
│   └── Tidak ada language barrier

├── Project-Based
│   └── Bukan teori saja, langsung build real apps

├── Lifetime Access
│   └── Akses selamanya, termasuk updates

├── Mentor Support
│   └── Tanya jawab dengan praktisi berpengalaman

├── Portfolio-Ready Projects
│   └── Hasil belajar bisa langsung masuk portfolio

├── Community
│   └── Network dengan ribuan developers lain

└── Sertifikat
    └── Bukti kompetensi untuk CV


📱 Konsultasi Gratis

Bingung harus mulai dari mana? Tim BuildWithAngga siap membantu!

WhatsApp: +62 877-7891-9943

KIRIM PESAN SEPERTI INI:

"Halo tim BuildWithAngga!

Saya [nama], seorang [profesi/status].
Background saya: [jelaskan singkat pengalaman].

Saya tertarik belajar Laravel dengan Swoole untuk
membangun aplikasi [jenis aplikasi].

Target saya: [apa yang ingin dicapai].

Bisa bantu recommend learning path yang tepat?"

Yang Bisa Dikonsultasikan:

├── Learning path recommendation
├── Kelas yang sesuai dengan goals
├── Promo dan bundle yang tersedia
├── Technical questions
├── Career advice
└── Portfolio review


Penutup

Swoole dan Laravel Octane membuka dimensi baru untuk PHP development. Aplikasi yang sebelumnya impossible atau sangat mahal untuk di-scale, sekarang bisa dibangun dengan cost yang reasonable.

Tapi seperti semua teknologi, Swoole bukan silver bullet. Ia powerful di tangan yang tepat — developer yang memahami cara kerjanya, pitfalls yang harus dihindari, dan patterns yang harus diikuti.

Movie streaming platform adalah salah satu use case perfect. Real-time requirements, high concurrency, cost sensitivity — semuanya addressed oleh Swoole dengan elegant.

Langkah selanjutnya:

  1. Practice — Setup Swoole di local, experiment dengan code examples dari artikel ini
  2. Build — Buat small project untuk familiarize dengan patterns
  3. Learn — Dalami dengan structured learning di BuildWithAngga
  4. Deploy — Implement di real project dengan proper monitoring
  5. Share — Ajari yang lain, teaching is the best learning

"Technology yang dipahami dengan baik adalah technology yang powerful. Technology yang dipakai tanpa pemahaman adalah recipe for disaster."

Selamat belajar dan building!

Angga Risky Setiawan Founder, BuildWithAngga