Serializers And Comparers

The key components configured when a ZoneTree is created have different responsibilities:

  • the comparer defines key equality and order,
  • the key hasher supports Bloom-filter membership checks,
  • serializers define the bytes stored in WALs and disk segments.

Comparer ordering and serializer output are persisted storage contracts. The hasher is a runtime correctness contract for Bloom filtering: it does not define the persisted key order or binary format.

Defaults For Known Types

ZoneTreeFactory<TKey, TValue> fills missing components for supported common types. Built-in serializers cover byte, bool, char, DateTime, decimal, double, short, ushort, int, uint, long, ulong, Guid, string, and Memory<byte>. Built-in ascending comparers and compatible hashers cover the same list except bool.

Use Memory<byte> for byte-sequence keys and values. ZoneTree rejects byte[] because Memory<byte> supports efficient slicing without allocating and copying smaller arrays.

Comparers Define The Keyspace

An IRefComparer<TKey> determines both equality and order.

using ZoneTree.Comparers;

using var zoneTree = new ZoneTreeFactory<int, string>()
    .SetComparer(new Int32ComparerAscending())
    .OpenOrCreate();

Hashers Must Match Equality

IKeyHasher<TKey> computes a hash by reference:

public interface IKeyHasher<TKey>
{
    int GetHashCode(in TKey key);
}

The required invariant is:

If the configured comparer considers two keys equal, the configured hasher must produce the same hash code for both keys.

Unequal keys may collide; that only increases Bloom-filter false positives. Equal keys producing different hashes can cause a false negative and make an existing key appear absent.

Configure a custom hasher with the comparer:

using ZoneTree.Comparers;
using ZoneTree.Hashers;

using var zoneTree = new ZoneTreeFactory<string, long>()
    .SetComparer(new StringOrdinalIgnoreCaseComparerAscending())
    .SetKeyHasher(new OrdinalIgnoreCaseHasher())
    .OpenOrCreate();

sealed class OrdinalIgnoreCaseHasher : IKeyHasher<string>
{
    public int GetHashCode(in string key) =>
        StringComparer.OrdinalIgnoreCase.GetHashCode(key);
}

ZoneTree validates a common string case-sensitivity mismatch by comparing "a" and "A". It cannot prove arbitrary custom implementations compatible; test the invariant over representative and edge-case keys.

See mutable-segment Bloom filters for filter sizing, false-positive behavior, and tuning guidance.

Serializers Define Persisted Bytes

Serializers affect WAL size, disk size, backup batches, CPU cost, compression, recovery, and compatibility across application versions.

using ZoneTree.Serializers;

using var zoneTree = new ZoneTreeFactory<int, string>()
    .SetKeySerializer(new Int32Serializer())
    .SetValueSerializer(new Utf8StringSerializer())
    .OpenOrCreate();

Custom serializers should have a stable, versioned binary contract. Serializer output size directly affects memory, merge work, I/O, and compression ratio.

Metadata And Compatibility

ZoneTree metadata records:

  • key and value types,
  • comparer type,
  • key-hasher type,
  • key- and value-serializer types,
  • mutable-segment Bloom-filter density.

When opening an existing database, the loader validates the key type, value type, comparer type, key-serializer type, and value-serializer type.

Type checks cannot detect a behavioral change inside the same .NET type. Changing comparer ordering or serializer bytes is a storage migration: create a new ZoneTree and copy or rebuild the data. A hasher can be changed without rewriting persisted data, but the replacement must still satisfy the comparer equality invariant because in-memory Bloom filters are built with the active hasher.