Skip to content

Session Architecture - Database-First with Redis CacheΒΆ

Complete guide to RawDigs session management using a reliable database-first approach with Redis caching.

🎯 Architecture Overview¢

RawDigs uses a database-first session architecture that provides:

  • βœ… Reliability: PostgreSQL as source of truth (always available)
  • βœ… Performance: Redis cache layer for fast access
  • βœ… Automatic Fallback: Sessions work even if Redis fails
  • βœ… Auditability: All sessions tracked in database
  • βœ… Scalability: Works in distributed environments
graph TB
    subgraph "Client"
        BROWSER[Web Browser]
    end

    subgraph "Application Layer"
        APP[Laravel Application]
        SESSION_MANAGER[Session Manager]
    end

    subgraph "Cache Layer - Optional"
        REDIS[(Redis DB 2<br/>Session Cache)]
    end

    subgraph "Primary Storage - Always Available"
        PGSQL[(PostgreSQL<br/>sessions table)]
    end

    BROWSER -->|Request with Cookie| APP
    APP --> SESSION_MANAGER
    SESSION_MANAGER -->|1. Try Cache First| REDIS
    REDIS -.->|Cache Hit| SESSION_MANAGER
    REDIS -.->|Cache Miss| SESSION_MANAGER
    SESSION_MANAGER -->|2. Fallback to DB| PGSQL
    PGSQL -->|Always Reliable| SESSION_MANAGER
    SESSION_MANAGER -->|Write to Cache| REDIS
    SESSION_MANAGER -->|Write to DB| PGSQL

πŸ“Š Data FlowΒΆ

Read OperationΒΆ

sequenceDiagram
    participant Browser
    participant Laravel
    participant Redis Cache
    participant PostgreSQL

    Browser->>Laravel: Request with session cookie
    Laravel->>Redis Cache: Check cache (DB 2)

    alt Cache Hit
        Redis Cache-->>Laravel: Return session data
        Laravel->>Browser: Response
    else Cache Miss or Redis Down
        Laravel->>PostgreSQL: Query sessions table
        PostgreSQL-->>Laravel: Return session data
        Laravel->>Redis Cache: Update cache (if available)
        Laravel->>Browser: Response
    end

Write OperationΒΆ

sequenceDiagram
    participant Browser
    participant Laravel
    participant Redis Cache
    participant PostgreSQL

    Browser->>Laravel: Request modifies session
    Laravel->>PostgreSQL: Write session data (primary)
    PostgreSQL-->>Laravel: Confirm write

    alt Redis Available
        Laravel->>Redis Cache: Update cache (DB 2)
        Redis Cache-->>Laravel: Confirm
    else Redis Unavailable
        Note over Laravel,Redis Cache: Cache update skipped<br/>Session still works!
    end

    Laravel->>Browser: Response with updated cookie

βš™οΈ ConfigurationΒΆ

Environment VariablesΒΆ

######################################################################
# Session
# Config: config/session.php
# Database-first with Redis cache layer for optimal reliability
######################################################################

# Primary session driver - always use database for reliability
SESSION_DRIVER=database

# Database configuration
SESSION_CONNECTION=pgsql              # PostgreSQL connection
SESSION_TABLE=sessions                # Table name

# Redis cache layer (optional but recommended)
SESSION_STORE=redis                   # Cache driver
SESSION_CACHE_CONNECTION=session      # Redis connection name

# Session behavior
SESSION_LIFETIME=120                  # Minutes (2 hours)
SESSION_EXPIRE_ON_CLOSE=false         # Persist after browser close
SESSION_ENCRYPT=false                 # No encryption needed

# Cookie configuration
SESSION_COOKIE=rawdigs_session        # Cookie name
SESSION_PATH=/                        # Cookie path
SESSION_DOMAIN=                       # Cookie domain (empty for current)
SESSION_SECURE_COOKIE=false           # HTTPS only (set true in production)
SESSION_HTTP_ONLY=true                # Prevent JavaScript access
SESSION_SAME_SITE=lax                 # CSRF protection
SESSION_PARTITIONED_COOKIE=false      # Partitioned cookies

