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