Sessions

Store & watermarks

CleaningMemoryStore

Why

The tower-sessions MemoryStore does not implement cleanup of expired sessions. Without purging, every request from a bot without cookies creates a session that is never deleted — memory grows unboundedly.

CleaningMemoryStore solves this with three mechanisms:

MechanismTriggerBehavior
Periodic timerEvery 60s (configurable)Deletes all expired sessions
Low watermark128 MB (configurable)Async purge of expired anonymous sessions
High watermark256 MB (configurable)Synchronous emergency purge + 503 refusal if still exceeded

Size estimation

Each record is estimated as: 24 bytes (UUID + expiry) + JSON length of session data.

A warning is logged if a record exceeds 50 KB (image or file accidentally stored in session).


Watermark system

Low watermark (128 MB default)

When the total store size exceeds this threshold, a non-blocking background cleanup is launched via tokio::spawn. It removes expired anonymous sessions without blocking the current request.

High watermark (256 MB default)

When the size exceeds this threshold at session creation time:

  1. Pass 1 — removes expired anonymous sessions
  2. Pass 2 — if still exceeded, removes all expired sessions (including authenticated)
  3. Refusal — if still exceeded, returns 503 Service Unavailable

Protected sessions are never sacrificed in pass 1.



Persistence — database fallback

By default, the store is purely in-memory. When the orm feature is active, a database fallback is automatically enabled for authenticated sessions — no additional configuration required.

  • Read: memory first, DB on miss (session survives server restart)
  • Write: synchronous to memory + asynchronous to DB for sessions with user_id
  • Anonymous sessions: never persisted to DB

The eihwaz_sessions table must exist — it is created by the framework migrations.


Configuration

All the thresholds described above are tunable from the builder, inside the .middleware() block. No manual store construction is needed — the framework instantiates CleaningMemoryStore and applies these values.

use runique::prelude::*;
use time::Duration;

let app = RuniqueApp::builder(config)
    // Inactivity TTL before expiration (authenticated sessions)
    .with_session_duration(Duration::hours(2))
    .middleware(|m| {
        m
            // Low / high watermark — in bytes
            .with_session_memory_limit(64 * 1024 * 1024, 128 * 1024 * 1024)
            // Periodic purge timer interval — in seconds
            .with_session_cleanup_interval(30)
            // One connected device at a time per user
            .with_exclusive_login(true)
    })
    .build()
    .await?;
Mechanism (section above)Builder methodDefault
Periodic timerwith_session_cleanup_interval(secs)60 s
Low / High watermarkwith_session_memory_limit(low, high)128 MB / 256 MB
Authenticated session TTLwith_session_duration(Duration)
Anonymous session TTLwith_anonymous_session_duration(Duration)
Exclusive login (one device)with_exclusive_login(bool)false

Watermarks are expressed in bytes: 64 * 1024 * 1024 = 64 MB. The timer interval is in seconds.