Belajar RoadRunner - Sessions, State & Stateless Best Practice
Episode 14 of 26

Belajar RoadRunner - Sessions, State & Stateless Best Practice

Mempelajari bahaya state di worker persistent: mengapa variabel global dan singleton request-scoped bocor antar request, cara mengelola session secara aman dengan store eksternal seperti Redis, dan praktik refactor aplikasi agar worker-safe.

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

Pendahuluan

Setelah di episode 10-13 kita belajar menyimpan data dan melayani aset, pada episode kali ini kita membahas bahaya terbesar aplikasi long-running: state. Di PHP-FPM, setiap request mendapat dunia baru — variabel global, session, dan koneksi semua di-reset otomatis. Di RoadRunner, worker hidup lebih lama dari satu request; apa pun yang kalian simpan di memory worker bisa bocor ke request berikutnya.

Mengapa episode ini penting? Karena bug yang paling sulit dideteksi di app server PHP bukan crash, melainkan kebocoran state yang berperilaku acak — sesekali user melihat data user lain. Biasanya ini berawal dari satu variabel statis yang lupa di-reset. Episode ini mencegah kelas bug paling berbahaya tersebut.

Mengapa Worker Persistent Berbahaya

Perhatikan contoh bug klasik:

Bug: state bocor antar request
class UserService
{
    private static ?int $currentUserId = null;
 
    public function setUser(int $id): void
    {
        self::$currentUserId = $id;
    }
 
    public function getUser(): ?int
    {
        return self::$currentUserId;
    }
}

Di PHP-FPM, self::$currentUserId selalu nol di awal request — aman. Di worker RoadRunner, nilai dari request sebelumnya masih tersimpan saat request baru datang. Request pertama set user 42, request kedua di worker yang sama membaca 42 tanpa pernah set — kebocoran data. Inilah contoh konkret mengapa aturan emasnya:

Danger

Jangan pernah menyimpan state request di property class, variabel statis, atau singleton. Setiap state yang hidup lebih dari satu request adalah risiko kebocoran antar user. State harus selalu di-reset di awal tiap request atau disimpan di luar worker.

Barang yang Rentan Bocor

Beberapa tempat state sering "menyelinap":

ItemRisikoSolusi
Variabel staticBocor antar requestReset manual per-request, atau hindari
Property singletonBocor bila diisi data requestHanya simpan dependency, bukan data
$_SESSION, $_COOKIETidak di-reset otomatisKelola eksplisit atau via framework
Koneksi DB dengan transaksi menggantungTransaksi request sebelumnya ikutCommit/rollback di tiap akhir request
Buffer outputSisa output request sebelumnyaBersihkan per-request

Framework modern menangani ini secara otomatis — Octane me-reset container dan singleton antar request — tetapi kode kalian sendiri (di service, helper, library) tetap tanggung jawab kalian.

Pola Aman: Request-Scoped State

Bila butuh state per-request, bawa lewat request itu sendiri, bukan global:

State lewat atribut request
// Simpan di request (aman — request adalah scope-nya)
$request = $request->withAttribute('current_user_id', 42);
 
// Di service yang menerima request
$userId = $request->getAttribute('current_user_id');

Atau gunakan container yang mendukung request scope (Laravel/Octane sudah melakukan ini). Kuncinya: data harus lahir dan mati bersama request.

Session dengan Store Eksternal

Session adalah state yang justru harus bertahan lintas request — tetapi bukan di memory worker. Simpan di store eksternal, sesuai pola episode 10:

Session aman berbasis KV Redis
function loadSession(Psr\Http\Message\ServerRequestInterface $request, $cache): array
{
    $sessionId = $request->getCookieParams()['session'] ?? bin2hex(random_bytes(16));
    $data = $cache->get('session:' . $sessionId) ?? [];
    $data['_id'] = $sessionId;
 
    return $data;
}
 
function saveSession(array $data, $cache): void
{
    $cache->set('session:' . $data['_id'], $data, 1800);
}

Keuntungan: session bisa dibaca worker mana pun di pool dan node mana pun di cluster — tidak ada ketergantungan pada worker tertentu. Ini juga persiapan natural untuk scaling horizontal (episode 22).

Framework juga menyediakan ini: Laravel SESSION_DRIVER=redis, Symfony session handler Redis. Pilih store eksternal, jangan default file yang bisa memakai path lokal worker.

Refactor Menuju Worker-Safe

Langkah praktis men-scan aplikasi kalian:

  1. Cari static dan global: grep static $, global $, $_SESSION langsung di kode.
  2. Cek singleton yang bisa terisi data: property non-final yang di-assign per-request.
  3. Audit koneksi: pastikan transaksi DB selalu commit/rollback per request (Octane punya listener untuk ini).
  4. Reset buffer output di awal request bila ada ob_start/output buffering.
  5. Test dengan max_jobs besar: jalankan banyak request berurutan di worker yang sama, cek respons tetap independen.
Cari kandidat state tidak aman
rg -n "static \$|global \$|\$_SESSION" app/ src/

Checklist Worker-Safe

PertanyaanJawaban aman
Apakah data user disimpan di property class?Tidak — lewat request/store
Apakah session memakai file lokal?Tidak — Redis/DB
Apakah singleton hanya berisi dependency?Ya
Apakah transaksi selalu ditutup per request?Ya
Apakah ada global mutable?Tidak

Penutup

Pada episode 14 ini, kalian telah memahami aturan paling penting app server PHP.

Inti yang harus dibawa pulang:

  • Worker persistent = state di memory bertahan lintas request — berbahaya.
  • Jangan simpan state request di property/static/singleton.
  • State per-request dibawa lewat request; state lintas-request di store eksternal.
  • Session via Redis/KV — worker-safe dan siap scaling.
  • Scan kode dengan grep untuk kandidat state tidak aman.

Di episode 15 selanjutnya, kita masuk ke TLS/HTTPS & reverse proxy — mengaktifkan http.ssl dengan sertifikat, atau menaruh Nginx/Caddy di depan RoadRunner, serta menyusun deployment HTTPS production yang benar. Sampai jumpa di episode 15!

Belajar RoadRunner - Sessions, State & Stateless Best Practice | Belajar RoadRunner