ZoneTree 1.7.9

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package ZoneTree --version 1.7.9                
NuGet\Install-Package ZoneTree -Version 1.7.9                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ZoneTree" Version="1.7.9" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ZoneTree --version 1.7.9                
#r "nuget: ZoneTree, 1.7.9"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ZoneTree as a Cake Addin
#addin nuget:?package=ZoneTree&version=1.7.9

// Install ZoneTree as a Cake Tool
#tool nuget:?package=ZoneTree&version=1.7.9                

img

ZoneTree

ZoneTree is a persistent, high-performance, transactional, and ACID-compliant ordered key-value database for .NET. It can operate in memory or on local/cloud storage.

License Downloads Platform Build

ZoneTree is a lightweight, transactional and high-performance LSM Tree for .NET.

It is several times faster than Facebook's RocksDB and hundreds of times faster than SQLite. It is faster than any other alternative that I have tested so far. 100 Million integer key-value pair inserts in 20 seconds. You may get longer durations based on the durability level. For example, with async-compressed WAL mode, you can insert 100M integer key-value pairs in 28 seconds. Background merge operation that might take a bit longer is excluded from the insert duration because your inserted data is immediately queryable. Loading 100M integer key-value pair database is in 812 ms. The iteration on 100M key-value pairs takes 24 seconds. There are so many tuning options wait you to discover.

INTRODUCTION

QUICK START GUIDE

API DOCS

TUNING ZONETREE

FEATURES

TERMINOLOGY

PERFORMANCE

Why ZoneTree?

  1. It is pure C#.
  2. It is fast. See benchmark below.
  3. Your data is protected against crashes / power cuts (optional).
  4. Supports transactional and non-transactional access with blazing speeds and ACID guarantees.
  5. You can embed your database into your assembly. Therefore, you don't have to pay the cost of maintaining/shipping another database product along with yours.
  6. You can create scalable and non-scalable databases using ZoneTree as core database engine.

How fast is it?

It is possible with ZoneTree to insert 100 Million integer key-value pairs in 20 seconds using WAL mode = NONE.

Benchmark for all modes: benchmark

Insert Benchmarks 1M 2M 3M 10M
int-int ZoneTree async-compressed WAL 267 ms 464 ms 716 ms 2693 ms
int-int ZoneTree sync-compressed WAL 834 ms 1617 ms 2546 ms 8642 ms
int-int ZoneTree sync WAL 2742 ms 5533 ms 8242 ms 27497 ms
str-str ZoneTree async-compressed WAL 892 ms 1833 ms 2711 ms 9443 ms
str-str ZoneTree sync-compressed WAL 1752 ms 3397 ms 5070 ms 19153 ms
str-str ZoneTree sync WAL 3488 ms 7002 ms 10483 ms 38727 ms
RocksDb sync WAL (10K ⇒ 11 sec) ~1.100.000 ms N/A N/A N/A
int-int RocksDb sync-compressed WAL 8059 ms 16188 ms 23599 ms 61947 ms
str-str RocksDb sync-compressed WAL 8215 ms 16146 ms 23760 ms 72491 ms

Benchmark Configuration:

DiskCompressionBlockSize = 1024 * 1024 * 10;
WALCompressionBlockSize = 1024 * 32 * 8;
DiskSegmentMode = DiskSegmentMode.SingleDiskSegment;
MutableSegmentMaxItemCount = 1_000_000;
ThresholdForMergeOperationStart = 2_000_000;

Additional Notes: According to our tests, ZoneTree is stable and fast even with big data. Tested up to 1 billion records in desktop computers till now.

ZoneTree offers 4 WAL modes to let you make a flexible tradeoff.

  • The sync mode provides maximum durability but slower write speed. In case of a crash/power cut, the sync mode ensures that the inserted data is not lost.

  • The sync-compressed mode provides faster write speed but less durability. Compression requires chunks to be filled before appending them into the WAL file. It is possible to enable a periodic job to persist decompressed tail records into a separate location in a specified interval. See IWriteAheadLogProvider options for more details.

  • The async-compressed mode provides faster write speed but less durability. Log entries are queued to be written in a separate thread. async-compressed mode uses compression in WAL files and provides immediate tail record persistence.

  • None WAL mode disables WAL completely to get maximum performance. Data still can be saved to disk by tree maintainer automatically or manually.