Database SchemaΒΆ

The sessions table structure:

CREATE TABLE sessions (
    id VARCHAR(255) PRIMARY KEY,                -- Session ID (random string)
    user_id BIGINT NULLABLE,                    -- Authenticated user ID
    ip_address VARCHAR(45) NULLABLE,            -- Client IP address
    user_agent TEXT NULLABLE,                   -- Browser user agent
    payload TEXT NOT NULL,                      -- Serialized session data
    last_activity INTEGER NOT NULL              -- Unix timestamp
);

-- Indexes for performance
CREATE INDEX sessions_user_id_index ON sessions(user_id);
CREATE INDEX sessions_last_activity_index ON sessions(last_activity);

Redis ConfigurationΒΆ

Redis DB 2 is used as the session cache layer:

REDIS_HOST=redis
REDIS_PORT=6379
REDIS_SESSION_DB=2                    # Dedicated database for sessions
REDIS_PASSWORD=null

πŸ”„ How It WorksΒΆ

1. Session CreationΒΆ

When a user first visits:

// Laravel automatically:
1. Generates random session ID
2. Creates session record in PostgreSQL
3. Caches session data in Redis (if available)
4. Sets cookie in browser response

2. Session ReadingΒΆ

On subsequent requests:

// Laravel automatically:
1. Reads session ID from cookie
2. Checks Redis cache first (fast)
3. Falls back to PostgreSQL if cache miss
4. Updates cache with fresh data
5. Returns session data to application

3. Session WritingΒΆ

When session data changes:

// Laravel automatically:
1. Updates session in PostgreSQL (source of truth)
2. Updates Redis cache (if available)
3. Updates cookie if needed

4. Session CleanupΒΆ

Old sessions are automatically cleaned:

// Laravel lottery system (default: 2% chance per request)
// Deletes sessions older than SESSION_LIFETIME

πŸš€ BenefitsΒΆ

Reliability FirstΒΆ

  • Database is Always Available: PostgreSQL ensures sessions never fail
  • Redis is Optional: If Redis is down, sessions continue working
  • No Data Loss: All session data persisted to disk
  • Distributed Environments: Works across multiple app servers

Performance OptimizedΒΆ

  • Fast Reads: Redis cache provides sub-millisecond access
  • Efficient Writes: Database writes are async from user perspective
  • Automatic Caching: Laravel handles cache population
  • Smart Fallback: Minimal performance impact when cache unavailable

Security & ComplianceΒΆ

  • Audit Trail: All sessions recorded in database
  • User Tracking: Link sessions to authenticated users
  • IP Tracking: Monitor session origins
  • Device Tracking: Track user agents

πŸ› οΈ Management CommandsΒΆ

View Current SessionsΒΆ

# Count total sessions
sail artisan tinker --execute="echo DB::table('sessions')->count();"

# View active sessions
sail artisan tinker --execute="DB::table('sessions')->latest('last_activity')->take(5)->get()->toArray()"

# View sessions for specific user
sail artisan tinker --execute="DB::table('sessions')->where('user_id', 1)->get()->toArray()"

Clear Old SessionsΒΆ

# Clear expired sessions manually
sail artisan tinker --execute="DB::table('sessions')->where('last_activity', '<', now()->subMinutes(120)->timestamp)->delete()"

# Clear all sessions
sail artisan tinker --execute="DB::table('sessions')->truncate()"

Monitor Session StorageΒΆ

# Check database table size
sail artisan db:show --database=pgsql

# Check Redis keys (if Redis is cache layer)
docker exec rawdigs-core-app-redis-1 redis-cli -n 2 KEYS "*"

# Count Redis session keys
docker exec rawdigs-core-app-redis-1 redis-cli -n 2 DBSIZE

πŸ” DebuggingΒΆ

Verify ConfigurationΒΆ

# Check session driver
sail artisan tinker --execute="echo config('session.driver');"

