Skip to content

CONCEPT Cited by 1 source

Shared-nothing PHP request model

Definition

The shared-nothing PHP request model is PHP's foundational language-runtime design decision that every HTTP request executes in a fresh PHP process image, with no state carried across requests. At the end of every request, PHP tears down the interpreter state, releases all file handles, closes all database connections, and returns to a clean slate.

This is a language-runtime property, not a framework choice — it predates Laravel, Symfony, WordPress, and every modern PHP framework by 20+ years. It was a deliberate simplifying assumption from PHP's early web-origins: each request is independent; the web server forks / runs CGI / uses FPM to dispatch a fresh PHP interpreter; when the request is done, the interpreter exits.

Canonical statement

"PHP, by design, shares nothing across requests. This means at the end of every request, PHP will close the connection to the database."

— Matthieu Napoli, PlanetScale blog, 2023-05-03 (Source: sources/2026-04-21-planetscale-serverless-laravel-applications-with-aws-lambda-and-planetscale)

What "shared nothing" eliminates

  • Connection pools across requests — a PDO connection opened on request N does not survive to request N+1. Any application- level "connection pool" lasts for at most one request.
  • Compiled template caches — Blade / Twig templates recompile (or re-load-from-disk) per request unless a framework-specific opcache is configured.
  • Parsed config / route-table / service-container state — Laravel's service container boots per request; the configuration cache files bootstrap/cache/config.php help reduce the cost but the container graph still re-resolves.
  • Authentication / authorization caches — user record, policy cache, role resolution all re-hit the DB unless explicitly session-cached.

The opposite: persistent-process runtimes

Most modern web stacks have persistent-process request models:

  • Node.js — one Node process, event loop handles thousands of requests, DB connections live for the process lifetime.
  • Python ASGI (FastAPI, Starlette) — one ASGI app instance, async event loop, persistent DB pool.
  • Ruby Puma — worker processes serve many requests each.
  • Go net/http — single process, goroutine per request, shared DB pool.
  • Java Spring Boot / servlet containers — single JVM, thread pool, persistent Hibernate session factories and DB pools.

All of these amortise expensive setup (DB connection, TLS handshake, ORM bootstrap, framework wiring) across many requests. PHP's shares-nothing model pays that setup cost on every request.

How classic PHP hid the cost

In a traditional PHP deployment (LAMP stack, nginx + php-fpm), persistent database connections were the folk workaround — mysqli_pconnect() / PDO's PDO::ATTR_PERSISTENT => true instruct the PHP-FPM worker process to keep the DB connection open in the worker's own process memory between requests, even though the user-visible PHP execution context is torn down. The trick works because PHP-FPM pre-forks worker processes that persist across requests; the process stays alive, only the PHP execution context resets. A fixed pool of FPM workers × persistent connections = bounded DB connection count.

This is the LAMP-era "pool" — it's implicit, tied to the FPM worker pool shape, and only works when requests actually land on the same FPM worker process repeatedly.

Why serverless breaks the LAMP workaround

On AWS Lambda via Bref, the LAMP persistent-connection trick doesn't work the same way:

  1. Each Lambda invocation is a fresh execution environment in the worst case (cold start). No FPM worker to keep connections around in.
  2. Even warm Lambda execution contexts are isolated per-concurrent- invocation. 50 concurrent requests = 50 Lambda execution environments, each with its own fresh PHP state.
  3. PDO persistent-connection caching within one Lambda context is bounded by BREF_LOOP_MAX. Without explicit opt-in, every invocation pays the full setup cost.

The TLS handshake against PlanetScale (2-3 RTT over encrypted TLS) is the dominant per-request cost because: - PlanetScale requires SSL (no plaintext). - Query time is ~0.3 ms. - Handshake is ~10-50 ms. - PHP bootstrap + Laravel boot is ~20-60 ms.

Canonical benchmarked result: p50 75 ms under classic shares-nothing Bref, vs p50 14 ms with Laravel Octane which keeps the PHP process + DB connection warm across BREF_LOOP_MAX = 250 invocations. (Source: sources/2026-04-21-planetscale-serverless-laravel-applications-with-aws-lambda-and-planetscale.)

Why the constraint is architecturally interesting

Shares-nothing PHP is a counter-example to the "stateless good, stateful bad" simplification. Stateless per-request process boot is conceptually pure — easy to reason about, trivially horizontally scales — but it can't amortise setup costs across requests. In a world where setup is cheap (~1 ms), the tradeoff is fine. In a world where setup is expensive (TLS handshake, ORM bootstrap, service-container resolution), the tradeoff costs 5× latency.

The fix is selective persistence via Octane / Swoole / RoadRunner / FrankenPHP: keep the expensive parts (DB connections, compiled routes, service container) alive across requests, reset the cheap-to-reset parts (request scope, session, auth guard) per request. The programmer accepts a narrow window of cross-request state-leak risk in exchange for a large latency amortisation win.

Seen in

Last updated · 470 distilled / 1,213 read