Symfony Session Handlers: Redis vs Memcached vs Database vs Files

Or: Why “just use Redis” isn’t always the answer

“We’re scaling to multiple servers. Should we switch to Redis sessions?”

I’ve heard this question dozens of times. The answer is almost always “it depends”—but not in the frustrating, non-committal way. There are clear criteria that make the choice obvious once you know them.


The Four Options

Permalink to "The Four Options"

Symfony supports four session handlers out of the box:

Handler Storage Scaling Persistence
NativeFileSessionHandler Local filesystem Single server Survives restarts
PdoSessionHandler Database (MySQL, PostgreSQL) Multi-server Survives restarts
MemcachedSessionHandler Memcached server Multi-server Lost on restart
RedisSessionHandler Redis server Multi-server Configurable

Each has trade-offs.


File Sessions: The Reliable Default

Permalink to "File Sessions: The Reliable Default"
# config/packages/framework.yaml
framework:
  session:
    handler_id: null # Uses PHP's default file handler
    save_path: "%kernel.project_dir%/var/sessions/%kernel.environment%"

When to use:

  • Single-server deployments
  • Development environments
  • Applications where simplicity matters more than scale

The good:

  • Zero configuration
  • Battle-tested reliability
  • Built-in session locking (no race conditions)
  • Survives server restarts

The bad:

  • Can’t share sessions across multiple app servers
  • Filesystem I/O can become a bottleneck
  • Session files accumulate (needs garbage collection)

Production tip: If you’re on a single server and it handles your load, file sessions are perfectly fine. Don’t over-engineer.


Database Sessions: ACID Guarantees

Permalink to "Database Sessions: ACID Guarantees"
# config/packages/framework.yaml
framework:
  session:
    handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
// config/services.yaml
services:
    Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
        arguments:
            - '%env(DATABASE_URL)%'
            - lock_mode: 1  # LOCK_ADVISORY

When to use:

  • Multi-server deployments where you already have a database
  • Applications requiring transactional consistency
  • When session data must survive infrastructure changes

The good:

  • Works across multiple app servers
  • ACID guarantees (sessions won’t corrupt)
  • Configurable locking modes
  • Sessions survive restarts and deployments

The bad:

  • Adds database load
  • Lock contention under high concurrency
  • Slower than in-memory solutions

Lock Modes Explained

Permalink to "Lock Modes Explained"

PdoSessionHandler offers three lock modes:

use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;

// No locking - fastest, but race conditions possible
PdoSessionHandler::LOCK_NONE

// Advisory locking - balanced approach
PdoSessionHandler::LOCK_ADVISORY

// Transactional locking - safest, but slowest
PdoSessionHandler::LOCK_TRANSACTIONAL

My recommendation: Start with LOCK_ADVISORY. Only drop to LOCK_NONE if you’ve profiled and confirmed it’s a bottleneck—and you understand the race condition risks.


Redis Sessions: The Scalable Choice

Permalink to "Redis Sessions: The Scalable Choice"
# config/packages/framework.yaml
framework:
  session:
    handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler
// config/services.yaml
services:
    Redis:
        class: Redis
        calls:
            - connect: ['%env(REDIS_HOST)%', '%env(int:REDIS_PORT)%']

    Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler:
        arguments:
            - '@Redis'

When to use:

  • High-traffic applications
  • Horizontal scaling with multiple app servers
  • When you need sub-millisecond session reads

The good:

  • Blazing fast (in-memory)
  • Scales horizontally
  • Can persist to disk (RDB/AOF)
  • Supports clustering

The bad:

  • No native session locking (this is a big one)
  • Additional infrastructure to maintain
  • Memory costs for large session data

The Locking Problem

Permalink to "The Locking Problem"

This deserves its own article (coming next), but here’s the summary:

Redis does not perform session locking. You can face race conditions when accessing sessions. For example, you may see an “Invalid CSRF token” error. — Symfony Documentation

If your app makes concurrent AJAX requests that modify session data, Redis sessions can corrupt. The symptom: random logouts, lost flash messages, CSRF failures.

Workarounds:

  • Implement application-level locking
  • Use session_write_close() early
  • Consider a locking Redis session handler (like the one from snc/redis-bundle)

Memcached Sessions: Simple Caching

Permalink to "Memcached Sessions: Simple Caching"
# config/packages/framework.yaml
framework:
  session:
    handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler
// config/services.yaml
services:
    Memcached:
        class: Memcached
        calls:
            - addServer: ['%env(MEMCACHED_HOST)%', '%env(int:MEMCACHED_PORT)%']

    Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler:
        arguments:
            - '@Memcached'

When to use:

  • Simple distributed caching needs
  • When you already have Memcached for other caching
  • Stateless-ish applications where session loss is acceptable

The good:

  • Fast (in-memory)
  • Simple protocol
  • Works across multiple servers

The bad:

  • No persistence (sessions lost on restart)
  • No native locking (same race condition issues as Redis)
  • LRU eviction can randomly delete sessions under memory pressure

My take: Unless you already have Memcached in your stack, prefer Redis. It does everything Memcached does, plus persistence and more data structures.


The Decision Matrix

Permalink to "The Decision Matrix"
Requirement Best Choice
Single server, simple app Files
Multi-server, need reliability Database (PDO)
Multi-server, need speed Redis (with locking awareness)
Already using Memcached Memcached (but consider Redis)
ACID guarantees required Database (PDO)
Sub-millisecond reads Redis or Memcached
Session data must survive restarts Files, Database, or Redis with persistence

Migrating Between Handlers

Permalink to "Migrating Between Handlers"

Switching session handlers usually logs everyone out. Symfony’s MigratingSessionHandler solves this:

use Symfony\Component\HttpFoundation\Session\Storage\Handler\MigratingSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;

// Read from old handler, write to both
$handler = new MigratingSessionHandler(
    new PdoSessionHandler($pdo),      // Old handler (reads from here first)
    new RedisSessionHandler($redis)   // New handler (writes go to both)
);

Run this for a session lifetime (e.g., 24 hours), then switch to Redis-only:

$handler = new RedisSessionHandler($redis);

Zero-downtime migration without logging anyone out.


My Recommendations

Permalink to "My Recommendations"

For Most Applications

Permalink to "For Most Applications"

Start with file sessions. If you’re not running multiple app servers, you don’t need distributed session storage. File sessions are reliable, fast enough, and have proper locking.

When You Scale

Permalink to "When You Scale"

Move to database sessions first. You already have a database. The added load is usually negligible, and you get proper locking. Only switch to Redis if you’ve measured and confirmed the database is a bottleneck.

For High-Traffic Applications

Permalink to "For High-Traffic Applications"

Use Redis, but:

  1. Understand the locking limitations
  2. Implement proper safeguards (more on this in the next article)
  3. Configure persistence (RDB snapshots at minimum)

The “best” session handler depends on your constraints. File sessions work fine for most applications. When you need to scale, database sessions are a sensible middle ground. Redis is the right choice for high-traffic apps—but only if you handle the locking problem.

Next up: Redis Session Locking Pitfalls—the race conditions that catch everyone off guard.