Wednesday, 14 October 2026

Why Horizontal Scaling Exposes Bad State Management in PHP

Why Horizontal Scaling Exposes Bad State Management in PHP

Scaling a PHP application across multiple servers sounds simple:

1 server
   ↓
2 servers
   ↓
4 servers

But adding servers does not automatically make an application scalable.

In many cases, horizontal scaling exposes problems that already existed in the architecture — especially poor state management.

This is the next post in Batch 5: Scaling PHP Without Losing the Architecture.


What Is Horizontal Scaling?

Horizontal scaling means adding more application instances instead of making one server more powerful.

For example:

                Load Balancer
              /      |      \
             /       |       \
          App 1    App 2    App 3

Requests can now be handled by any application instance.

This gives you more capacity and can improve availability.

But it introduces an important rule:

Any request should be able to reach any application instance without breaking the application.

That is where state becomes a problem.


The Hidden Single-Server Assumption

A PHP application may accidentally depend on data stored inside one server.

For example:

$_SESSION['cart'] = $cart;

Or perhaps the application stores temporary data in:

/tmp
local filesystem
in-memory cache
static variables
local session files

With one server, everything appears fine.

Then you add another server.

A user logs in through App 1:

User
 ↓
App 1
 ↓
Session stored locally

Their next request reaches App 2:

User
 ↓
App 2
 ↓
Session not found

Nothing is technically wrong with the load balancer.

The problem is that the application assumed all future requests would return to the same machine.


Sticky Sessions Are Not a Real Architectural Fix

One common solution is sticky sessions.

The load balancer keeps sending the same user back to the same server.

User A → App 1
User B → App 2
User C → App 3

This can work, but it often hides the underlying architecture problem.

What happens when App 1 fails?

If important state exists only there, User A may lose it.

Sticky sessions can be useful operationally, but they should not become an excuse for letting important application state live inside one process or machine.


Shared State Should Live Outside the Application Instance

A horizontally scalable application usually treats application servers as replaceable.

Instead of:

App 1 owns user state
App 2 owns different user state

you want:

             Shared State
            /     |      \
         App 1  App 2   App 3

That shared state might live in:

  • a database
  • Redis or another shared cache
  • object storage
  • a queue
  • another dedicated service

The important point is not the technology.

The important point is ownership.

Application instances should not secretly become the authoritative owner of business-critical state.


Stateless Does Not Mean “No State”

This is an important distinction.

Applications still need state.

Users have sessions.

Orders have statuses.

Jobs have progress.

Carts contain products.

The goal is not to remove state.

The goal is to avoid tying important state to one replaceable application process.

A better model is:

Request
   ↓
Any App Instance
   ↓
Shared Authoritative State

Now App 1 can disappear and App 2 can continue serving the next request.


Local Files Can Cause the Same Problem

Consider file uploads.

An application writes:

App 1
└── uploads/invoice.pdf

Later the user requests the file, but the load balancer sends the request to App 3.

App 3 does not have it.

Again, horizontal scaling did not create the problem.

It revealed the fact that storage ownership was tied to one machine.

Moving shared files to object storage or another shared storage layer makes the boundary explicit.


Long-Running Workers Make State Problems Worse

Traditional PHP requests are short-lived, which can hide state leaks.

With workers or persistent runtimes, memory survives between executions.

Suddenly things like:

static $currentUser;

$GLOBALS['tenant'];

$container->set('request', $request);

can become dangerous if they are not reset correctly.

One user's state may survive long enough to affect another execution.

That is why scalable PHP architecture needs clear execution boundaries and disciplined lifecycle management.


A Useful Rule

When designing for horizontal scaling, ask:

If this application instance disappears right now, what important information disappears with it?

If the answer includes:

  • user identity
  • orders
  • workflow state
  • uploaded files
  • business decisions
  • retry information
  • job progress

then that state probably belongs somewhere more durable or explicitly shared.


Scaling Exposes Architecture

Horizontal scaling is not just an infrastructure decision.

It tests whether your application has clear boundaries around:

  • state
  • storage
  • sessions
  • caching
  • execution lifecycle
  • ownership

A system that only works correctly when every request reaches the same machine is not truly horizontally scalable.

The servers may multiply.

The architecture still has not.

No comments:

Post a Comment