Skip to main content

TTL / Expiry

Records can carry a time-to-live (TTL). After the TTL elapses:

  • The record is transparently filtered from get(), list(), count(), and search() results
  • The data still occupies disk space until the next compact(), which garbage-collects expired records permanently

Use TTL for session vectors, retrieval caches, ephemeral chat history, or any record with a natural expiry.

Setting a TTL​

Python​

# On insert/upsert (seconds from now)
db.upsert("session1", embedding, {"user": "alice"}, ttl=3600) # expires in 1 hour

# On an existing record
db.set_ttl("doc1", 86400) # expire 24 h from now
db.clear_ttl("doc1") # remove expiry, live forever

Node.js​

db.upsert('session1', embedding, { user: 'alice' }, { ttl: 3600 })
db.setTtl('doc1', 86400)
db.clearTtl('doc1')

Inspecting expiry​

expires_at (Python) / expiresAt (Node) on a Record exposes the absolute expiry timestamp in epoch seconds, or None/null when no TTL is set.

record = db.get("session1")
if record and record.get("expires_at"):
print(f"Expires at {record['expires_at']}")

Garbage collection​

Expired records are invisible to reads immediately. They are physically removed from disk only on compact():

db.compact()  # also folds the WAL into the snapshot

Schedule compact() periodically (cron, background job) when TTL is in heavy use, otherwise expired data accumulates on disk.

Transactions​

The ttl parameter works inside transactions too:

with db.transaction() as tx:
tx.upsert("session1", emb1, {"user": "alice"}, ttl=3600)
tx.upsert("session2", emb2, {"user": "bob"}, ttl=3600)

Common patterns​

Session vectors​

db.upsert(session_id, user_embedding, {"user": user_id}, ttl=3600)

Retrieval cache​

db.upsert(query_hash, query_embedding, {"results": result_ids}, ttl=300)

Sliding window​

Call set_ttl() on every read to refresh the expiry — turns the store into an LRU-by-time cache.

record = db.get(key)
if record:
db.set_ttl(key, 3600) # refresh