TTL database
RocksDb.OpenWithTtl opens a database where every key carries an implicit time-to-live. Once a key is older than ttlSeconds, compaction is allowed to drop it. TTL is not exact-expiry — keys are eligible for removal but only actually vanish during the next compaction that touches their SST file.
Upstream reference: Time-to-Live wiki.
Opening a TTL database
var options = new DbOptions().SetCreateIfMissing(true);
using var db = RocksDb.OpenWithTtl(
options,
path,
ttlSeconds: 60 * 60 * 24); // 1 day
After this, every Put records a timestamp alongside the value. Compaction filters keys whose timestamp is older than ttlSeconds.
Semantics
- TTL is per-database, not per-key. All keys share the same TTL.
- Reads of expired keys may still succeed until compaction removes them. If you need strict expiry, layer your own timestamp on the value and check at read time.
- Compaction does the work. A key that never sees compaction stays around forever. For caches you usually want background compaction enabled (the default).
- TTL counts from
Puttime. Updating a key resets its TTL.
Typical use cases
| Pattern | Why TTL fits |
|---|---|
| HTTP / RPC response cache | Stale entries fall out without manual eviction. |
| Idempotency keys | Forget request IDs older than the retry window. |
| Rate-limit buckets | Counters that auto-purge after their window. |
| Short-lived dedupe set | Drop "seen" markers once the dedupe horizon passes. |
When TTL is the wrong tool
- You need exact expiry. TTL is compaction-driven; expired keys may be readable for a while.
- You need per-key TTLs. Use a value-side timestamp and a periodic sweep.
- You mix TTL and non-TTL data. Use column families — one CF with TTL on a
OpenWithTtl-style configuration won't help, but you can manage expiry yourself per CF.
Worked example: caching layer
public sealed class TtlCache : IDisposable
{
private readonly RocksDb _db;
public TtlCache(string path, TimeSpan ttl)
{
var options = new DbOptions()
.SetCreateIfMissing(true)
.IncreaseParallelism(Environment.ProcessorCount);
_db = RocksDb.OpenWithTtl(options, path, (int)ttl.TotalSeconds);
}
public void Set(string key, string value) => _db.Put(key, value);
public string? Get(string key) => _db.Get(key);
public void Dispose() => _db.Dispose();
}
using var cache = new TtlCache("/tmp/cache", TimeSpan.FromHours(1));
cache.Set("response:42", "...");