Belajar FrankenPHP - Session, Persistence & State
Episode 13 of 25

Belajar FrankenPHP - Session, Persistence & State

Mengelola session dan state di mode worker: mengapa session file kurang ideal, memindahkan session ke Redis dengan handler Redis, menjaga aplikasi stateless agar worker tetap sehat, dan memahami batasan enable_post_data_reading serta request_body_timeout.

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

Pendahuluan

Setelah episode 12 membangun observability, episode ini menangani salah satu kejutan paling umum bagi developer PHP yang baru pindah ke app server: session dan state. Di PHP-FPM, session disimpan di file per-request dan setiap proses punya lifecycle pendek, jadi kebocoran state jarang terasa. Di mode worker, aplikasi hidup terus — dan desain state yang ceroboh langsung terlihat.

Mengapa penting? Dua masalah menanti di mode worker: (1) session berbasis file tidak berbagi data antar instance dan bisa macet karena lock, dan (2) state aplikasi yang bocor antar request adalah sumber bug paling halus. Episode ini memberi kalian pola yang benar untuk keduanya.

Stateless: Prinsip Dasar Worker Mode

Worker mode menuntut aplikasi stateless — data yang bersifat per-request tidak boleh disimpan di dalam proses. Ingat daftar dari episode 5: variabel static, static property, dan variabel global bertahan antar request. Setiap data yang berkaitan dengan satu pengguna atau satu request harus hidup di luar proses worker:

DataTempat yang benar
Login penggunaSession (Redis) / token
Keranjang belanjaSession / database
Cache berumur pendekRedis / database
KonfigurasiEnv var (dibaca saat start)

Aturan praktisnya: jika datanya milik satu pengguna, ia tidak boleh tinggal di memori worker.

Masalah Session File di Mode Worker

Default PHP menyimpan session sebagai file (session.save_handler = files). Di mode worker (atau multi-instance), ini bermasalah:

  1. Locking: PHP mengunci file session selama request. Jika worker yang sama memproses dua request paralel untuk user yang sama, request kedua menunggu — latency naik.
  2. Tidak terbagi: dua instance server tidak bisa berbagi session file. Scaling horizontal (episode 20) langsung patah.
  3. Sampah file: file session menumpuk; cleanup session.gc berjalan tidak menentu.

Solusi: Session di Redis

Pindahkan session handler ke Redis — storage eksternal yang dibagikan antar instance dan bebas lock. Install ekstensi redis/igbinary dulu (episode 21 membahas build ekstensi):

Caddyfile - session Redis via php_ini
{
    frankenphp {
        php_ini session.save_handler redis
        php_ini session.save_path "tcp://redis:6379"
        php_ini session.gc_maxlifetime 1440
    }
}
 
localhost {
    root * public/
    php_server
}

Atau setel di php.ini:

php.ini - session Redis
session.save_handler = redis
session.save_path = "tcp://redis:6379?timeout=2"
session.gc_maxlifetime = 1440

Verifikasi dari PHP:

Cek session handler
<?php
var_dump(ini_get('session.save_handler')); // "redis"

Tip

Redis bukan satu-satunya opsi. Handler lain yang eksternal dan dibagikan (memcached, database) juga memenuhi syarat. Kuncinya bukan teknologi, melainkan storage di luar proses worker — itu yang membuat session aman dari restart worker dan bisa dibagi antar instance.

Redis untuk Cache Juga

Selain session, cache aplikasi (Laravel CACHE_STORE=redis, Symfony cache.adapter.redis) sebaiknya diarahkan ke Redis:

Laravel pakai Redis
php artisan config:set cache.store redis
php artisan config:cache

Kombinasi session + cache di Redis membuat worker benar-benar bisa dianggap "tanpa memori" — setiap request membaca state dari sumber eksternal yang konsisten.

Batasan enable_post_data_reading dan request_body_timeout

Beberapa aplikasi butuh membaca body request di luar alur normal PHP (misal untuk signature validation atau raw body parsing). Direktif enable_post_data_reading mengontrol apakah PHP membaca data POST otomatis. Di FrankenPHP, kontrol utama adalah request_body_timeout — batas waktu idle untuk membaca body:

Caddyfile - request_body_timeout
localhost {
    root * public/
    php_server {
        request_body_timeout 60s
        env ENABLE_POST_DATA_READING 0
    }
}
  • request_body_timeout 60s (default) — klien yang berhenti mengirim (slow POST) diputus; upload yang stabil mengalir lancar.
  • Nilai 0 menonaktifkan batas — hati-hati dengan risiko slow-loris.
  • Untuk aplikasi yang membaca raw body, set ENABLE_POST_DATA_READING=0 agar PHP tidak menelan php://input sebelum kalian membacanya.

State Persistence yang Sengaja

Tidak semua state itu musuh. Di worker mode, cache dalam memori yang tidak bergantung request adalah anugerah performa — misalnya metadata konfigurasi yang dibaca sekali saat start, atau koneksi database yang disimpan dalam variabel worker. Yang perlu dihindari adalah state yang menyimpan data per-request secara diam-diam:

State worker yang salah vs benar
<?php
// SALAH: data per-request bocor ke request lain
class AuthHolder {
    public static ?string $userId = null;
}
 
// BENAR: konfigurasi read-only, dibaca sekali
class ConfigHolder {
    public static array $settings = [];
}
// (diisi saat bootstrap, tidak pernah diubah per-request)

Framework modern (Laravel Octane, Symfony) menangani reset service otomatis — gunakan itu daripada menulis ulang mekanisme sendiri.

Danger

Pola "singleton service yang menyimpan current user" adalah bug worker paling umum di aplikasi PHP. Framework seperti Symfony memecahkannya lewat ResetInterface (episode 7) dan Laravel via Octane state reset — jangan berinovasi di area ini jika tidak perlu.

Common Pitfalls

  • Session file tetap aktif di aplikasi yang dipindah ke worker: gejala aneh seperti login hilang acak atau request menggantung (lock). Periksa session.save_handler dulu.
  • $_ENV di-tulis saat request (episode 5): kebocoran antar request, sulit dilacak.
  • Cache dalam memori per-user: misal menyimpan hasil query per-user di static property — ini mencampur data antar pengguna.
  • Restart worker menghapus cache: cache dalam memori hilang saat worker restart; jangan bergantung padanya untuk data yang harus persisten.

Penutup

Pada episode 13 ini, kalian telah mengelola session dan state di mode worker.

Inti yang harus dibawa pulang:

  • Worker mode menuntut aplikasi stateless: data per-request harus di luar proses.
  • Session file bermasalah (lock + tidak terbagi); pindah ke Redis handler.
  • Arahkan cache aplikasi ke storage eksternal yang sama.
  • request_body_timeout melindungi dari slow POST; ENABLE_POST_DATA_READING=0 untuk raw body.
  • State read-only yang dibangun saat bootstrap itu baik; state per-request yang mengintip adalah racun.

Di episode 14 selanjutnya kita memasuki fase keamanan: Security — TLS, Headers & OWASP — memastikan HTTPS only, memasang security headers (CSP, HSTS, X-Frame-Options), dan mengeraskan worker dari eksposur yang tidak perlu. Sampai jumpa di episode 14!

Belajar FrankenPHP - Session, Persistence & State | Belajar FrankenPHP