# Check database connection
sail artisan tinker --execute="echo config('session.connection');"

# Check cache store
sail artisan tinker --execute="echo config('session.store');"

# Check all session config
sail artisan tinker --execute="print_r(config('session'));"

Test Session FunctionalityΒΆ

// In routes/web.php
Route::get('/test-session', function () {
    // Write to session
    session(['test_key' => 'Hello from ' . now()]);

    // Read from session
    $value = session('test_key');

    // Check session driver info
    $driver = config('session.driver');
    $connection = config('session.connection');
    $store = config('session.store');

    return [
        'session_value' => $value,
        'session_id' => session()->getId(),
        'driver' => $driver,
        'connection' => $connection,
        'cache_store' => $store,
        'database_records' => DB::table('sessions')->count(),
    ];
});

Then visit: http://localhost/test-session

Check Database SessionsΒΆ

# View session table
sail artisan tinker --execute="
    \$sessions = DB::table('sessions')->get();
    foreach(\$sessions as \$session) {
        echo 'ID: ' . \$session->id . ' | ';
        echo 'User: ' . (\$session->user_id ?? 'guest') . ' | ';
        echo 'IP: ' . \$session->ip_address . ' | ';
        echo 'Last Activity: ' . date('Y-m-d H:i:s', \$session->last_activity) . PHP_EOL;
    }
"

πŸ” Production ConsiderationsΒΆ

Security SettingsΒΆ

Update for production:

SESSION_SECURE_COOKIE=true            # Require HTTPS
SESSION_DOMAIN=.rawdigs.com           # Specific domain
SESSION_LIFETIME=60                   # Shorter lifetime (1 hour)

Performance TuningΒΆ

# Increase cache effectiveness
REDIS_SESSION_DB=2                    # Dedicated database
SESSION_LIFETIME=120                  # Balance security vs. cache hits

Scheduled CleanupΒΆ

Add to app/Console/Kernel.php:

protected function schedule(Schedule $schedule): void
{
    // Clean up old sessions daily
    $schedule->call(function () {
        DB::table('sessions')
            ->where('last_activity', '<', now()->subMinutes(config('session.lifetime'))->timestamp)
            ->delete();
    })->daily();
}

MonitoringΒΆ

// Monitor session table size
$schedule->call(function () {
    $count = DB::table('sessions')->count();
    $oldCount = DB::table('sessions')
        ->where('last_activity', '<', now()->subHour()->timestamp)
        ->count();

    if ($count > 10000) {
        // Alert: Too many sessions
    }

    if ($oldCount > 1000) {
        // Alert: Cleanup not running properly
    }
})->hourly();

πŸŽ“ Best PracticesΒΆ

Do's βœ…ΒΆ

  • βœ… Keep SESSION_DRIVER=database for reliability
  • βœ… Use SESSION_STORE=redis for performance
  • βœ… Set reasonable SESSION_LIFETIME (60-120 minutes)
  • βœ… Enable SESSION_HTTP_ONLY=true for security
  • βœ… Use SESSION_SECURE_COOKIE=true in production
  • βœ… Schedule regular session cleanup
  • βœ… Monitor session table growth
  • βœ… Track authenticated sessions via user_id

Don'ts ❌¢

  • ❌ Don't use SESSION_DRIVER=redis (no fallback)
  • ❌ Don't store large objects in sessions (use cache instead)
  • ❌ Don't disable SESSION_HTTP_ONLY
  • ❌ Don't use long session lifetimes (security risk)
  • ❌ Don't forget to clean up old sessions
  • ❌ Don't store sensitive data in sessions unencrypted

πŸ”— External ResourcesΒΆ


SummaryΒΆ

βœ… Configured: Database-first sessions with Redis cache
βœ… Tested: Sessions storing in PostgreSQL
βœ… Verified: Redis caching working
βœ… Reliable: Automatic fallback when Redis unavailable
βœ… Performant: Sub-millisecond reads from Redis
βœ… Auditable: All sessions tracked in database

Your session architecture is now production-ready! πŸš€