From e4d8615a99c5315c7cc90603082d1fe66ad9b6d6 Mon Sep 17 00:00:00 2001 From: Gregory Burd Date: Tue, 24 Apr 2012 10:37:45 -0400 Subject: [PATCH] Cleanup --- DESIGN.md | 7 ++----- README.md | 47 ++++++++++++++++++++++++++++++----------------- TODO | 50 ++++++++++++++++++++++---------------------------- 3 files changed, 54 insertions(+), 50 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 9eec04f..5fb367b 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,6 +1,6 @@ +# Hanoi's Design -# Hanoi's Design: How this LSM-BTree Works - +### Basics If there are N records, there are in log2(N) levels (each being a plain B-tree in a file named "A-*level*.data"). The file `A-0.data` has 1 record, `A-1.data` has 2 records, `A-2.data` has 4 records, and so on: `A-n.data` has 2n records. In "stable state", each level file is either full (there) or empty (not there); so if there are e.g. 20 records stored, then there are only data in filed `A-2.data` (4 records) and `A-4.data` (16 records). @@ -28,7 +28,6 @@ Deletes are the same: they are also done by inserting a tombstone (a special val ## Merge Logic - The really clever thing about this storage mechanism is that merging is guaranteed to be able to "keep up" with insertion. Bitcask for instance has a similar merging phase, but it is separated from insertion. This means that there can suddenly be a lot of catching up to do. The flip side is that you can then decide to do all merging at off-peak hours, but it is yet another thing that need to be configured. With LSM B-Trees; back-pressure is provided by the injection mechanism, which only returns when an injection is complete. Thus, every 2nd insert needs to wait for level #0 to finish the required merging; which - assuming merging has linear I/O complexity - is enough to guarantee that the merge mechanism can keep up at higher-numbered levels. @@ -71,6 +70,4 @@ When X is closed and clean, it is actually intermittently renamed M so that if t ABC files have 2^level KVs in it, regardless of the size of those KVs. XM files have 2^(level+1) approximately ... since tombstone merges might reduce the numbers or repeat PUTs of cause. ### File Descriptors - Hanoi needs a lot of file descriptors, currently 6*⌈log2(N)-TOP_LEVEL⌉, with a nursery of size 2TOP_LEVEL, and N Key/Value pairs in the store. Thus, storing 1.000.000 KV's need 72 file descriptors, storing 1.000.000.000 records needs 132 file descriptors, 1.000.000.000.000 records needs 192. - diff --git a/README.md b/README.md index afb12e3..52885cb 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,42 @@ # Hanoi Key/Value Storage Engine -This Erlang-based storage engine implements a structure somewhat like LSM-trees (Log-Structured Merge Trees, see docs/10.1.1.44.2782.pdf). The notes below describe how this storage engine work; I have not done extensive studies as how it differs from other storage mechanisms, but a brief brows through available online resources on LSM-trees indicates that this storage engine is quite different in several respects. - -The storage engine can function as an alternative backend for Basho's Riak/KV. +This storage engine implements a structure somewhat like LSM-trees +(Log-Structured Merge Trees, see docs/10.1.1.44.2782.pdf). The notes in +DESIGN.md describe how this storage engine work; I have not done extensive +studies as how it differs from other storage mechanisms, but a brief review of +available research on LSM-trees indicates that this storage engine is quite +different in several respects. Here's the bullet list: -- Insert, Delete and Read all have worst case log2(N) complexity. -- The cost of evicting stale key/values is amortized into insertion, so you don't need to schedule merge to happen at off-peak hours. -- Operations-friendly "append-only" storage (allows you to backup live system, and crash-recovery is very fast) -- Supports range queries (and thus eventually Riak 2i.) -- Doesn't need much RAM, but does need a lot of file descriptors -- All around 3000 lines of pure Erlang code +- Insert, Delete and Read all have worst case log2(N) complexity. +- The cost of evicting stale key/values is amortized into insertion + - you don't need a separate eviction thread to keep memory use low + - you don't need to schedule merges to happen at off-peak hours +- Operations-friendly "append-only" storage + - allows you to backup live system + - crash-recovery is very fast and the logic is straight forward +- Supports efficient range queries +- Uses bloom filters to avoid unnecessary lookups on disk +- Efficient resource utilization + - Doesn't store all keys in memory + - Uses a modest number of file descriptors proportional to the number of levels + - IO is generally balanced between random and sequential + - Low CPU overhead +- ~2000 lines of pure Erlang code in src/*.erl -### Deploying the hanoi for testing with Riak/KV +### How to deploy Hanoi as a Riak/KV backend -You can deploy `hanoi` into a Riak devrel cluster using the -`enable-hanoi` script. Clone the `riak` repo, change your working directory -to it, and then execute the `enable-hanoi` script. It adds `hanoi` as a -dependency, runs `make all devrel`, and then modifies the configuration -settings of the resulting dev nodes to use the hanoi storage backend. +This storage engine can function as an alternative backend for Basho's Riak/KV. + +You can deploy `hanoi` into a Riak devrel cluster using the `enable-hanoi` +script. Clone the `riak` repo, change your working directory to it, and then +execute the `enable-hanoi` script. It adds `hanoi` as a dependency, runs `make +all devrel`, and then modifies the configuration settings of the resulting dev +nodes to use the hanoi storage backend. 1. `git clone git://github.com/basho/riak.git` 1. `cd riak/deps` 1. `git clone git://github.com/basho/hanoi.git` 1. `cd ..` -1. `./deps/hanoi/enable-hanoi` # which does `make all devrel` - +1. `./deps/hanoi/enable-hanoi` diff --git a/TODO b/TODO index 9dd8112..1e72907 100644 --- a/TODO +++ b/TODO @@ -1,22 +1,28 @@ -* hanoi (in order of priority) - * [2i] secondary index support +* Phase 1: Minimum viable product (in order of priority) + * lager; check for uses of lager:error/2 + * configurable TOP_LEVEL size + * support for future file format changes + * Define a standard struct which is the metadata added at the end of the + file, e.g. [btree-nodes] [meta-data] [offset of meta-data]. This is written + in hanoi_writer:flush_nodes, and read in hanoi_reader:open2. + * test new snappy compression support + * Riak/KV secondary index (2i) support * atomic multi-commit/recovery - * add checkpoint/1 and sync/1 - flush pending writes to stable storage - (nursery:finish() and finish/flush any merges) - * [config] add config parameters on open - * {cache, bytes(), name} share max(bytes) cache named 'name' via etc - * [stats] statistics - * For each level {#merges, {merge-time-min, max, average}} - * [expiry] support for time based expiry, merge should eliminate expired data + * support for time based expiry, merge should eliminate expired data + * statistics + * for each level {#merges, {merge-time-min, max, average}} * add @doc strings and and -spec's * check to make sure every error returns with a reason {error, Reason} - * lager; check for uses of lager:error/2 - * add version 1, crc to the files - * add compression via snappy (https://github.com/fdmanana/snappy-erlang-nif) - * add encryption - * adaptive nursery sizing + +* Phase 2: Production Ready + * dual-nursery + * cache for read-path + * {cache, bytes(), name} share max(bytes) cache named 'name' via etc + +* Phase 3: Wish List * add truncate/1 - quickly truncates a database to 0 items * count/1 - return number of items currently in tree + * adaptive nursery sizing * backpressure on fold operations - The "sync_fold" creates a snapshot (hard link to btree files), which provides consistent behavior but may use a lot of disk space if there is @@ -24,22 +30,10 @@ - The "async_fold" folds a limited number, and remembers the last key serviced, then picks up from there again. So you could see intermittent puts in a subsequent batch of results. + * add block-level encryption support -PHASE 2: -* hanoi - * Define a standard struct which is the metadata added at the end of the - file, e.g. [btree-nodes] [meta-data] [offset of meta-data]. This is written - in hanoi_writer:flush_nodes, and read in hanoi_reader:open2. - * [feature] compression, encryption on disk - - - -REVIEW LITERATURE AND OTHER SIMILAR IMPLEMENTATAIONS: -* nessdb https://code.google.com/p/nessdb/source/browse/LSM-BTREE?r=3a1df166a19505a2369dd954e8fc6d0a545f3d7b -* http://tokutek.com/downloads/mysqluc-2010-fractal-trees.pdf page 14+ -* http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782&rep=rep1&type=pdf - +## NOTES: 1: make the "first level" have more thatn 2^5 entries (controlled by the constant TOP_LEVEL in hanoi.hrl); this means a new set of files is opened/closed/merged for every 32 insert/updates/deletes. Setting this higher will just make the nursery correspondingly larger, which should be absolutely fine.