Environment:

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
64 GB DDR4 Memory
SSD: Samsung SSD 850 EVO 1TB
Config: 1M mutable segment size, 2M readonly segments merge-threshold

How to use ZoneTree?

The following sample shows the most basic setup of a ZoneTree database.

using var zoneTree = new ZoneTreeFactory<int, string>()
   .OpenOrCreate();
zoneTree.Upsert(39, "Hello Zone Tree");

The following sample demonstrates creating a database.

  var dataPath = "data/mydatabase";
  using var zoneTree = new ZoneTreeFactory<int, string>()
    .SetComparer(new Int32ComparerAscending())
    .SetDataDirectory(dataPath)
    .SetKeySerializer(new Int32Serializer())
    .SetValueSerializer(new Utf8StringSerializer())
    .OpenOrCreate();
    
    // atomic (thread-safe) on single mutable-segment.
    zoneTree.Upsert(39, "Hello Zone Tree!");
    
    // atomic across all segments
    zoneTree.TryAtomicAddOrUpdate(39, "a", 
        bool (ref string x) => 
        {
            x += "b";
            return true;
        });

How to maintain LSM Tree?

Big LSM Trees require maintenance tasks. ZoneTree provides the IZoneTreeMaintenance interface to give you full power on maintenance tasks. It also comes with a default maintainer to let you focus on your business logic without wasting time with LSM details. You can start using the default maintainer like in the following sample code. Note: For small data you don't need a maintainer.

  var dataPath = "data/mydatabase";

  // 1. Create your ZoneTree
  using var zoneTree = new ZoneTreeFactory<int, string>()
    .SetComparer(new Int32ComparerAscending())
    .SetDataDirectory(dataPath)
    .SetKeySerializer(new Int32Serializer())
    .SetValueSerializer(new Utf8StringSerializer())
    .OpenOrCreate();
 
  using var maintainer = zoneTree.CreateMaintainer();
  maintainer.EnableJobForCleaningInactiveCaches = true;

  // 2. Read/Write data
  zoneTree.Upsert(39, "Hello ZoneTree!");

  // 3. Wait for background threads.
  maintainer.WaitForBackgroundThreads();

How to delete keys?

In Log-Structured Merge (LSM) trees, deletions are managed by upserting a key/value pair with a deletion marker. The actual removal of the data occurs during the compaction stage. In ZoneTree, by default, the system assumes that the default values indicate deletion. However, you can customize this behavior by defining a specific deletion flag, such as using -1 for integer values or completely disable deletion by calling DisableDeletion method.

Custom Deletion Flag

If you need more control over how deletions are handled, you can define a custom structure to represent your values and their deletion status. For example:

[StructLayout(LayoutKind.Sequential)]
struct MyDeletableValueType {
   int Number; 
   bool IsDeleted; 
}

This struct allows you to include a boolean flag indicating whether a value is deleted. You can then use this custom type as the value type in your ZoneTree.

Configuring Deletion Markers

ZoneTree provides flexibility in managing the tree size by allowing you to configure how deletion markers are set and identified. Below are examples of how you can configure these markers for your database:

Example 1: Using an Integer Deletion Flag

In this example, -1 is used as the deletion marker for integer values:

using var zoneTree = new ZoneTreeFactory<int, int>()
  // Additional stuff goes here
  .SetIsValueDeletedDelegate((in int x) => x == -1)
  .SetMarkValueDeletedDelegate((ref int x) => x = -1)
  .OpenOrCreate();  
Example 2: Using a Custom Struct for Deletion

Alternatively, if you're using a custom struct to manage deletions, you can configure ZoneTree to recognize and mark deletions as follows:

using var zoneTree = new ZoneTreeFactory<int, MyDeletableValueType>()
  // Additional stuff goes here
  .SetIsValueDeletedDelegate((in MyDeletableValueType x) => x.IsDeleted)
  .SetMarkValueDeletedDelegate((ref MyDeletableValueType x) => x.IsDeleted = true)
  .OpenOrCreate();  

You can also use built in generic Deletable<TValue> for deletion.

How to iterate over data?

