
A suite of embedded data stores in Nim with
write-ahead log (WAL) support for durability and crash recovery
nimble install boogie
- BTrees storage and Hash Tables for fast lookups
- Write-ahead log (WAL) for durability and crash recovery
- Simple API for inserting, updating, deleting, and querying records
- Configurable options for performance tuning, such as batch sizes and flush intervals
- In-memory or On-disk storage modes
- Primitive data types (
string,int,float,bool,json,null)
What's included?
| Store Type | Description |
|---|---|
| Key/Value Store | A simple key-value store implementation with WAL support |
| RDBMS Store | A relational wal-based database with support for schemas, primary keys and typed columns (However, it currently lacks explicit support for foreign keys, joins, or advanced relational features (relations between tables)) |
| Vector Store | Vector store implementation with WAL support |
| Columnar Store | Columnar storage engine for analytics workloads with WAL support |
| Graph Store | A simple graph database with support for nodes, relationships, and basic graph queries (e.g., neighbors, shortest path) with WAL support |
Note
Boogie is an experimental project mostly made with the chatbot for fun and learning. It is still in early stages, so expect data loss and breaking changes. Use at your own risk.
This can be used as a simple embedded database for your Nim applications. If you want, you can use openpeeps/e2ee to encrypt the data before inserting it into Boogie database.
Here is a simple example of how to use Boogie
Here is an example of using the RDBMS store to create a table, insert some data, and query it:
import boogie/stores/rdbms
var db = newStore("tests" / "data" / "myboogie.db", StorageMode.smDisk,
enableWal = true, walFlushEveryOps = 100'u32)
# Create a table with some columns
db.createTable(newTable(
name = "users",
primaryKey = "id",
columns = [
newColumn("id", DataType.dtInt, false),
newColumn("name", DataType.dtText, false),
newColumn("age", DataType.dtInt, false),
newColumn("active", DataType.dtBool, false),
newColumn("meta", DataType.dtJson, true)
]
))
# insert some data
db.insertRow("users", row({
"name": newTextValue("Alice"),
"age": newIntValue(30),
"active": newBoolValue(true),
"meta": newJsonValue(%*{"hobbies": ["reading", "hiking"]})
}))
# flush the WAL to disk (when enabled)
db.checkpoint()
# Query the data
for row in db.getTable("users").get().allRows():
for key, col in row[1]:
echo fmt"{key}: {$col}"Boogie also provides a simple key-value store implementation with WAL support.
import boogie/stores/kv
let kv = newKVStore("./mykv.db", StorageMode.ksmDisk,
enableWal = true,
checkpointEveryOps = 50'u32)
kv.put("name", "Alice")
assert kv.get("name") == "Alice"
kv.delete("name")
assert kv.hasKey("name") == falseNote
Check the tests for more examples.
Here you can find some benchmarks for the available stores. You can run it yourself by cloning the repo and running nimble test -d:release (note -d:release flag is required for accurate benchmarks)
[Suite] No WAL + memory store tests
Database opened in 0.000 seconds
[OK] init database without WAL
[OK] create table
Insert: 0.594 s for 100000 rows
[OK] insert rows
Lookup: 0.076 s for 100000 gets (hits=100000)
[OK] lookup rows
Ordered scan: 0.978 s for 100000 rows
[OK] ordered scan
Unsorted scan: 0.067 s for 100000 rows
[OK] ordered scan #2
Where scan: 0.065 s for 3334 matches
[OK] where scan
[Suite] No WAL + disk store tests
Database opened in 0.000 seconds
[OK] init database without WAL
[OK] create table
Insert: 0.502 s for 100000 rows
[OK] insert rows
Lookup: 0.108 s for 100000 gets (hits=100000)
[OK] lookup rows
Ordered scan: 0.754 s for 100000 rows
[OK] ordered scan
Unsorted scan: 0.103 s for 100000 rows
[OK] ordered scan #2
Where scan: 0.081 s for 3334 matches
[OK] where scan
[Suite] WAL + disk store tests
Database opened in 0.001 seconds
[OK] init database without WAL
[OK] create table
Insert: 0.056 s for 10000 rows
[OK] insert rows
Lookup: 0.010 s for 10000 gets (hits=10000)
[OK] lookup rows
Ordered scan: 0.075 s for 10000 rows
[OK] ordered scan
Unsorted scan: 0.010 s for 10000 rows
[OK] ordered scan #2
Where scan: 0.008 s for 334 matches
[OK] where scan
[Suite] WAL + memory store tests
Database opened in 0.000 seconds
[OK] init database without WAL
[OK] create table
Insert: 0.046 s for 10000 rows
[OK] insert rows
Lookup: 0.010 s for 10000 gets (hits=10000)
[OK] lookup rows
Ordered scan: 0.076 s for 10000 rows
[OK] ordered scan
Unsorted scan: 0.010 s for 10000 rows
[OK] ordered scan #2
Where scan: 0.008 s for 334 matches
[OK] where scan
[Suite] WAL functions tests
Recovered rows: 1000
[OK] WAL crash recovery
As you can see, the performance of the RDBMS store is quite good, especially when using the WAL. The in-memory store is faster than the disk store, but the disk store provides durability and crash recovery. The WAL also significantly improves the performance of inserts and lookups.
todo
- Add support for multiple tables
- Add basic tests and benchmarks
- π Found a bug? Create a new Issue
- π Wanna help? Fork it!
- π Get β¬20 in cloud credits from Hetzner
LGPLv3 license. Made by Humans from OpenPeeps.
Copyright OpenPeeps & Contributors β All rights reserved.