Skip to content

About

C++17 LSM-style key-value storage engine with WAL, SSTables, sequence-based versioning, streaming compaction, corruption validation, and an LRU block cache.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

โšก LogStoreDB โ€” LSM-Style Key-Value Storage Engine

C++

Platform

Storage Engine

LSM Tree

Tests

License

A lightweight, crash-recoverable Log-Structured Merge (LSM)-style key-value storage engine built from scratch in modern C++.

LogStoreDB implements core storage-engine internals including:

  • Write-Ahead Logging (WAL) with CRC32 integrity checking
  • In-memory MemTable backed by a custom SkipList
  • Explicit sequence numbers for deterministic version ordering
  • Immutable SSTables with block-based storage
  • SSTable index and footer metadata
  • Tombstone-based logical deletes
  • Streaming K-way merge compaction
  • Atomic SSTable installation using temporary files and rename
  • Defensive corruption and bounds validation
  • O(1) LRU block caching for repeated reads
  • Crash recovery through WAL replay and persistent sequence recovery
  • Automated correctness, corruption, restart, and stress testing

Inspired by storage-engine concepts used in systems such as LevelDB, RocksDB, Pebble, and Badger.


๐Ÿ“‘ Table of Contents


๐Ÿš€ Features

๐Ÿ’พ Persistent LSM Storage

  • MemTable: Ordered in-memory storage backed by a custom SkipList.
  • SSTables: Immutable on-disk tables containing sorted records organized into approximately 4 KB data blocks.
  • Index Blocks: Map SSTable key ranges to data-block locations so reads can avoid scanning the entire file.
  • Footer Metadata: Stores persistent SSTable metadata including the maximum sequence number.
  • Write-Ahead Log: Records mutations before they are reflected in memory, enabling recovery after restart.
  • CRC32: Detects corrupted WAL records during recovery.

๐Ÿ”ข Explicit Versioning

Each mutation receives a monotonically increasing uint64_t sequence number.

PUT A = "old"    seq=10
PUT A = "new"    seq=20

latest version:

A = "new"

Version selection is based on the sequence number rather than filesystem ordering or filename ordering.

๐Ÿ—‘๏ธ Tombstone-Based Deletes

Deletes are represented explicitly using:

std::optional<std::string>

A normal record contains a value:

A โ†’ "hello"

A deletion is represented as:

A โ†’ nullopt

This prevents collisions with user-provided values such as:

"[[TOMBSTONE]]"

๐Ÿ”„ Streaming Compaction

Multiple sorted SSTables are merged using a streaming K-way merge.

  • Avoids loading all SSTable records into a large in-memory map.
  • Resolves overlapping keys using sequence numbers.
  • Keeps compaction memory proportional to the number of input streams.
  • Produces a new compacted SSTable through atomic file installation.

โšก LRU Block Cache

Frequently accessed SSTable blocks can be served from an in-memory LRU cache.

  • std::unordered_map + std::list
  • Average O(1) lookup
  • Bounded memory usage
  • Caches parsed block contents
  • Cache hits avoid repeated disk reads and block parsing

๐Ÿง  Architecture Overview

LogStoreDB follows an LSM-style architecture:

                    Client / CLI
                         โ”‚
                         โ–ผ
                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                โ”‚     KVStore     โ”‚
                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
            โ”‚                         โ”‚
            โ–ผ                         โ–ผ
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚    WAL     โ”‚            โ”‚  MemTable  โ”‚
     โ”‚   CRC32    โ”‚            โ”‚  SkipList  โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
                                      โ”‚
                                Flush threshold
                                      โ”‚
                                      โ–ผ
                              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                              โ”‚   SSTable    โ”‚
                              โ”‚              โ”‚
                              โ”‚ Data Blocks  โ”‚
                              โ”‚ Index Block  โ”‚
                              โ”‚ Footer       โ”‚
                              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                     โ”‚
                                     โ–ผ
                              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                              โ”‚ LRU Block    โ”‚
                              โ”‚    Cache     โ”‚
                              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                     โ”‚
                                     โ–ผ
                              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                              โ”‚  Compaction  โ”‚
                              โ”‚  K-Way Merge โ”‚
                              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The main responsibilities are divided into four layers:

Durability       โ†’ WAL + CRC32
In-memory state  โ†’ MemTable + SkipList
Persistent state โ†’ SSTables
Maintenance      โ†’ Compaction + Block Cache

โœ๏ธ Write Path (PUT)

PUT key value
      โ”‚
      โ–ผ
1. Allocate sequence number
      โ”‚
      โ–ผ
2. Append binary record to WAL
      โ”‚
      โ–ผ
3. Update MemTable
      โ”‚
      โ–ผ
4. Continue serving requests
      โ”‚
      โ–ผ
5. MemTable reaches flush threshold
      โ”‚
      โ”œโ”€โ”€ Build sorted SSTable
      โ”œโ”€โ”€ Write to temporary .tmp file
      โ”œโ”€โ”€ Complete and close file
      โ””โ”€โ”€ Atomically rename to .sst

The WAL provides a recovery record while the MemTable provides fast in-memory access.


๐Ÿ” Read Path (GET)

GET key
   โ”‚
   โ–ผ
1. Check MemTable
   โ”‚
   โ”œโ”€โ”€ Found newest visible version
   โ”‚
   โ””โ”€โ”€ Otherwise continue
            โ”‚
            โ–ผ
2. Search SSTables
            โ”‚
            โ–ผ
3. Locate candidate block using SSTable index
            โ”‚
            โ–ผ
4. Check LRU Block Cache
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       HIT             MISS
        โ”‚                โ”‚
        โ”‚                โ–ผ
        โ”‚           Read block
        โ”‚                โ”‚
        โ”‚                โ–ผ
        โ”‚           Validate + parse
        โ”‚                โ”‚
        โ”‚                โ–ผ
        โ”‚           Insert into cache
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ–ผ
5. Resolve matching KVPair by sequence number
                 โ”‚
                 โ–ผ
6. Newest sequence wins
                 โ”‚
                 โ–ผ
7. Tombstone โ†’ NOT FOUND

The logical result does not depend on whether a block was served from the cache or read directly from disk.


๐Ÿ“ DELETE Path

Deletes are represented using explicit tombstones.

REMOVE key
    โ”‚
    โ–ผ
Allocate sequence number
    โ”‚
    โ–ผ
Append DELETE record to WAL
    โ”‚
    โ–ผ
Insert tombstone into MemTable
    โ”‚
    โ–ผ
Flush / Compact

Example:

Older SSTable:

seq=10 โ†’ user = Alice


Newer record:

seq=20 โ†’ user = DELETE

The delete masks the older version.


๐Ÿ’พ WAL & Crash Recovery

The WAL is an append-only binary log containing mutation records and checksums.

Conceptually:

WAL Record
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Sequence Number         โ”‚
โ”‚ Operation               โ”‚
โ”‚ Key Size                โ”‚
โ”‚ Value Size              โ”‚
โ”‚ Key                     โ”‚
โ”‚ Value                   โ”‚
โ”‚ CRC32                   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

During recovery:

Database Restart
       โ”‚
       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ–ผ               โ–ผ
 Load SSTables     Replay WAL
       โ”‚               โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ–ผ
        Reconstruct state
               โ”‚
               โ–ผ
       Restore sequence state

Each WAL record is validated before replay:

Read record
    โ”‚
    โ–ผ
Calculate CRC32
    โ”‚
    โ–ผ
Compare checksum
    โ”‚
    โ”œโ”€โ”€ Valid โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ Replay
    โ”‚
    โ””โ”€โ”€ Invalid โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ Reject corrupted record

This protects recovery from malformed or corrupted WAL records.


๐Ÿ”ข Sequence Numbers

Every mutation receives an explicit monotonically increasing sequence number.

Example:

seq=1  PUT     user = Alice
seq=2  PUT     age  = 20
seq=3  PUT     user = Bob
seq=4  DELETE  age
seq=5  PUT     user = Charlie

For a key with multiple physical versions:

seq=1 โ†’ Alice
seq=3 โ†’ Bob
seq=5 โ†’ Charlie

the newest version is:

seq=5 โ†’ Charlie

Sequence numbers are preserved through:

WAL
 โ†“
MemTable
 โ†“
SSTable
 โ†“
Compaction
 โ†“
Read conflict resolution

SSTable footer metadata also preserves the maximum sequence number represented by the table, allowing the engine to recover the correct sequence counter even after the WAL has been flushed.

Example:

SSTable A โ†’ maxSeq = 100
SSTable B โ†’ maxSeq = 147
SSTable C โ†’ maxSeq = 132

Recovered maxSeq = 147
Next sequence   = 148

๐Ÿง  MemTable & SkipList

The MemTable stores recent mutations in memory.

It is backed by a custom SkipList that maintains keys in sorted order.

Level 3:        โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’
Level 2:    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’
Level 1:    โ”€โ”€โ”€โ”€โ”€โ†’ โ”€โ”€โ”€โ”€โ”€โ†’ โ”€โ”€โ”€โ”€โ”€โ†’ โ”€โ”€โ”€โ”€โ”€โ†’
Level 0:    A  โ†’  B  โ†’  C  โ†’  D  โ†’  E

Level 0 contains all records in sorted order.

Higher levels act as express lanes, giving the SkipList expected logarithmic search and insertion behavior.

Sorted MemTable contents can then be written directly into sorted SSTable blocks.


๐Ÿ“ฆ SSTable Storage

When the MemTable reaches its configured threshold, its sorted contents are persisted as an immutable SSTable.

MemTable
   โ”‚
   โ–ผ
Sorted KVPair records
   โ”‚
   โ–ผ
BlockBuilder
   โ”‚
   โ”œโ”€โ”€ Data Block 1
   โ”œโ”€โ”€ Data Block 2
   โ”œโ”€โ”€ Data Block 3
   โ””โ”€โ”€ ...
   โ”‚
   โ–ผ
Index Block
   โ”‚
   โ–ผ
Footer
   โ”‚
   โ–ผ
SSTable

A logical record contains:

Sequence Number
Key
Value / Tombstone

The approximately 4 KB block structure keeps individual reads bounded while allowing the SSTable reader to avoid processing unrelated data.


๐Ÿ“š SSTable Index

Each SSTable maintains index information describing where data blocks are stored.

Conceptually:

firstKey       offset       size
----------------------------------
apple          0            4096
banana         4096         4020
carrot         8116         3975

A lookup can therefore narrow the search to the relevant block:

Key
 โ”‚
 โ–ผ
Index
 โ”‚
 โ–ผ
Candidate Block
 โ”‚
 โ–ผ
Block Reader
 โ”‚
 โ–ผ
KVPair

This avoids scanning the entire SSTable for every lookup.


๐Ÿงฑ Atomic SSTable Installation

Flush and compaction outputs are first written to temporary files:

Build SSTable
     โ”‚
     โ–ผ
sst_x.sst.tmp
     โ”‚
     โ–ผ
Complete write
     โ”‚
     โ–ผ
Close file
     โ”‚
     โ–ผ
Atomic rename
     โ”‚
     โ–ผ
sst_x.sst

This prevents an incomplete write from being installed as a normal SSTable.

Temporary files represent unfinished work, while .sst files represent completed table outputs.


๐Ÿ—‘๏ธ Tombstones

A deletion is not represented using a special user-visible string.

Instead:

std::optional<std::string>

is used to distinguish between:

Actual value:

"A" โ†’ "hello"


Tombstone:

"A" โ†’ nullopt

This means a user can safely store:

[[TOMBSTONE]]

as an ordinary value.

For example:

PUT message "[[TOMBSTONE]]"

GET message
โ†’ [[TOMBSTONE]]

while:

REMOVE message

GET message
โ†’ NOT FOUND

Tombstones also prevent older SSTable versions from becoming visible again.


๐Ÿ”„ Compaction

As SSTables accumulate, the engine performs compaction.