Iteration is possible in both directions, forward and backward. Unlike other LSM tree implementations, iteration performance is equal in both directions. The following sample shows how to do the iteration.

 using var zoneTree = new ZoneTreeFactory<int, int>()
    // Additional stuff goes here
    .OpenOrCreate();
 using var iterator = zoneTree.CreateIterator();
 while(iterator.Next()) {
    var key = iterator.CurrentKey;
    var value = iterator.CurrentValue;
 } 
 
 using var reverseIterator = zoneTree.CreateReverseIterator();
 while(reverseIterator.Next()) {
    var key = reverseIterator.CurrentKey;
    var value = reverseIterator.CurrentValue;
 }

How to iterate starting with a key (Seekable Iterator)?

ZoneTreeIterator provides Seek() method to jump into any record with in O(log(n)) complexity. That is useful for doing prefix search with forward-iterator or with backward-iterator.

 using var zoneTree = new ZoneTreeFactory<string, int>()
    // Additional stuff goes here
    .OpenOrCreate();
 using var iterator = zoneTree.CreateIterator();
 // iterator jumps into the first record starting with "SomePrefix" in O(log(n)) complexity. 
 iterator.Seek("SomePrefix");
 
 //iterator.Next() complexity is O(1)
 while(iterator.Next()) {
    var key = iterator.CurrentKey;
    var value = iterator.CurrentValue;
 } 

Transaction Support

ZoneTree supports Optimistic Transactions. It is proud to announce that the ZoneTree is ACID-compliant. Of course, you can use non-transactional API for the scenarios where eventual consistency is sufficient.

ZoneTree supports 3 way of doing transactions.

  1. Fluent Transactions with ready to use retry capability.
  2. Classical Transaction API.
  3. Exceptionless Transaction API.

The following sample shows how to do the transactions with ZoneTree Fluent Transaction API.

using var zoneTree = new ZoneTreeFactory<int, int>()
    // Additional stuff goes here
    .OpenOrCreateTransactional();
using var transaction =
    zoneTree
        .BeginFluentTransaction()
        .Do((tx) => zoneTree.UpsertNoThrow(tx, 3, 9))
        .Do((tx) =>
        {
            if (zoneTree.TryGetNoThrow(tx, 3, out var value).IsAborted)
                return TransactionResult.Aborted();
            if (zoneTree.UpsertNoThrow(tx, 3, 21).IsAborted)
                return TransactionResult.Aborted();
            return TransactionResult.Success();
        })
        .SetRetryCountForPendingTransactions(100)
        .SetRetryCountForAbortedTransactions(10);
    await transaction.CommitAsync();

The following sample shows traditional way of doing transactions with ZoneTree.

 using var zoneTree = new ZoneTreeFactory<int, int>()
    // Additional stuff goes here
    .OpenOrCreateTransactional();
 try 
 {
     var txId = zoneTree.BeginTransaction();
     zoneTree.TryGet(txId, 3, out var value);
     zoneTree.Upsert(txId, 3, 9);
     var result = zoneTree.Prepare(txId);
     while (result.IsPendingTransactions) {
         Thread.Sleep(100);
         result = zoneTree.Prepare(txId);
     }
     zoneTree.Commit(txId);
  }
  catch(TransactionAbortedException e)
  {
      //retry or cancel
  }

Features

ZoneTree Features
Works with .NET primitives, structs and classes.
High Speed and Low Memory consumption.
Crash Resilience
Optimum disk space utilization.
WAL and DiskSegment data compression.
Very fast load/unload.
Standard read/upsert/delete functions.
Optimistic Transaction Support
Atomic Read Modify Update
Can work in memory.
Can work with any disk device including cloud devices.
Supports optimistic transactions.
Supports Atomicity, Consistency, Isolation, Durability.
Supports Read Committed Isolation.
4 different modes for write ahead log.
Audit support with incremental transaction log backup.
Live backup.
Configurable amount of data that can stay in memory.
Partially (with sparse arrays) or completely load/unload data on disk to/from memory.
Forward/Backward iteration.
Allow optional dirty reads.
Embeddable.
Optimized for SSDs.
Exceptionless Transaction API.
Fluent Transaction API with ready to use retry capabilities.
Easy Maintenance.
Configurable LSM merger.
Transparent and simple implementation that reveals your database's internals.
Fully open-source with unrestrictive MIT license.
Transaction Log compaction.
Analyze / control transactions.
Concurrency Control with minimum overhead by novel separation of Concurrency Stamps and Data.
TTL support.
Use your custom serializer for keys and values.
Use your custom comparer.
MultipleDiskSegments Mode to enable dividing data files into configurable sized chunks.
Snapshot iterators.

