# Time-to-Live (TTL) Support RocksDB supports automatic expiration of key-value pairs through Time-to-Live (TTL). Keys inserted into a TTL-enabled database will be automatically deleted after a specified duration. ## How TTL Works When you open a database with TTL enabled: 1. **Timestamp Suffixing**: A 32-bit timestamp (creation time) is automatically appended to each value during `put` operations 2. **Expiration Check**: During compaction, RocksDB checks if `timestamp + ttl < current_time` 3. **Lazy Deletion**: Expired keys are removed only during compaction (not immediately upon expiration) ## Important Behaviors - **Non-Strict Guarantees**: Keys are guaranteed to exist for at least TTL seconds, but may persist longer until compaction runs - **Stale Reads**: `get` and iterator operations may return expired entries if compaction hasn't run yet - **Read-Only Mode**: Opens the database without triggering compactions, so expired keys won't be removed - **Variable TTL**: Different TTL values can be used across different database opens ## Basic Usage ### Opening a Database with TTL ```erlang %% Open a database with 1 hour (3600 seconds) TTL {ok, Db} = rocksdb:open_with_ttl( "my_ttl_db", [{create_if_missing, true}], 3600, % TTL in seconds false % read_only flag ). %% All standard operations work normally ok = rocksdb:put(Db, <<"key1">>, <<"value1">>, []), {ok, <<"value1">>} = rocksdb:get(Db, <<"key1">>, []), %% Close when done ok = rocksdb:close(Db). ``` ### TTL Expiration Example ```erlang %% Open with 1 second TTL for demonstration {ok, Db} = rocksdb:open_with_ttl("ttl_test", [{create_if_missing, true}], 1, false), %% Insert a key ok = rocksdb:put(Db, <<"temp_key">>, <<"temp_value">>, []), %% Key exists immediately {ok, <<"temp_value">>} = rocksdb:get(Db, <<"temp_key">>, []), %% Wait for TTL to expire timer:sleep(2000), %% Key may still exist (compaction hasn't run) %% Force compaction to trigger cleanup ok = rocksdb:compact_range(Db, <<"a">>, <<"z">>, []), %% Now the key is gone not_found = rocksdb:get(Db, <<"temp_key">>, []), ok = rocksdb:close(Db). ``` ### Read-Only Mode ```erlang %% Open in read-only mode - no compactions will run {ok, Db} = rocksdb:open_with_ttl("my_ttl_db", [], 3600, true), %% Can read but not write {ok, Value} = rocksdb:get(Db, <<"key">>, []), %% Note: Expired keys won't be cleaned up in read-only mode ok = rocksdb:close(Db). ``` ## Column Family Support ### Opening with Multiple Column Families (each with its own TTL) ```erlang %% Open with column families, each having a different TTL {ok, Db, [DefaultCF, SessionsCF, CacheCF]} = rocksdb:open_with_ttl_cf( "multi_ttl_db", [{create_if_missing, true}], [ {"default", [], 86400}, % 24 hours {"sessions", [], 3600}, % 1 hour {"cache", [], 300} % 5 minutes ], false ). %% Write to different column families ok = rocksdb:put(Db, DefaultCF, <<"user:1">>, <<"data">>, []), ok = rocksdb:put(Db, SessionsCF, <<"sess:abc">>, <<"token">>, []), ok = rocksdb:put(Db, CacheCF, <<"cache:xyz">>, <<"cached">>, []), ok = rocksdb:close(Db). ``` ### Creating a Column Family with TTL ```erlang %% First open a TTL database {ok, Db} = rocksdb:open_with_ttl("my_db", [{create_if_missing, true}], 3600, false), %% Create a new column family with a specific TTL {ok, NewCF} = rocksdb:create_column_family_with_ttl( Db, "temp_data", [], % Column family options 600 % 10 minute TTL ), %% Use the new column family ok = rocksdb:put(Db, NewCF, <<"key">>, <<"value">>, []), ok = rocksdb:close(Db). ``` ### Getting and Setting TTL Dynamically ```erlang {ok, Db, [DefaultCF]} = rocksdb:open_with_ttl_cf( "my_db", [{create_if_missing, true}], [{"default", [], 3600}], false ), %% Get current TTL for a column family {ok, CurrentTTL} = rocksdb:get_ttl(Db, DefaultCF), io:format("Current TTL: ~p seconds~n", [CurrentTTL]), %% Set a new TTL for the column family ok = rocksdb:set_ttl(Db, DefaultCF, 7200), % Change to 2 hours %% Set default TTL for the database ok = rocksdb:set_ttl(Db, 1800), % 30 minutes ok = rocksdb:close(Db). ``` ## Alternative: Compaction Filter TTL For more control over TTL behavior, you can use compaction filters with timestamp-based rules. This is useful when your keys contain embedded timestamps. ```erlang %% TTL based on timestamp embedded in key %% Format: {ttl_from_key, Offset, Length, TTLSeconds} {ok, Db} = rocksdb:open("my_db", [ {create_if_missing, true}, {compaction_filter, #{ rules => [{ttl_from_key, 0, 8, 3600}] % Read 8 bytes at offset 0 as timestamp }} ]), %% Create keys with embedded timestamps Timestamp = erlang:system_time(second), Key = <>, ok = rocksdb:put(Db, Key, <<"value">>, []), ok = rocksdb:close(Db). ``` See the [Compaction Filters Guide](compaction_filters.md) for more details. ## API Reference ### rocksdb:open_with_ttl/4 ```erlang -spec open_with_ttl(Name, DBOpts, TTL, ReadOnly) -> {ok, db_handle()} | {error, any()}. ``` Opens a database with TTL support. | Parameter | Type | Description | |-----------|------|-------------| | Name | `file:filename_all()` | Path to the database directory | | DBOpts | `db_options()` | Database options | | TTL | `integer()` | Time-to-live in seconds (0 or negative = infinity) | | ReadOnly | `boolean()` | If true, opens in read-only mode | ### rocksdb:open_with_ttl_cf/4 ```erlang -spec open_with_ttl_cf(Name, DBOpts, CFDescriptors, ReadOnly) -> {ok, db_handle(), [cf_handle()]} | {error, any()}. ``` Opens a database with multiple column families, each with its own TTL. | Parameter | Type | Description | |-----------|------|-------------| | Name | `file:filename_all()` | Path to the database directory | | DBOpts | `db_options()` | Database options | | CFDescriptors | `[{Name, CFOpts, TTL}]` | List of column family descriptors with TTLs | | ReadOnly | `boolean()` | If true, opens in read-only mode | ### rocksdb:create_column_family_with_ttl/4 ```erlang -spec create_column_family_with_ttl(DBHandle, Name, CFOpts, TTL) -> {ok, cf_handle()} | {error, any()}. ``` Creates a new column family with a specific TTL. ### rocksdb:get_ttl/2 ```erlang -spec get_ttl(DBHandle, CFHandle) -> {ok, integer()} | {error, any()}. ``` Gets the current TTL for a column family. ### rocksdb:set_ttl/2 ```erlang -spec set_ttl(DBHandle, TTL) -> ok | {error, any()}. ``` Sets the default TTL for the database. ### rocksdb:set_ttl/3 ```erlang -spec set_ttl(DBHandle, CFHandle, TTL) -> ok | {error, any()}. ``` Sets the TTL for a specific column family. ## Best Practices 1. **Trigger Compaction for Immediate Cleanup**: If you need expired keys removed immediately, call `rocksdb:compact_range/4` 2. **Use Appropriate TTL Values**: Very short TTLs (< 1 second) may cause excessive data churn 3. **Don't Mix TTL and Non-TTL Opens**: Always use `open_with_ttl` functions to access a TTL database. Using regular `open` will return corrupted values (with timestamp suffix) 4. **Consider Column Family TTLs**: Use different TTLs for different data types by organizing them into column families 5. **Monitor Disk Usage**: Expired keys consume disk space until compaction runs ## Warnings - **Value Corruption**: Opening a TTL database with regular `rocksdb:open` will return corrupted values because of the timestamp suffix - **Short TTLs**: Using very small TTL values may delete your entire database quickly - **No Immediate Expiration**: TTL expiration is lazy - keys persist until compaction