Open a tree. Own its lifetime.
Configure a factory once at startup, choose whether existing data is required, and keep the opened tree alive for the work that uses it.
Create the tree and its maintenance coordinator
This complete example opens a persistent tree and starts the standard maintenance coordinator:
using ZoneTree;
using var tree = new ZoneTreeFactory<int, string>()
.SetDataDirectory("data/opening-example")
.OpenOrCreate();
using var maintainer = tree.CreateMaintainer();
tree.Upsert(42, "Ada");
if (tree.TryGet(42, out var name))
Console.WriteLine(name); In a service, create the tree at startup and share it with the application code that needs it. Dispose it during shutdown after requests and scans have finished. Opening a new tree for every request repeats initialization and complicates resource ownership.
Choose an opening mode
| Method | Directory contains a tree | No tree exists | Use it for |
|---|---|---|---|
OpenOrCreate() | Opens it. | Creates it. | Applications that initialize their own storage. |
Open() | Opens it. | Throws DatabaseNotFoundException. | Services or tools that must use an existing database. |
Create() | Throws DatabaseAlreadyExistsException. | Creates it. | Explicit provisioning and fresh test databases. |
Each method returns an opened IZoneTree<TKey, TValue>. Configure the factory before calling it.
Configure before opening
The factory fills supported serializers, comparers, and hashers for known types. Specify components explicitly when the stored representation or ordering needs to be controlled:
using ZoneTree;
using ZoneTree.Comparers;
using ZoneTree.Serializers;
using var tree = new ZoneTreeFactory<int, string>()
.SetDataDirectory("data/configured-example")
.SetComparer(new Int32ComparerAscending())
.SetKeySerializer(new Int32Serializer())
.SetValueSerializer(new Utf8StringSerializer())
.SetMutableSegmentMaxItemCount(100_000)
.OpenOrCreate();
using var maintainer = tree.CreateMaintainer(); MutableSegmentMaxItemCount controls the number of records in the active mutable segment. It is not a byte limit, and repeated writes to an existing key do not each create another record in that segment.
Treat serializers, key comparison, and deletion rules as part of the storage design. Reopening existing data with incompatible rules can make it unreadable or incorrectly ordered. Plan a migration when changing that design.
| Decision | Read next |
|---|---|
| What determines equality and ordering? | Key ordering |
| How are keys and values encoded? | Serializers, comparers, and hashers |
| Which values represent deleted records? | Deletion markers and TTL |
| When are writes recoverable after a failure? | WAL modes |
When Bloom filtering is enabled with a custom comparer, comparer-equal keys must produce the same hash. Supply a compatible key hasher with your comparer.
Open a transactional tree
Use the transactional factory methods when several keys in the same tree must participate in a transaction:
using ZoneTree;
using var tree = new ZoneTreeFactory<int, string>()
.SetDataDirectory("data/transactional-example")
.OpenOrCreateTransactional();
using var maintainer = tree.CreateMaintainer(); OpenTransactional() requires existing data; CreateTransactional() requires that a tree does not already exist. Their opening rules match the non-transactional methods above.
The returned ITransactionalZoneTree<TKey, TValue> reads and writes through transactions: Execute runs one and retries its conflicts, and single-key operations each run in a transaction of their own. Choose it because an invariant spans multiple keys, rather than merely because several application threads will write. See transactions for a complete example.
A transactional tree stores every key together with the transaction that wrote it, so its files cannot be opened with Open() or OpenOrCreate(), and an ordinary tree's files cannot be opened as a transactional tree.
Close resources in the right order
| Resource | Lifetime and ownership |
|---|---|
| Tree | Application-owned. Keep it open while operations need it; dispose at shutdown. |
| Maintainer | Caller-owned. Keep it with the tree; disposal waits for tracked merge threads. |
| Iterator | Scan-owned. Dispose as soon as that scan finishes so replaced segments can be reclaimed. |
The examples declare the tree first and the maintainer second. C# disposes them in reverse order, so maintenance finishes before the tree closes. A later iterator declaration is disposed before both.
Creating a maintainer is optional. Omitting it means your application must supply the merge policy, cache cleanup, and background-work coordination through tree.Maintenance.