Simple LSM Tree database
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-09 15:13:45 -05:00
bufferpool Trying to make bufferpool more effecient 2026-10-09 14:35:20 -05:00
cmd 2026-10-09 14:35:20 -05:00
compression Running the fieldalignment tool to get structs ordered better for 2025-12-12 18:22:26 -06:00
dberr 2026-10-09 14:35:20 -05:00
epoch Hardening the removal of files. While looking at the other race 2026-10-09 14:35:20 -05:00
fs 2026-10-09 14:35:20 -05:00
keys Small optimization for comparing keys 2026-10-09 14:35:20 -05:00
memtable Using RLock instead of Lock for the memtable. I think there was a 2026-10-09 14:35:20 -05:00
sstable Minor fixes for some previous changes 2026-10-09 15:13:45 -05:00
wal Reworking how single Get operations work. Wasn't even hitting the bloom filters. 2026-10-09 15:13:45 -05:00
.gitignore Cleaning up old benchmark scripts. These are being kept in their own branch for now. 2025-11-29 11:10:45 -06:00
batch.go 2026-10-09 14:35:20 -05:00
batch_test.go 2026-10-09 14:35:20 -05:00
benchmark_test.go Adding LGPLv3 license 2025-11-27 22:26:52 -06:00
compaction.go Working on the recently discovered errors after upgrading to go 1.27 2026-10-09 14:35:20 -05:00
compaction_bench_test.go Adding LGPLv3 license 2025-11-27 22:26:52 -06:00
compaction_policy.go 2026-10-09 14:35:20 -05:00
compaction_test.go Working on the recently discovered errors after upgrading to go 1.27 2026-10-09 14:35:20 -05:00
compression_integration_test.go 2026-10-09 14:35:20 -05:00
concurrency_bench_test.go 2026-10-09 14:35:20 -05:00
concurrency_test.go 2026-10-09 14:35:20 -05:00
core_operations_test.go 2026-10-09 14:35:20 -05:00
db.go Minor fixes for some previous changes 2026-10-09 15:13:45 -05:00
disable_wal_test.go Adding LGPLv3 license 2025-11-27 22:26:52 -06:00
durability_test.go Fixing large resource leak. Files opened through the file cache were 2026-01-28 21:02:36 +00:00
filecache.go Reworking how single Get operations work. Wasn't even hitting the bloom filters. 2026-10-09 15:13:45 -05:00
flock_test.go 2026-10-09 14:35:20 -05:00
go.mod 2026-10-09 14:35:20 -05:00
go.sum Bumping compression version again. Ran go fix 2026-10-06 11:24:02 +00:00
interval_tree.go Reworking how single Get operations work. Wasn't even hitting the bloom filters. 2026-10-09 15:13:45 -05:00
iterator.go 2026-10-09 14:35:20 -05:00
iterator_integration_test.go 2026-10-09 14:35:20 -05:00
LICENSE.md Ran go fix and changed LICENCE to .md 2026-07-14 19:24:59 -05:00
lifecycle_integration_test.go 2026-10-09 14:35:20 -05:00
manifest.go 2026-10-09 14:35:20 -05:00
merge_iterator.go 2026-10-09 14:35:20 -05:00
merge_iterator_bench_test.go 2026-10-09 14:35:20 -05:00
merge_iterator_test.go 2026-10-09 14:35:20 -05:00
options.go 2026-10-09 14:35:20 -05:00
options_test.go 2026-10-09 14:35:20 -05:00
query_performance_bench_test.go Running the fieldalignment tool to get structs ordered better for 2025-12-12 18:22:26 -06:00
range_delete.go 2026-10-09 14:35:20 -05:00
range_delete_bench_test.go Adding LGPLv3 license 2025-11-27 22:26:52 -06:00
range_delete_optimization_test.go 2026-10-09 14:35:20 -05:00
range_delete_test.go Working on the recently discovered errors after upgrading to go 1.27 2026-10-09 14:35:20 -05:00
range_operations_test.go 2026-10-09 14:35:20 -05:00
README.md 2026-10-09 14:35:20 -05:00
recovery_test.go Adding LGPLv3 license 2025-11-27 22:26:52 -06:00
snapshot.go 2026-10-09 14:35:20 -05:00
sstable_builder.go 2026-10-09 14:35:20 -05:00
stress_test.go 2026-10-09 14:35:20 -05:00
system_integration_test.go 2026-10-09 14:35:20 -05:00
testutil.go 2026-10-09 14:35:20 -05:00
version.go Reworking how single Get operations work. Wasn't even hitting the bloom filters. 2026-10-09 15:13:45 -05:00
write_options.go No comment 2025-12-08 22:55:55 -06:00
write_options_test.go Working on test coverage 2025-12-19 11:52:52 -06:00

lgdb - Level Go Database

An embedded key-value store. Thread-safe, minimal dependencies (just compression at this point). References used include pebble, rocksdb, and goleveldb

Started as a learning project but ended up actually being useful. Did my best to keep things simple and understandable while maintaining performance. Easier said than done.

Doesn't have bloom filters since my main use case is range queries on time series data. May add them as an option at some point.

Range Deletion

I have attempted a bunch of different methods, but was unsatisfied with the results. Current solution is to store range deletions in a manifest like file and read into memory on open. All iterators respect the range deletion including compactions. To keep these range deletions from lingering in memory for too long the db attempts to compact them away after sweeping L0 and making sure no other levels are over their size limit. The compaction manager will select the oldest range deletion and perform a same level compaction by rewriting any sstable with overlapping keys and containing a lower sequence (without the keys covered by the range deletion of course). After clearing all levels of possible overlap the range deletion is removed. Still needs more testing, but initially seems to perform well for my workload.

Tiered Compression

More a sliding range where you can set lower levels to one compression and higher levels to a different one. Run hot data (L0-L2) through fast S2 compression and let the cold data (L3+) get hit with Zstd for space efficiency. Or just pick one compression method for everything if you prefer.

Quick Start

package main

import (
	"fmt"
	"log"

	"twlk9.com/twlk9/lgdb"
)

func main() {
	// Open database
	opts := lgdb.DefaultOptions()
	opts.Path = "/tmp/mydb"
	db, err := lgdb.Open(opts)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Put a value
	err = db.Put([]byte("name"), []byte("Alice"))
	if err != nil {
		log.Fatal(err)
	}

	// Get a value
	value, err := db.Get([]byte("name"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("name = %s\n", value)

	// Delete a key
	err = db.Delete([]byte("name"))
	if err != nil {
		log.Fatal(err)
	}

	// Iterate over all keys
	iter := db.NewIterator(nil)
	defer iter.Close()

	for iter.SeekToFirst(); iter.Valid(); iter.Next() {
		key := iter.Key()
		value := iter.Value()
		fmt.Printf("%s = %s\n", key, value)
	}

	// Iterate over key range
	rangeIter, err := db.Scan([]byte("key2"), []byte("key4"), nil)
	if err != nil {
		log.Fatal(err)
	}
	defer rangeIter.Close()
	for rangeIter.SeekToFirst(); rangeIter.Valid(); rangeIter.Next() {
		fmt.Printf("%s\n", rangeIter.Key())
	}

	// Iterate over a prefix
	prefixIter, err := db.ScanPrefix([]byte("user:"), nil)
	if err != nil {
		log.Fatal(err)
	}
	defer prefixIter.Close()

	for prefixIter.SeekToFirst(); prefixIter.Valid(); prefixIter.Next() {
		fmt.Printf("%s\n", prefixIter.Key())
	}
}