Maintenance
ZoneTree exposes two maintenance layers:
CreateMaintainer()returns an optional, ready-to-use maintenance coordinator.zoneTree.Maintenanceexposes segment state, counters, lifecycle events, and operations for custom maintenance policy.
ZoneTree can operate without a maintainer. Without one, the maintainer's event-driven merge policy, merge-thread tracking, periodic cache cleanup, and disposal coordination do not run. Omitting the maintainer transfers responsibility for maintenance policy and coordination to the caller. zoneTree.Maintenance is the supported surface for that custom implementation, exposing the required state, counters, events, and operations.
Create A Maintainer
using var zoneTree = new ZoneTreeFactory<int, string>()
.SetDataDirectory("data/app")
.OpenOrCreate();
using var maintainer = zoneTree.CreateMaintainer(); The maintainer:
- subscribes to ZoneTree lifecycle events,
- starts normal merges according to configurable thresholds,
- retries or schedules follow-up merges for retryable merge results,
- tracks normal and bottom merge threads for waiting and cancellation,
- periodically releases inactive read buffers and circular-cache entries,
- provides explicit merge, bottom-merge, and eviction methods,
- waits for its tracked merge threads during disposal.
The returned maintainer is caller-owned and disposable. Disposal waits for its tracked merge threads to finish.
Merge Triggers
The default maintainer starts a normal merge after the mutable segment moves forward and either configured limit is exceeded:
| Setting | Default | Meaning |
|---|---|---|
ThresholdForMergeOperationStart | 0 records | merge when read-only records exceed this count |
MaximumReadOnlySegmentCount | 64 | merge when read-only segment count exceeds this count |
With the default threshold of 0, any non-empty read-only layer can start a merge after segment movement.
maintainer.ThresholdForMergeOperationStart = 500_000;
maintainer.MaximumReadOnlySegmentCount = 32; Use a higher record threshold when you want larger merge batches. Use a lower read-only segment count limit when memory pressure should trigger merge work sooner.
Evict Current Data
EvictToDisk() moves the current mutable segment forward and starts a normal merge.
maintainer.EvictToDisk();
maintainer.WaitForBackgroundThreads(); Use it before controlled shutdowns, exports, or explicit maintenance points when you want the current in-memory records to enter the merge pipeline immediately. Call WaitForBackgroundThreads() when the caller needs the merge to finish before continuing.
Waiting And Cancellation
The maintainer tracks the merge threads it starts.
maintainer.WaitForBackgroundThreads(); For async callers:
await maintainer.WaitForBackgroundThreadsAsync(); To request a faster shutdown:
maintainer.TryCancelBackgroundThreads();
maintainer.WaitForBackgroundThreads(); TryCancelBackgroundThreads() asks active normal and bottom-segment merges to cancel. The merge threads finish when they observe the cancellation request.
Dispose() waits for tracked merge threads, stops periodic cleanup, and detaches event handlers. Call WaitForBackgroundThreads() when later code must observe merge completion before disposal. Call TryCancelBackgroundThreads() before disposal to request cancellation of current merge work.
Cache Cleanup
The default maintainer starts inactive cache cleanup automatically.
| Setting | Default |
|---|---|
EnableJobForCleaningInactiveCaches | true |
BlockCacheLifeTime | 1 minute |
InactiveBlockCacheCleanupInterval | 30 seconds |
maintainer.BlockCacheLifeTime = TimeSpan.FromMinutes(2);
maintainer.InactiveBlockCacheCleanupInterval = TimeSpan.FromSeconds(30); Longer cache lifetime can help repeated disk reads. Shorter cache lifetime reduces retained read-cache memory. The cleanup job releases inactive decompressed blocks and expired circular key/value cache records.
For read-cache details, see read-path caching.
Bottom Segment Merge
Bottom segment merge is an explicit operation. Run it when your service wants to compact a range of bottom segments.
maintainer.StartBottomSegmentsMerge();
maintainer.WaitForBackgroundThreads(); To merge a selected range:
maintainer.StartBottomSegmentsMerge(fromIndex: 0, toIndex: 4);
maintainer.WaitForBackgroundThreads(); The range uses the current bottom segment order. A broad range such as 0..int.MaxValue asks ZoneTree to merge as much of the bottom layer as possible.
Custom Maintenance Control
zoneTree.Maintenance exposes the state, lifecycle events, and lower-level operations used to build custom maintenance policy. Use it to integrate maintenance with a scheduler, monitoring system, or storage orchestrator.
zoneTree.Maintenance.MoveMutableSegmentForward();
var thread = zoneTree.Maintenance.StartMergeOperation();
thread?.Join(); Useful direct operations:
| Operation | Purpose |
|---|---|
MoveMutableSegmentForward() | move the current mutable segment into the read-only layer |
StartMergeOperation() | start a normal merge thread |
StartBottomSegmentsMergeOperation(fromIndex, toIndex) | start a bottom-segment merge thread |
TryCancelMergeOperation() | request cancellation for the active normal merge |
TryCancelBottomSegmentsMergeOperation() | request cancellation for the active bottom-segment merge |
SaveMetaData() | refresh the JSON metadata file and clear pending metadata records |
ReleaseReadBuffers(ticks) | release inactive decompressed disk blocks |
ReleaseCircularKeyCacheRecords() | release expired key-cache records |
ReleaseCircularValueCacheRecords() | release expired value-cache records |
StartMergeOperation() and StartBottomSegmentsMergeOperation(...) return the created thread, or null when a merge of the same kind is already active.
Merge Results
Merge completion is reported through maintenance events.
zoneTree.Maintenance.OnMergeOperationEnded += (_, result) =>
{
Console.WriteLine(result);
}; | Result | Meaning |
|---|---|
SUCCESS | merge completed |
NOTHING_TO_MERGE | no eligible read-only segments were available |
ANOTHER_MERGE_IS_RUNNING | a merge of the same kind was already active |
RETRY_READONLY_SEGMENTS_ARE_NOT_READY | read-only segments were still preparing |
CANCELLED_BY_USER | cancellation was requested |
FAILURE | an exception occurred; inspect the logger |
The default maintainer retries RETRY_READONLY_SEGMENTS_ARE_NOT_READY. If it sees ANOTHER_MERGE_IS_RUNNING, it starts another merge after the active merge finishes.
Counters
Maintenance counters are useful for dashboards and health checks.
| Counter | Meaning |
|---|---|
MutableSegmentRecordCount | records in the current mutable segment |
ReadOnlySegmentsCount | read-only in-memory segment count |
ReadOnlySegmentsRecordCount | records across read-only in-memory segments |
InMemoryRecordCount | mutable plus read-only record count |
TotalRecordCount | physical records across memory and disk layers |
IsMerging | normal merge is active |
IsBottomSegmentsMerging | bottom-segment merge is active |
TotalRecordCount is a physical storage counter. Use Count() or CountFullScan() for live-record counts.
Events
Use events for monitoring, scheduling, and cleanup reporting.
| Event | Use |
|---|---|
OnMutableSegmentMovedForward | observe mutable segment movement |
OnMergeOperationStarted | observe normal merge start |
OnMergeOperationEnded | observe normal merge result |
OnBottomSegmentsMergeOperationStarted | observe bottom merge start |
OnBottomSegmentsMergeOperationEnded | observe bottom merge result |
OnDiskSegmentCreated | observe created disk segment files |
OnDiskSegmentActivated | observe the active disk segment change |
OnCanNotDropReadOnlySegment | report cleanup failure for read-only segment files |
OnCanNotDropDiskSegment | report cleanup failure for disk segment files |
OnCanNotDropDiskSegmentCreator | report cleanup failure for unfinished merge output |
Failed drop events mean obsolete files or temporary output stayed behind after a cleanup attempt failed. Log the exception and investigate the file-system or provider error.
Iterator Lifetime
Dispose iterators as soon as scans finish. Long-lived iterators can keep segments alive, which delays cleanup of old segment files and read buffers.
Snapshot iterators move the mutable segment forward when they are created. Heavy snapshot-iterator usage under write load can increase read-only segment pressure.
Practical Patterns
For the default maintenance model:
using var zoneTree = new ZoneTreeFactory<int, string>()
.SetDataDirectory("data/app")
.OpenOrCreate();
using var maintainer = zoneTree.CreateMaintainer();
// run application For a controlled checkpoint:
maintainer.EvictToDisk();
maintainer.WaitForBackgroundThreads();
zoneTree.Maintenance.SaveMetaData(); For a custom maintenance window:
maintainer.StartMerge();
maintainer.StartBottomSegmentsMerge();
maintainer.WaitForBackgroundThreads(); For related tuning, see memory usage, disk segment tuning, and read-path caching.