Maintenance

July 15, 2026 ยท View on GitHub

ZoneTree exposes two maintenance layers:

  • CreateMaintainer() returns an optional, ready-to-use maintenance coordinator.
  • zoneTree.Maintenance exposes 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:

SettingDefaultMeaning
ThresholdForMergeOperationStart0 recordsmerge when read-only records exceed this count
MaximumReadOnlySegmentCount64merge 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.

SettingDefault
EnableJobForCleaningInactiveCachestrue
BlockCacheLifeTime1 minute
InactiveBlockCacheCleanupInterval30 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:

OperationPurpose
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);
};
ResultMeaning
SUCCESSmerge completed
NOTHING_TO_MERGEno eligible read-only segments were available
ANOTHER_MERGE_IS_RUNNINGa merge of the same kind was already active
RETRY_READONLY_SEGMENTS_ARE_NOT_READYread-only segments were still preparing
CANCELLED_BY_USERcancellation was requested
FAILUREan 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.

CounterMeaning
MutableSegmentRecordCountrecords in the current mutable segment
ReadOnlySegmentsCountread-only in-memory segment count
ReadOnlySegmentsRecordCountrecords across read-only in-memory segments
InMemoryRecordCountmutable plus read-only record count
TotalRecordCountphysical records across memory and disk layers
IsMergingnormal merge is active
IsBottomSegmentsMergingbottom-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.

EventUse
OnMutableSegmentMovedForwardobserve mutable segment movement
OnMergeOperationStartedobserve normal merge start
OnMergeOperationEndedobserve normal merge result
OnBottomSegmentsMergeOperationStartedobserve bottom merge start
OnBottomSegmentsMergeOperationEndedobserve bottom merge result
OnDiskSegmentCreatedobserve created disk segment files
OnDiskSegmentActivatedobserve the active disk segment change
OnCanNotDropReadOnlySegmentreport cleanup failure for read-only segment files
OnCanNotDropDiskSegmentreport cleanup failure for disk segment files
OnCanNotDropDiskSegmentCreatorreport 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.