Inspect WALs and build recovery tools
ZoneTree handles incomplete WAL tails during startup. For checksum and other read errors, logging and public storage APIs support diagnosis and specialized recovery tools.
WAL corruption diagnostics
Current-format WAL records carry checksums. A mismatch is reported as a read failure; it does not repair the record or establish which bytes should replace it. Reconstructing records that fail checksum validation requires a specialized tool and a policy for deciding which records can be trusted.
| Signal | Where it is reported |
|---|---|
| Checksum mismatch | An entry in WriteAheadLogReadLogEntriesResult.Exceptions, with Success = false. |
| Record read or deserialization failure | Reader diagnostics through ILogger, plus errors in the read result. |
| Normal loading encounters corruption | WriteAheadLogCorruptionException, carrying the per-record exception details. |
| Shared journal has a missing required chunk, invalid chain, or sequence gap | WriteAheadLogCorruptionException while the journal is opened, before segment handles are exposed. |
| A damaged end of the log that a power loss can explain | A warning and the automatic incomplete-tail recovery path. |
Automatic recovery of incomplete tails
A power loss can tear everything written after the last disk sync: a final record can be cut off, read as zeros, or fail its checksum. ZoneTree recovers this suffix automatically. The recovery unit depends on the journal layout.
Shared journals and transaction recovery
Shared and AsyncShared recover the journal once for all memory segments. Recovery starts from the manifest's retained chunk and sequence, validates compressed blocks and record checksums, and verifies chunk boundaries and global record continuity. A damaged unsynced suffix ends the entire journal prefix, including otherwise valid later chunks. The journal repairs that suffix before exposing segment handles.
The built-in transactional factories promote Sync and SyncCompressed to Shared, and AsyncCompressed to AsyncShared. Their transactions recover one prefix of commits across segments, including every commit covered by a successful sync. Pending transactions remain invisible. A missing required chunk or a complete, checksummed record with a broken sequence is corruption rather than an incomplete tail.
Durable transaction APIs sync before publishing their commits to readers. Ordinary Commit() can become visible before sync and can be lost on power loss. Recovery may also retain a commit whose durable call was interrupted or failed after its commit record reached storage. See transaction durability.
Shared handles do not use the per-segment TruncateIncompleteTailRecord() repair API. Do not truncate one handle's records independently: they share the journal's recovery boundary. When migrating legacy WALs from 2.0.8 and earlier, the old WAL and tail are deleted only after their replacement in the shared journal has synced.
Per-segment WAL recovery
Ordinary trees can still use separate Sync, SyncCompressed, and AsyncCompressed WALs. Their unsynced suffixes recover independently; they do not provide the shared journal's cross-segment prefix. For these WALs, loading a mutable or read-only segment follows this path:
- The WAL reader stops at the first damaged record that a power loss can explain, and reports
IncompleteTailRecordFoundExceptionthroughILogger.LogWarning(). The exception recordsRecordIndex,RecordPosition, andFileLength;TruncationAmountis the length after that record's starting position. - The result's
HasFoundIncompleteTailRecordis true only when this is the sole recorded exception.Successremains false, andIncompleteTailRecordexposes the details. - The segment loader calls
TruncateIncompleteTailRecord()atRecordPosition, discards the damaged suffix, and rebuilds the segment from the preceding valid records. Subsequent writes can append from the corrected end.
Which damage a power loss can explain depends on the WAL mode:
Sync: appended sectors can persist in any order. The damage is a torn tail when no valid record follows it, or when a zero run reaches the end of a sector before the next valid record. Otherwise it is corruption.- All compressed-block modes, including shared journals: new main-file blocks carry header and payload checksums. Recovery validates every block and truncates the suffix at the first damaged block outside a known durable prefix. A surviving tail checkpoint identifies a durable main-file prefix and its logical content length; damage inside that prefix is corruption. The checkpoint may lag behind the latest sync or be absent, because acknowledging a durable write syncs the main file once without separately syncing the checkpoint. Intact acknowledged blocks remain recoverable without it. The unsynced suffix can span multiple blocks.
For compressed WALs from 2.0.8 and earlier, recovery also reads the original block headers and uses the legacy tail index and last main block to identify a possible torn suffix. Their tail is migrated before its storage is reused. Unknown tail-format versions and invalid format-descriptor checksums are rejected before recovery changes the files.
For compressed WALs, the record can be damaged within the tail block or span a committed block and the tail. Truncation handles that logical record boundary across the compressed storage. A .tail file can also contain complete records; its presence alone is not an incomplete-record error.
Real corruption inside these ranges looks the same as a torn tail, so it is also truncated with a warning. A record that passes its checksum but fails to deserialize is never truncated. Logs in the legacy format have no checksums, so only a record cut off at the end of the file is recovered automatically.
For per-segment WALs, ReadLogEntries() by itself only reports the result. The standard segment loaders perform the truncation. If other read errors were found before the damaged end, HasFoundIncompleteTailRecord is false and this automatic path does not apply. Shared-journal suffix repair happens earlier, while opening the whole journal.
Capture diagnostics in your application
Configure ZoneTree.Logger.ILogger through SetLogger() for reader warnings and errors. Also log WriteAheadLogCorruptionException from opening: its Exceptions dictionary carries checksum and other per-record failures.
This integration example uses the built-in console logger for an existing database. Replace it with your ILogger adapter to attach application context or forward diagnostics elsewhere:
using ZoneTree;
using ZoneTree.Exceptions;
using ZoneTree.Logger;
var logger = new ConsoleLogger(LogLevel.Info);
try
{
using var tree = new ZoneTreeFactory<int, string>()
.SetDataDirectory("data/app")
.SetLogger(logger)
.Open();
}
catch (WriteAheadLogCorruptionException error)
{
// Includes the loader's per-record failures in the exception details.
logger.LogError(error);
throw;
} Preserve record indexes and exception details with your application context for the inspection tool or recovery procedure.
Read a WAL from a specialized tool
ZoneTree exposes its storage building blocks so tools can work below the factory's normal load path. For a WAL selected with the correct implementation and serializers, this helper stops at the first read or checksum failure and returns the diagnostic result:
using ZoneTree.WAL;
public static class WalInspection
{
public static WriteAheadLogReadLogEntriesResult<TKey, TValue> ReadAndReport<TKey, TValue>(
IWriteAheadLog<TKey, TValue> wal)
{
var result = wal.ReadLogEntries(
stopReadOnException: true,
stopReadOnChecksumFailure: true,
sortByOpIndexes: false);
foreach (var (recordIndex, error) in result.Exceptions)
Console.Error.WriteLine($"Record {recordIndex}: {error.Message}");
return result;
}
} The caller owns the WAL and decides what to do with the result.
The stop flags control how far reading continues. With continuation enabled, returned key/value lists can include entries associated with errors. Inspect Success and Exceptions before using records; do not blindly replay the returned lists into a new database. sortByOpIndexes controls returned record ordering, not validation.
Reuse the public storage APIs
| API | Role in a tool |
|---|---|
WriteAheadLogProvider and concrete WAL implementations | Select the log format and supply the matching key/value serializers. |
IWriteAheadLog.ReadLogEntries() | Read deserialized records and structured failure details. |
WriteAheadLogEntryReader and LogEntry | Inspect record framing and checksums or compose a reader with custom deserialization callbacks. |
ZoneTreeMetaWAL | Inspect saved metadata and metadata-log operations to identify the storage shape. |
DiskSegmentFactory | Open sorted disk segments with compatible options. |
MutableSegmentLoader, ReadOnlySegmentLoader, and ZoneTreeLoader | Reuse startup-loading components where their validation and replay behavior fits the tool. |
Work on a copy of the affected files: some APIs create, migrate, flush, truncate, or rewrite files. For example, GetOrCreateWAL() can create a missing log, and ordinary tree opening can update metadata.
Preserve the data contracts
Use the original key/value types, serializers, comparer, compatible hasher, and deletion rules when interpreting the database. A tool must preserve their behavior, not just their class names.
| Contract | What a tool needs to preserve |
|---|---|
| Key and value types | The types used to interpret records. |
| Serializers | The binary representation of keys and values. |
| Comparer | Ordering and equality when reconstructing the keyspace. |
| Key hasher | Equality-compatible hashing for enabled Bloom filtering. |
| Deletion rules | The interpretation of tombstones and expiration in stored values. |
See serializers and comparers and deletion markers and TTL for those contracts.
Inspection and reconstruction workflow
- Preserve the affected database and any separately configured WAL location, along with the diagnostic logs. Use a separate working copy for inspection.
- Identify the affected segment, record failures, and file format with the public readers and metadata APIs.
- Use a purpose-built recovery tool to validate candidate records and reconstruct the chosen state. Its policy must account for write ordering, repeated keys, and deletion markers.
- Verify the resulting data against application invariants before using it. If a suitable backup is available, restore and verify that generation instead.
For routine startup and WAL replay, see opening a tree.