Column families
A column family (CF) is a logical key space inside a single RocksDB database — think "table". Every database has at least one column family called "default". You can create more to:
- Keep keys with different schemas separate (no prefix collisions).
- Tune compaction, compression, or block size per-namespace.
- Drop a whole namespace cheaply with
DropColumnFamily. - Atomically write across multiple namespaces — a single
WriteBatchcan touch any column family.
Upstream reference: Column Families wiki.
Listing existing column families
foreach (var name in RocksDb.ListColumnFamilies(new DbOptions(), path))
{
Console.WriteLine(name);
}
A non-throwing variant returns a bool:
if (RocksDb.TryListColumnFamilies(new DbOptions(), path, out var names))
{
// … use names
}
Opening with column families
Every Open call must enumerate every existing column family — RocksDB throws if any are missing.
var cfs = new ColumnFamilies
{
{ "users", new ColumnFamilyOptions() },
{ "orders", new ColumnFamilyOptions().SetCompression(Compression.Zstd) },
};
var options = new DbOptions()
.SetCreateIfMissing(true)
.SetCreateMissingColumnFamilies(true);
using var db = RocksDb.Open(options, path, cfs);
ColumnFamilies always carries a "default" family. You can override its options:
cfs.Add(new ColumnFamilies.Descriptor(
ColumnFamilies.DefaultName,
new ColumnFamilyOptions().SetCompression(Compression.Lz4)));
Getting handles
A ColumnFamilyHandle is the runtime token for "this CF". Every read/write API takes one as an optional last argument; null means the default CF.
var users = db.GetColumnFamily("users");
var orders = db.GetColumnFamily("orders");
var def = db.GetDefaultColumnFamily();
db.Put("u:42", "Ada", users);
string name = db.Get("u:42", users);
The safe variant returns false if the CF is missing:
if (db.TryGetColumnFamily("audit", out var audit))
{
db.Put("e:1", "...", audit);
}
Creating and dropping at runtime
var audit = db.CreateColumnFamily(new ColumnFamilyOptions(), "audit");
db.Put("e:1", "...", audit);
db.DropColumnFamily("audit");
Dropping is cheap — RocksDB tombstones the whole CF and compaction reclaims the space asynchronously.
Atomic writes across families
A single WriteBatch can touch any number of column families. The whole batch is one atomic write.
using var batch = new WriteBatch();
batch.Put("o:42", "pending", orders);
batch.Put("u:42", "Ada", users);
batch.Delete("draft:42", users);
db.Write(batch); // atomic across families
Iterating a single column family
Pass the handle to NewIterator:
using var it = db.NewIterator(cf: users);
for (it.SeekToFirst(); it.Valid(); it.Next())
{
Console.WriteLine($"{it.StringKey()} = {it.StringValue()}");
}
Per-family tuning
Each ColumnFamilyOptions is independent — different CFs can have different compaction styles, compression algorithms, block sizes, or merge operators.
var hot = new ColumnFamilyOptions()
.SetCompression(Compression.No)
.SetWriteBufferSize(64UL * 1024 * 1024);
var cold = new ColumnFamilyOptions()
.SetCompression(Compression.Zstd)
.OptimizeForPointLookup(blockCacheSizeMb: 64);
var cfs = new ColumnFamilies
{
{ "hot", hot },
{ "cold", cold },
};
See the Tuning Guide for which knobs are CF-scoped vs. DB-wide.
Common patterns
Schemas as CFs
One CF per logical type — users, orders, events. No key-prefix collisions, easy to drop a type entirely.
Hot / warm / cold
Split data by access pattern. Tune each CF independently. Cheap to evolve over time.
Secondary indexes
Maintain a CF where keys are the index entry and values point to the primary key. Update both atomically with a WriteBatch.
Cheap purge
Need to wipe a tenant's data? Put each tenant in its own CF and DropColumnFamily to nuke it.