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=databasefor reliability - β
Use
SESSION_STORE=redisfor performance - β
Set reasonable
SESSION_LIFETIME(60-120 minutes) - β
Enable
SESSION_HTTP_ONLY=truefor security - β
Use
SESSION_SECURE_COOKIE=truein 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
π Related DocumentationΒΆ
- Infrastructure Map - Complete service overview
- Monitoring Tools - Horizon, Telescope, Pulse
- Database Migrations - Database setup
π 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! π