SSTable 1 โ”€โ”€ Iterator โ”€โ”€โ”
SSTable 2 โ”€โ”€ Iterator โ”€โ”€โ”ค
SSTable 3 โ”€โ”€ Iterator โ”€โ”€โ”ผโ”€โ”€โ†’ K-Way Merge
SSTable 4 โ”€โ”€ Iterator โ”€โ”€โ”ค
SSTable N โ”€โ”€ Iterator โ”€โ”€โ”˜
                              โ”‚
                              โ–ผ
                       Sequence Resolution
                              โ”‚
                              โ–ผ
                         New SSTable

Each input SSTable is traversed incrementally using an iterator rather than being fully loaded into memory.

For the same key:

SSTable A:

seq=10 โ†’ A = old


SSTable B:

seq=25 โ†’ A = new


SSTable C:

seq=18 โ†’ A = older

the compactor retains:

seq=25 โ†’ A = new

because the highest sequence number represents the newest version.


โšก LRU Block Cache

The LRU Block Cache stores parsed SSTable blocks in memory.

              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚      Block Cache       โ”‚
              โ”‚                        โ”‚
              โ”‚ B1  B7  B9  B12        โ”‚
              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ–ฒ
                         โ”‚
                      GET(key)
                         โ”‚
                   SSTable Index

The cache uses:

std::unordered_map
        +
std::list

to provide average constant-time operations.

Lookup   โ†’ O(1) average
Insert   โ†’ O(1)
Touch    โ†’ O(1)
Eviction โ†’ O(1)

The cache is bounded, so its memory consumption depends on its configured capacity rather than total database size.

Only successfully validated and parsed blocks are inserted into the cache.


๐Ÿ›ก๏ธ Corruption Handling

Persistent storage is treated as untrusted input.

The engine validates:

  • WAL checksums
  • WAL record structure
  • SSTable footer metadata
  • index offsets
  • block offsets
  • block sizes
  • variable-length fields
  • physical file boundaries
  • truncated files

For example, before accessing a block:

offset
  โ”‚
  โ”œโ”€โ”€ must be within file
  โ”‚
size
  โ”‚
  โ””โ”€โ”€ must fit completely within file

Invalid metadata is rejected instead of blindly allocating memory or reading outside the file's valid boundaries.


๐Ÿ“Š Benchmarks

A cache benchmark was run using 10,000 generated records across multiple SSTables.

Random GET

Without Cache : 1028 ms
With Cache    : 479 ms

Improvement   : ~2.15ร— faster

Hot / Repeated GET

Without Cache : 991 ms
With Cache    : 326 ms

Improvement   : ~3.04ร— faster

Cache Statistics

Hits          : 18,569
Misses        : 2,792
Hit Rate      : ~86.9%
Evictions     : 2,728

These results demonstrate the benefit of caching repeatedly accessed SSTable blocks for the tested workload.

Performance is workload- and environment-dependent and should not be interpreted as a universal database benchmark.


๐Ÿงช Testing

The project includes automated tests covering the core storage-engine pipeline.

Test coverage includes:

  • WAL writes and replay
  • CRC32 validation
  • SkipList operations
  • MemTable behavior
  • SSTable creation and reading
  • SSTable corruption handling
  • sequence-number ordering
  • sequence recovery after restart
  • tombstone handling
  • literal tombstone-like user values
  • overlapping SSTables
  • compaction
  • LRU cache behavior
  • lifecycle stress testing

Current test result:

31 tests
31 passed
0 failed

A larger lifecycle stress test also exercises:

20,000 keys
    โ”‚
    โ–ผ
Bulk PUT
    โ”‚
    โ–ผ
Flush
    โ”‚
    โ–ผ
State validation
    โ”‚
    โ–ผ
Delete subset
    โ”‚
    โ–ผ
Additional flushes
    โ”‚
    โ–ผ
Engine shutdown
    โ”‚
    โ–ผ
Restart
    โ”‚
    โ–ผ
State validation

๐Ÿ“‚ Project Structure

