Qirava DMS docs
Maintenance: TTL sweep
Time-to-live (TTL) lets rows expire automatically. A table declares a TTL field holding a unix-seconds expiry; the sweeper deletes every row whose expiry is <= now. The sweep runs as a normal QQL DELETE per TTL table, so it honors the same durability and (importantly) is index-served when you declare a Filter index on the TTL field — turning the sweep into a range scan instead of a full scan. The sweeper is a public maintenance function the worker scheduler drives on a schedule. This page covers declaring TTL, how the sweep selects rows, and running it.
Declaring a TTL field#
A table sets its TTL field to the field that holds each row's expiry as a unix-seconds integer. In code this is TableDef::new(...).with_ttl("<field>"). A row is expired when <field> <= now. Declaring a Filter index on that field makes the sweep index-served (a range scan over expired keys), avoiding a full table scan.
TableDef::new("sessions", TableSchema::Schemaless)
.with_ttl("exp")
.with_index(IndexDef::new("exp_idx", IndexKind::Filter, vec!["exp".into()]))How the sweep selects rows#
sweep_expired(now) gathers every table that declared a TTL field and, for each, runs the equivalent of DELETE WHERE <ttl_field> <= now. The <= range is served by the Filter index when present (an ordered range scan), else by a scan. It returns the total number of rows reclaimed across all TTL tables. Because it uses the normal DELETE path, expirations are WAL-logged and indexes are maintained.
Request
-- sessions has TTL field `exp` + Filter index on `exp`
INSERT INTO sessions { id: 1, exp: 100 } -- expired
INSERT INTO sessions { id: 2, exp: 5000000000 } -- future
INSERT INTO sessions { id: 3, exp: 200 } -- expired
-- sweep at now = 1000
SELECT id FROM sessions -- after the sweepResponse
// sweep_expired(1000) reclaims 2 rows (id 1 and 3)
// SELECT id FROM sessions -> { "error": null, "data": [ { "id": 2 } ], "root": { "count": 1 } }
// a second sweep at now = 1000 reclaims 0Running the sweep on a schedule#
The sweeper is exposed as a public function (db.ttl_sweep) so the worker scheduler can run it like any other function, on a schedule. It runs through the executor, so it composes with the same concurrency and durability as normal writes.
Request
-- t has TTL field `exp`
INSERT INTO t { id: 1, exp: 1 } -- far past
-- scheduler invokes db.ttl_sweep
SELECT id FROM tResponse
// after the swept run -> { "error": null, "data": [], "root": { "count": 0 } }