Belajar Swoole - Distributed & High Availability
Episode 22 of 26

Belajar Swoole - Distributed & High Availability

Menaklukkan keterbatasan satu node: arsitektur multi-node dengan load balancer, sticky sessions, broadcast WebSocket lintas node via Redis pub/sub, serta graceful restart tanpa downtime.

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

Pendahuluan

Setelah di episode 21 kita membangun server game dan IoT — pada episode kali ini kita menaikkan skala: dari satu node menjadi banyak node. Semua yang kita pelajari — worker, coroutine, shared memory — bekerja di satu mesin. Begitu traffic melebihi satu mesin, kalian butuh arsitektur terdistribusi.

Mengapa episode ini penting? Karena inilah batas yang sering membuat aplikasi Swoole "berhasil di lab, gagal di produksi". Satu server Swoole bisa melayani ratusan ribu koneksi — tetapi WebSocket cluster, sticky session, dan restart tanpa downtime adalah masalah yang sama sekali berbeda level. Episode ini menjembatani keduanya.

Dari Satu Node ke Multi-Node

Arsitektur dasar multi-node:

100%

Tiga node Swoole di belakang load balancer. Sekarang timbul masalah yang tidak ada di satu node:

MasalahDi satu nodeDi multi-node
Broadcast WebSocket$server->connectionsTidak cukup — koneksi tersebar di node lain
State sharedSwoole\TableHarus pindah ke Redis/database
Koneksi userSatu node tahu semuaNode lain tidak tahu
Restart nodeCepatBerisiko memutus semua koneksi

Solusi untuk masing-masing ada di bawah.

Sticky Sessions

Koneksi WebSocket bersifat stateful: user terhubung ke satu node dan berharap pesannya datang dari sana. Bila load balancer mengarahkan user ke node lain, koneksi putus.

Dua pendekatan:

  1. Sticky session di LB: hash IP/cookie agar user selalu ke node yang sama. Untuk HTTP, cukup andalkan ip_hash/hash di Nginx/HAProxy.
  2. State di layer shared: jangan percaya pada "koneksi di node tertentu" — semua node berbagi state via Redis. Inilah pendekatan yang benar untuk skala.

Tip

Untuk HTTP biasa, kalian bisa memakai dispatch_mode => 4 (IP hash) di satu node. Tapi untuk multi-node, atur sticky di load balancer — jangan di aplikasi. WebSocket wajib sticky: browser tidak punya mekanisme failover otomatis antar node.

WebSocket Cluster: Redis Pub/Sub

Masalah inti broadcast lintas node: user terhubung ke Node 1, user lain di Node 3. Node 1 tidak punya koneksi Node 3. Solusinya: Redis pub/sub sebagai bus pesan — setiap node mendaftarkan dirinya, dan pesan broadcast diteruskan lewat Redis.

100%

Publisher: Dari Handler WebSocket

Broadcast via Redis pub/sub
$server->on('Message', function (Server $server, Frame $frame) use ($redis) {
    // 1. Broadcast lokal (node ini)
    foreach ($server->connections as $fd) {
        if ($fd !== $frame->fd && $server->isEstablished($fd)) {
            $server->push($fd, $frame->data);
        }
    }
 
    // 2. Teruskan ke node lain via Redis
    $redis->publish('chat-broadcast', json_encode([
        'data' => $frame->data,
        'sender' => $frame->fd,
        'origin' => getNodeId(),
    ]));
});

Subscriber: Proses Khusus di Tiap Node

Setiap node menjalankan subscriber yang mendengarkan channel dan meneruskan ke koneksi lokalnya:

Subscriber Redis di tiap node
use Swoole\Process;
 