LogStoreDB/
โ”‚
โ”œโ”€โ”€ include/
โ”‚   โ”œโ”€โ”€ Block.h
โ”‚   โ”œโ”€โ”€ BlockCache.h
โ”‚   โ”œโ”€โ”€ KVPair.h
โ”‚   โ”œโ”€โ”€ KVStore.h
โ”‚   โ”œโ”€โ”€ MemTable.h
โ”‚   โ”œโ”€โ”€ SSTableReader.h
โ”‚   โ”œโ”€โ”€ SSTableWriter.h
โ”‚   โ”œโ”€โ”€ SkipList.h
โ”‚   โ”œโ”€โ”€ WAL.h
โ”‚   โ””โ”€โ”€ WALRecord.h
โ”‚
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ Block.cpp
โ”‚   โ”œโ”€โ”€ BlockCache.cpp
โ”‚   โ”œโ”€โ”€ KVStore.cpp
โ”‚   โ”œโ”€โ”€ MemTable.cpp
โ”‚   โ”œโ”€โ”€ SSTableReader.cpp
โ”‚   โ”œโ”€โ”€ SSTableWriter.cpp
โ”‚   โ”œโ”€โ”€ SkipList.cpp
โ”‚   โ””โ”€โ”€ WAL.cpp
โ”‚
โ”œโ”€โ”€ cli/
โ”‚   โ””โ”€โ”€ main.cpp
โ”‚
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ test_benchmark.cpp
โ”‚   โ”œโ”€โ”€ test_cache.cpp
โ”‚   โ”œโ”€โ”€ test_corruption.cpp
โ”‚   โ”œโ”€โ”€ test_kvstore.cpp
โ”‚   โ”œโ”€โ”€ test_memtable.cpp
โ”‚   โ”œโ”€โ”€ test_sequence.cpp
โ”‚   โ”œโ”€โ”€ test_skiplist.cpp
โ”‚   โ”œโ”€โ”€ test_sstable.cpp
โ”‚   โ”œโ”€โ”€ test_stress.cpp
โ”‚   โ””โ”€โ”€ test_wal.cpp
โ”‚
โ”œโ”€โ”€ screenshots/
โ”‚   โ”œโ”€โ”€ WAL-binDump.png
โ”‚   โ”œโ”€โ”€ cli-demo.png
โ”‚   โ””โ”€โ”€ crash-recovery.png
โ”‚
โ”œโ”€โ”€ CMakeLists.txt
โ”œโ”€โ”€ README.md
โ””โ”€โ”€ .gitignore

๐Ÿ› ๏ธ Build & Test

LogStoreDB uses CMake and CTest.

Configure

cmake -S . -B build

Build

cmake --build build

Run Tests

ctest --test-dir build --output-on-failure

๐Ÿ’ป CLI Usage

The command-line interface supports basic key-value operations:

PUT <key> <value>
GET <key>
REMOVE <key>
EXIT

Example:

PUT name Sintu
GET name
REMOVE name
GET name
EXIT

Example output flow:

PUT name Sintu
โ†’ OK

GET name
โ†’ Sintu

REMOVE name
โ†’ OK

GET name
โ†’ NOT FOUND

๐Ÿ—๏ธ Design Decisions

Why WAL?

The WAL provides a persistent record of recent mutations that can be replayed after a process restart.

Mutation
   โ”‚
   โ–ผ
WAL
   โ”‚
   โ–ผ
MemTable

This separates recovery durability from the later SSTable flush process.


Why SkipList?

The SkipList provides ordered in-memory storage with expected logarithmic lookup and insertion behavior while remaining relatively simple to implement from scratch.

Its sorted ordering also makes SSTable generation straightforward.


Why SSTables?

SSTables provide immutable sorted storage on disk.

Their block-oriented structure supports:

  • targeted reads
  • sequential writes
  • indexing
  • compaction
  • caching

Why Sequence Numbers?

Filesystem order is not a reliable representation of logical update order.

Explicit sequence numbers provide deterministic version selection:

higher seq
    โ†“
newer version

This becomes particularly important when the same key exists across multiple SSTables.


Why Tombstones?

Deleting only the in-memory entry is insufficient because older copies may still exist in SSTables.

A tombstone ensures that the deletion remains visible during reads and compaction.


Why K-Way Merge?

SSTables are sorted, making them suitable for multi-way merge operations.

Streaming merge avoids materializing all input records into a giant in-memory structure.


Why LRU Cache?

