The Log-Structured Merge-Tree (LSM-Tree) (1996) [pdf]
citeseerx.ist.psu.edu
citeseerx.ist.psu.edu
On paper level DB looks great but the extra write load from the write amplification thanks to the multi-level data structure meant the real world performance was much worse than a plain BTree / B+Tree for continuous random write load.
If you have a different workload, then levelDB might work for you: continuous appends of mostly new data perhaps?
The problem space was a seen-set store for a exhaustive prover. (ie, a continuous loop of: have you seen this state yet? If No, add it to the set of known states, rinse and repeat.) Any memory you allocated to another data structure is memory you’ve just taken away from your in-memory cache for the seen set. Total throughput was ultimately disk IO limited, so anything that increased the number of reads/writes just made things slower.
I don’t think the bloom filter would have helped, because every access was potentially a write, but I’d have to go away and think about it for a bit to be sure!
A:1, B:1, C:1, D:1, E:1
A:2, B:2, C:2, D:2, E:2
This turns into the worst-case key ordering for LevelDB because every write overlaps with the previous keys.Worst-case turns out to be ridiculously bad.
http://leveldb.googlecode.com/svn/trunk/doc/impl.html
Do the math.
For large keys, I agree with you, you shouldn't use an LSM-tree. There is a threshold between seek time and compaction.
I wrote an LSM-tree database based on Chu's LMDB[1] that was able to support a much higher write throughput than LMDB, although it was still slower than LevelDB for writes. Unfortunately it had some other drawbacks and would be hard to make it "production ready" so I'm not using it.
I'm using LMDB by default in my current project because of it's simplicity and read performance (which are truly amazing). But I'm convinced that a write-optimized database on par with LMDB is possible. SQLite4[2] might eventually be it, if it ever gets finished.
I'm inclined to believe there's no "there" there.
Every insert writes one of levels 1 or 2. One out of every 2B/k writes also rewrites level 3, at a cost of 2 block writes. This adds an amortized cost of k/B block writes. One out of every 4B/k writes also rewrites level 4, at a cost of 4 block writes. This, again, adds an amortized cost of k/B block writes.
This continues until we are out of levels. Since there are ceil(lg (nk/B)) + 1 levels (at most), the amortized cost of an insert is at most 1 + (k/B)(ceil(lg (nk/B)) + 1 - 1) block writes.
For a block size of 4096 bytes, a key size of 32 bytes, and n = 2^30, this 1.18 block writes per insert. If k = 256 and n = 2^40, this is 3.25 block writes per insert.
Is my math wrong?
Assume you have actually implemented this very simple LSM. Is it reliable? i.e., are you using ARIES WAL? If so, then every insert has an additional cost of at least k for the log record.
Are you using a raw block device, or are you using a filesystem? If a filesystem, then every insert has additional costs for metadata updates, and these costs are some multiple of the block size, not the key size.
If using a filesystem, every "rewrite" of a level includes multiple metadata updates to empty/delete existing files and create/allocate new files.
If not using a filesystem, then you must somehow manage metadata about the existence and size of your multiple levels yourself, as well as a space mapping of the raw block device.
A B+tree gets most of that for free because it uses a single file, so the amount of filesystem-level metadata updating is minimal.
Yes, it is true that LSM can incur a O(logN) read amplification, it was clearly shown in our bare storage tests. But! The full table scan read in full server has 3% (three percent) difference between different storages (LSM is 3% slower). And, as I haven't used BTrees for LSM levels, but some other superficially similar structures with different construction (and no modification - MVCC), the read of complex structures from our DB (a PCB project - you can guess complexity) netted in clear benefit - that read operation was from 16 (sixteen) to 19 (nineteen) percents faster compared to BerkeleyDB. Given that BerkeleyDB storage backend has not occupied more than 20% of server runtime in our profiles, it amounts to 5 to 20 times speedup in read operations.
This can be explained like using the following argument: because Btree gets fragmented when you randomly insert things - pages get scattered all over the place. The rebuild phase I employed made pages much less scattered (actually, there cannot be more than some fixed number (8 in our case) different contiguous runs of pages to hold all data). This amounted to radical difference in read performance.
Take a look: https://cs.brown.edu/research/pubs/theses/masters/2010/newto...
The paper above discusses Btree fragmentation and proposes something like small LSM structure for indices.
Wy original comment was about write amplification, not read amplification. It's already a given that LSMs are slow for reads. LSMs are claimed to be write optimized and yet it's easy to demonstrate that their write amplification is prohibitively bad. They also are inherently unreliable, making them a choice suitable only for idiots.
https://www.usenix.org/conference/osdi14/technical-sessions/...
So I achieved speedup of 5-20 times over BDB (albeit not quite recent one, I admit).
Scale-free 1M nodes graph write does not even ends with BDB in reasonable time (and produces HUGE log). The difference between BDB and my LSM implementation for smaller graph size writes was in order of 200-300 times, LSM is faster.
Finally, I admit your code is here to test and use, mine's closed source and there are only my words. So I have to shut myself up and try to recreate that wonderful thing for everyone to use.
The math is correct https://news.ycombinator.com/item?id=10437621