7.9 KiB
JormunDB
A high-performance, DynamoDB-compatible database server written in Odin, backed by RocksDB.
╦╔═╗╦═╗╔╦╗╦ ╦╔╗╔╔╦╗╔╗
║║ ║╠╦╝║║║║ ║║║║ ║║╠╩╗
╚╝╚═╝╩╚═╩ ╩╚═╝╝╚╝═╩╝╚═╝
DynamoDB-Compatible Database
Powered by RocksDB + Odin
What is JormunDB?
JormunDB (formerly ZynamoDB) is a local DynamoDB replacement that speaks the DynamoDB wire protocol. Point your AWS SDK or CLI at it and use it as a drop-in development database.
Why Odin? The original Zig implementation suffered from explicit allocator threading—every function taking an allocator parameter, every allocation needing errdefer cleanup. Odin's implicit context allocator system eliminates this ceremony: one context.allocator = arena_allocator at the request handler entry and everything downstream just works.
Features
- ✅ DynamoDB Wire Protocol: Works with AWS SDKs and CLI out of the box
- ✅ Binary Storage: Efficient TLV encoding for items, varint-prefixed keys
- ✅ Arena-per-Request: Zero explicit memory management in business logic
- ✅ Table Operations: CreateTable, DeleteTable, DescribeTable, ListTables
- ✅ Item Operations: PutItem, GetItem, DeleteItem
- ✅ Query & Scan: With pagination support (Limit, ExclusiveStartKey)
- ✅ Expression Parsing: KeyConditionExpression for Query operations
- ✅ Persistent Storage: RocksDB-backed with full ACID guarantees
- ✅ Concurrency: Table-level RW locks for safe concurrent access
Quick Start
Prerequisites
- Odin compiler (latest)
- RocksDB development libraries
- Standard compression libraries (snappy, lz4, zstd, etc.)
macOS (Homebrew)
brew install rocksdb odin
Ubuntu/Debian
sudo apt install librocksdb-dev libsnappy-dev liblz4-dev libzstd-dev libbz2-dev
# Install Odin from https://odin-lang.org/docs/install/
Build & Run
# Build the server
make build
# Run with default settings (localhost:8002, ./data directory)
make run
# Run with custom port
make run PORT=9000
# Run with custom data directory
make run DATA_DIR=/tmp/jormundb
Test with AWS CLI
# Create a table
aws dynamodb create-table \
--endpoint-url http://localhost:8002 \
--table-name Users \
--key-schema AttributeName=id,KeyType=HASH \
--attribute-definitions AttributeName=id,AttributeType=S \
--billing-mode PAY_PER_REQUEST
# Put an item
aws dynamodb put-item \
--endpoint-url http://localhost:8002 \
--table-name Users \
--item '{"id":{"S":"user123"},"name":{"S":"Alice"},"age":{"N":"30"}}'
# Get an item
aws dynamodb get-item \
--endpoint-url http://localhost:8002 \
--table-name Users \
--key '{"id":{"S":"user123"}}'
# Query items
aws dynamodb query \
--endpoint-url http://localhost:8002 \
--table-name Users \
--key-condition-expression "id = :id" \
--expression-attribute-values '{":id":{"S":"user123"}}'
# Scan table
aws dynamodb scan \
--endpoint-url http://localhost:8002 \
--table-name Users
Architecture
HTTP Request (POST /)
↓
X-Amz-Target header → Operation routing
↓
JSON body → DynamoDB types
↓
Storage engine → RocksDB operations
↓
Binary encoding → Disk
↓
JSON response → Client
Module Structure
jormundb/
├── rocksdb/ - C FFI bindings to librocksdb
├── dynamodb/ - Core types and operations
│ ├── types.odin - AttributeValue, Item, Key, etc.
│ ├── json.odin - DynamoDB JSON serialization
│ ├── storage.odin - Storage engine with RocksDB
│ └── handler.odin - HTTP request handlers
├── key_codec/ - Binary key encoding (varint-prefixed)
├── item_codec/ - Binary TLV item encoding
└── main.odin - HTTP server and entry point
Storage Format
Keys (varint-length-prefixed segments):
Meta: [0x01][len][table_name]
Data: [0x02][len][table_name][len][pk_value][len][sk_value]?
GSI: [0x03][len][table_name][len][index_name][len][gsi_pk][len][gsi_sk]?
LSI: [0x04][len][table_name][len][index_name][len][pk][len][lsi_sk]
Values (TLV binary encoding):
[attr_count:varint]
[name_len:varint][name:bytes][type_tag:u8][value_encoded:bytes]...
Type tags:
String=0x01, Number=0x02, Binary=0x03, Bool=0x04, Null=0x05
SS=0x10, NS=0x11, BS=0x12
List=0x20, Map=0x21
Memory Management
JormunDB uses Odin's context allocator system for elegant memory management:
// Request handler entry point
handle_request :: proc(conn: net.TCP_Socket) {
arena: mem.Arena
mem.arena_init(&arena, make([]byte, mem.Megabyte * 4))
defer mem.arena_destroy(&arena)
context.allocator = mem.arena_allocator(&arena)
// Everything below uses the arena automatically
// No manual frees, no errdefer cleanup needed
request := parse_request() // Uses context.allocator
response := process(request) // Uses context.allocator
send_response(response) // Uses context.allocator
// Arena is freed here automatically
}
Long-lived data (table metadata, locks) uses the default allocator. Request-scoped data uses the arena.
Development
# Build debug version
make build
# Build optimized release
make release
# Run tests
make test
# Format code
make fmt
# Clean build artifacts
make clean
# Run with custom settings
make run PORT=9000 DATA_DIR=/tmp/db VERBOSE=1
Performance
From benchmarks on the original Zig version (Odin expected to be similar or better):
Sequential Writes | 10000 ops | 245.32 ms | 40765 ops/sec
Random Reads | 10000 ops | 312.45 ms | 32006 ops/sec
Batch Writes | 10000 ops | 89.23 ms | 112071 ops/sec
PutItem | 5000 ops | 892.34 ms | 5604 ops/sec
GetItem | 5000 ops | 678.91 ms | 7365 ops/sec
Scan (full table) | 5000 ops | 234.56 ms | 21320 ops/sec
API Compatibility
Supported Operations
- ✅ CreateTable
- ✅ DeleteTable
- ✅ DescribeTable
- ✅ ListTables
- ✅ PutItem
- ✅ GetItem
- ✅ DeleteItem
- ✅ Query (with KeyConditionExpression)
- ✅ Scan (with pagination)
Coming Soon
- ⏳ UpdateItem (with UpdateExpression)
- ⏳ BatchWriteItem
- ⏳ BatchGetItem
- ⏳ Global Secondary Indexes
- ⏳ Local Secondary Indexes
- ⏳ ConditionExpression
- ⏳ FilterExpression
- ⏳ ProjectionExpression
Configuration
Environment Variables
JORMUN_PORT=8002 # Server port
JORMUN_HOST=0.0.0.0 # Bind address
JORMUN_DATA_DIR=./data # RocksDB data directory
JORMUN_VERBOSE=1 # Enable verbose logging
Command Line Arguments
./jormundb --port 9000 --host 127.0.0.1 --data-dir /var/db --verbose
Troubleshooting
"Cannot open RocksDB"
Ensure RocksDB libraries are installed and the data directory is writable:
# Check RocksDB installation
pkg-config --libs rocksdb
# Check permissions
mkdir -p ./data
chmod 755 ./data
"Connection refused"
Check if the port is already in use:
lsof -i :8002
"Invalid JSON" errors
Ensure you're using the correct DynamoDB JSON format:
{
"TableName": "Users",
"Item": {
"id": {"S": "user123"},
"age": {"N": "30"}
}
}
License
MIT License - see LICENSE file for details.
Credits
- Inspired by DynamoDB Local
- Built with Odin
- Powered by RocksDB
- Originally implemented as ZynamoDB in Zig
Contributing
Contributions welcome! Please:
- Format code with
make fmt - Run tests with
make test - Update documentation as needed
- Follow Odin idioms (context allocators, explicit returns, etc.)
Why "Jormun"? Jörmungandr, the World Serpent from Norse mythology—a fitting name for something that wraps around your data. Also, it sounds cool.