$subscriber = new Process(function (Process $proc) use ($server, $redisSub) {
    $redisSub->subscribe(['chat-broadcast'], function ($redis, $channel, $msg) use ($server) {
        $payload = json_decode($msg, true);
 
        if ($payload['origin'] === getNodeId()) {
            return; // pesan dari node sendiri — sudah di-broadcast lokal
        }
 
        foreach ($server->connections as $fd) {
            if ($fd !== $payload['sender'] && $server->isEstablished($fd)) {
                $server->push($fd, $payload['data']);
            }
        }
    });
});
 
$server->addProcess($subscriber);

Pola ini membuat broadcast global: satu pesan dari Node 1 sampai ke semua user di Node 2 dan 3. origin mencegah pesan berputar balik ke node asal (loop broadcast).

Warning

Tanpa pengecekan origin, satu pesan bisa dipancarkan dua kali oleh node asal (sekali lokal, sekali hasil subscribe). Simpan identitas node unik (misal env NODE_ID), dan lewati pesan yang berasal dari diri sendiri. Ini bug klasik cluster broadcast yang sulit dideteksi karena hanya muncul di multi-node.

State Shared Antar Node

Swoole\Table (episode 12) berhenti bekerja lintas node — memory-nya lokal. Ganti dengan:

KebutuhanPengganti multi-node
Online counterRedis SADD/SCARD atau INCR
Rate limiterRedis INCR + EXPIRE (sliding window)
Cache kecilRedis/KV shared
LockRedis SET NX EX (Redlock untuk kritis)

Contoh online counter global:

Online users global via Redis
$server->on('Open', function (Server $server, Request $req) use ($redis) {
    $redis->sAdd('ws:online', $req->fd);
    $server->push($req->fd, 'Online: ' . $redis->sCard('ws:online'));
});
 
$server->on('Close', function (Server $server, int $fd) use ($redis) {
    $redis->sRem('ws:online', $fd);
});

Graceful Restart Tanpa Downtime

Restart server Swoole tidak boleh memutus semua koneksi sekaligus. Swoole menyediakan dua mekanisme:

SignalFungsi
SIGUSR1Reload worker — proses lama dilanjutkan sampai request selesai, lalu diganti proses baru
SIGTERMGraceful shutdown — berhenti menangani request baru, tunggu yang sedang berjalan selesai
Reload worker tanpa memutus koneksi
kill -USR1 $(pgrep -f server.php)

Dalam kode, setelan penting agar reload aman:

Reload yang aman
$server->set([
    'max_request' => 100000,   // worker direcycle setelah N request
    'reload_async' => true,    // tunggu handler selesai sebelum mati
]);

Untuk deployment multi-node, lakukan rolling restart: reload node satu per satu, pastikan node kembali sehat (health check /health), baru lanjut node berikutnya. User tidak akan merasakan apa pun.

Common Pitfalls

MasalahPenyebabSolusi
Pesan broadcast gandaTidak cek origin di subscriberSkip pesan dari node sendiri
User terputus saat restartReload tidak async / semua node direstart bersamaanreload_async, rolling restart per node
Counter online beda antar nodeTable lokal dipakai di multi-nodePindah ke Redis
Broadcast tidak sampai ke node lainSubscriber tidak jalan / Redis downHealth-check subscriber, HA Redis
Sticky session rusakLB tidak di-set ip_hashAtur sticky di level load balancer

Penutup

Pada episode 22 ini, kalian telah menaikkan skala ke multi-node.

Inti yang harus dibawa pulang:

  • Multi-node: load balancer + N node Swoole + Redis sebagai bus & penyimpanan state.
  • Sticky session untuk WebSocket diatur di LB, bukan di aplikasi.
  • Broadcast lintas node = Redis pub/sub; cek origin agar tidak ganda.
  • Swoole\Table → Redis saat lintas node.
  • Graceful restart: SIGUSR1 reload async + rolling restart per node.

Di episode 23 selanjutnya, kita menyebarkan aplikasi tanpa PHP terinstall: Swoole CLI & Static Runtimeswoole-cli, build-static-php, dan TypePHP/AOT. Sampai jumpa di episode 23!