Sessions
Documentation / Sessions / Store & watermarks

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 (runique migration up). Without it, the first request looking a session up in the database fails (no such table).

With a custom store (with_session_store(...)), this fallback isn't installed, and Runique can't revoke a user's sessions (signing other devices out, password reset): that's the store's job.


Several instances

The memory store belongs to one process. Behind a load balancer with several instances of the app:

  • Anonymous sessions (CSRF token, flash messages, anything stored before sign-in) live only in the memory of the instance that created them. A request sent to another instance doesn't find them: a form fails its CSRF check, a flash message disappears.
  • Authenticated sessions are shared through the database: the database is the authority. On every request, an instance holding the session in memory still checks its row exists in the database. A logout, a password reset or a close_user_sessions on one instance deletes the row: the other instances refuse their copy on the next request.

Until a shared store exists for anonymous sessions, route each visitor to the same instance (sticky sessions, also called session affinity, on the load balancer) if your pages use forms before sign-in. With a single instance, none of this applies.


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 runique::tower_sessions::cookie::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)24 h
Anonymous session TTLwith_anonymous_session_duration(Duration)5 min
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. with_session_duration is called on the builder; the four other methods, inside .middleware(|m| …). Both durations are runique::tower_sessions::cookie::time::Duration.