I want to contribute. What can I do?

I appreciate any contribution to the project. These are the things I do think we need at the moment:

  1. Write tests / benchmarks.
  2. Write documentation.
  3. Feature requests & bug fixes.
  4. Performance improvements.
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (5)

Showing the top 5 NuGet packages that depend on ZoneTree:

Package Downloads
DifferentialComputeDotNet.Core

Package Description

ZoneTree.FullTextSearch

ZoneTree.FullTextSearch is an open-source library that extends ZoneTree to provide efficient full-text search capabilities. It offers a fast, embedded search engine suitable for applications that require high performance and do not rely on external databases.

ManagedCode.ZoneTree.BlobFileSystem

Azure Blob FileSystem for ZoneTree

ManagedCode.Database.ZoneTree

Repository for ZoneTree

RickDotNet.FusionStore.Stores.ZoneTree

A FusionCache and ZoneTree inspired data store abstraction.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.8.4 110 11/14/2024
1.8.3 208 10/16/2024
1.8.2 298 9/7/2024
1.8.1 119 9/7/2024
1.8.0 126 9/1/2024
1.7.9 138 8/30/2024
1.7.8 103 8/30/2024
1.7.7 164 8/24/2024
1.7.6 168 8/17/2024
1.7.5 132 8/17/2024
1.7.4 129 8/11/2024
1.7.3 105 8/10/2024
1.7.2 116 7/19/2024
1.7.1 226 6/14/2024
1.7.0 2,073 12/12/2023
1.6.9 630 9/13/2023
1.6.8 134 9/13/2023
1.6.7 132 9/13/2023
1.6.6 183 8/18/2023
1.6.5 194 6/17/2023
1.6.4 138 6/16/2023
1.6.3 201 5/29/2023
1.6.2 16,869 5/26/2023
1.6.1 283 4/5/2023
1.6.0 1,897 1/14/2023
1.5.9 308 1/14/2023
1.5.8 1,002 11/20/2022
1.5.7 382 11/14/2022
1.5.6 352 11/13/2022
1.5.5 466 10/20/2022
1.5.2 496 9/14/2022
1.5.1 415 9/3/2022
1.5.0 416 9/2/2022
1.4.9 418 8/31/2022
1.4.8 381 8/30/2022
1.4.7 417 8/30/2022
1.4.6 426 8/29/2022
1.4.5 440 8/28/2022
1.4.4 608 8/27/2022
1.4.3 426 8/26/2022
1.4.2 419 8/26/2022
1.4.1 388 8/26/2022
1.4.0 418 8/25/2022
1.3.9 404 8/25/2022
1.3.8 422 8/24/2022
1.3.7 684 8/23/2022
1.3.6 403 8/22/2022
1.3.5 425 8/22/2022
1.3.4 418 8/21/2022
1.3.3 405 8/18/2022
1.3.2 434 8/17/2022
1.3.1 403 8/17/2022
1.3.0 416 8/17/2022
1.2.9 404 8/16/2022
1.2.8 397 8/15/2022
1.2.7 423 8/15/2022
1.2.6 413 8/13/2022
1.2.5 428 8/13/2022
1.2.4 413 8/13/2022
1.2.3 438 8/9/2022
1.2.2 393 8/9/2022
1.2.1 436 8/9/2022
1.2.0 455 8/8/2022
1.1.9 429 8/8/2022
1.1.8 419 8/8/2022
1.1.7 443 8/8/2022
1.1.6 406 8/7/2022
1.1.5 432 8/6/2022
1.1.4 416 8/6/2022
1.1.3 423 8/5/2022
1.1.2 431 8/5/2022
1.1.1 431 7/27/2022
1.1.0 435 7/27/2022
1.0.9 424 7/25/2022
1.0.8 410 7/24/2022
1.0.7 385 7/23/2022
1.0.6 423 7/20/2022
1.0.5 437 7/18/2022
1.0.4 436 7/11/2022
1.0.3 474 7/11/2022
1.0.2 465 7/11/2022
1.0.1 450 7/6/2022
1.0.0 449 7/6/2022