Hot workloads may repeatedly access the same SSTable blocks.

Caching parsed blocks reduces repeated disk reads and parsing work while keeping memory usage bounded.


๐Ÿ“ Complexity

Approximate expected complexity:

Operation Complexity
SkipList lookup O(log N) expected
SkipList insertion O(log N) expected
SkipList deletion O(log N) expected
WAL append O(record size)
MemTable flush O(N) + disk I/O
LRU lookup O(1) average
LRU insertion O(1)
LRU eviction O(1)
K-way compaction O(N log K)
Compaction memory O(K)

Where:

  • N = number of records processed
  • K = number of input SSTables

๐ŸŽฏ Learning Outcomes

This project demonstrates practical understanding of systems and storage-engine concepts:

LSM-Tree Internals

Understanding how mutable in-memory state is transformed into immutable sorted disk structures.

Custom Data Structures

Designed and implemented a SkipList rather than relying solely on a standard ordered container.

Binary File Formats

Worked with explicit serialization of:

  • sequence numbers
  • keys
  • values
  • sizes
  • offsets
  • index metadata
  • footer metadata

Durability & Recovery

Implemented WAL-based replay and persistent sequence recovery across restarts.

Versioned Storage

Used explicit sequence numbers to resolve multiple physical versions of the same logical key.

Deletion Semantics

Implemented tombstones to prevent deleted data from resurfacing from older SSTables.

Compaction

Implemented a streaming K-way merge rather than materializing all records in memory.

Corruption Resistance

Validated persistent metadata and boundaries before using disk-derived sizes and offsets.

Performance Engineering

Implemented and benchmarked an LRU block cache to measure the impact of locality-aware caching.

Testing

Built tests around correctness, corruption, recovery, compaction, sequence ordering, and larger lifecycle workloads.


โš ๏ธ Limitations

LogStoreDB is an educational and portfolio-scale storage engine rather than a production replacement for mature databases.

The current implementation does not attempt to provide:

  • Distributed replication
  • Raft / consensus
  • Sharding
  • Multi-node coordination
  • Multi-key transactions
  • Network database protocol
  • Full production-grade observability
  • Production-scale durability guarantees across every filesystem/hardware configuration
  • The complete multi-level compaction sophistication of systems such as RocksDB

These limitations are intentional. The project focuses on implementing and understanding the fundamental mechanisms of a persistent LSM-style storage engine.


๐Ÿ”ฎ Future Work

Possible future extensions include:

  • Bloom filters
  • More sophisticated multi-level compaction
  • Immutable/frozen MemTables
  • Background flushing
  • Background compaction
  • Fuzz testing
  • Additional crash/failure injection
  • Extended benchmarking
  • More detailed metrics and observability
  • Optional network access

The current core implementation is considered feature-complete for the project's intended scope.


๐Ÿ“Œ Project Status

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚            LogStoreDB STATUS             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ WAL                         โœ“            โ”‚
โ”‚ CRC32                       โœ“            โ”‚
โ”‚ Crash Recovery              โœ“            โ”‚
โ”‚ MemTable                    โœ“            โ”‚
โ”‚ SkipList                    โœ“            โ”‚
โ”‚ Sequence Numbers            โœ“            โ”‚
โ”‚ Tombstones                  โœ“            โ”‚
โ”‚ SSTables                    โœ“            โ”‚
โ”‚ Block Storage               โœ“            โ”‚
โ”‚ SSTable Index               โœ“            โ”‚
โ”‚ SSTable Footer              โœ“            โ”‚
โ”‚ Streaming Compaction        โœ“            โ”‚
โ”‚ Corruption Validation       โœ“            โ”‚
โ”‚ Atomic SSTable Installation โœ“            โ”‚
โ”‚ LRU Block Cache             โœ“            โ”‚
โ”‚ Stress Testing              โœ“            โ”‚
โ”‚ Benchmarks                  โœ“            โ”‚
โ”‚ Automated Tests             โœ“            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“„ License

MIT License

About

C++17 LSM-style key-value storage engine with WAL, SSTables, sequence-based versioning, streaming compaction, corruption validation, and an LRU block cache.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages