class

KemalIdentity::Sweeper

Inherits Reference < Object

Reclaims disk from rows nothing will read again.

It is not a correctness mechanism, and that is the whole point

Every check this shard makes is evaluated on read: a session past its deadline fails when it is next resolved, a revoked one fails immediately, an action token's expiry is part of the statement that consumes it. Nothing waits for a sweeper.

That is a direct lesson from kemal-session issue #116, where timeout only marked a session for deletion at the next GC pass, so a session past its timeout stayed valid until the sweeper happened to run — and a read could refresh its access time before any expiry check, reviving it. The rule this shard follows instead is correctness on read, sweeping for disk only (docs/02-security-model.md), which means a sweeper that never runs costs storage and nothing else.

It does not start itself

run_every returns the fiber it spawned and the application decides whether to call it. A library that silently starts background work is a library that surprises people in a multi-process deployment — four processes behind a load balancer would run four sweepers against one database, which is wasteful at best (docs/03-data-model.md).

sweeper = KemalIdentity::Sweeper.new(KemalIdentity.app)
sweeper.run_every(1.hour)

For anything larger than a single process, run sweep from a cron job or a scheduler instead, so exactly one of them is sweeping.

Remember-me tokens are swept, never early

A spent remember-me token's row is the evidence of a replay. Delete it before it expires and a stolen token coming back looks unknown rather than replayed, so nobody is told their cookie may have been copied. The repository's delete_expired already refuses to touch anything before its expiry, and this only ever asks for that (blueprints/0012-remember-me.md).

Constants

DEFAULT_REVOKED_RETENTION = 1.day

How long a revoked session row is kept after it is revoked.

Not zero: the row is the evidence behind "you were signed out of this device", and it costs almost nothing to keep for a day.

This only reclaims anything for long-lived sessions

A revoked row is swept by delete_expired at its absolute deadline regardless, so this matters only when the retention window is shorter than the session's remaining lifetime. With the default twelve-hour absolute timeout, a session revoked at any point is gone within twelve hours anyway and this window never fires — it earns its keep when absolute_timeout is measured in weeks, where a logout would otherwise leave a row sitting until the deadline it will never reach.

A day is short enough to reclaim, long enough to answer "was I signed out?".

Constructors

new(app : Application, revoked_retention : Time::Span = DEFAULT_REVOKED_RETENTION)
Source

Instance methods

run_every(interval : Time::Span) : Fiber

Sweeps every interval until stop is called, and returns the fiber doing it.

The first sweep happens after the first interval, not immediately: a process that restarts often would otherwise sweep on every boot, which is the opposite of what a background job is for.

Source
running?
Source
stop

Asks the loop to stop. It finishes its current wait first, so this is not instant.

Source
sweep

Runs one sweep across every table this shard owns.

Synchronous, so a cron job or a spec can call it directly and know it finished.

Source