A lightweight, embeddable key-value database written in Go. Logra stores data in append-only flat files with an in-memory index for O(1) key lookups, and exposes a RESP-compatible TCP server so any Redis client can talk to it.
- Append-only storage with binary record encoding (CRC32 checksums)
- In-memory hash index for O(1) key lookups
- RESP protocol server compatible with
redis-cliand all Redis client libraries - Goroutine-safe with
sync.RWMutex(concurrent reads, exclusive writes) - Automatic file rotation at 1MB per data file
- Log compaction removes tombstones and reclaims disk space
- Crash recovery for interrupted compaction
git clone https://github.com/sakthirathinam/logra.git
cd logra
make build
make build-serverRequires Go 1.25.5+.
Start the RESP server:
# Default: listens on :6379, stores data in ./logra_data
make run-server
# Custom address and data directory
make run-server ADDR=:6380 DB=/var/lib/logra
# Or run the binary directly
./logra-server -addr :6379 -db logra_dataUse any Redis client to connect:
redis-cli SET mykey myvalue # OK
redis-cli GET mykey # "myvalue"
redis-cli EXISTS mykey # (integer) 1
redis-cli DEL mykey # (integer) 1
redis-cli GET mykey # (nil)
redis-cli PING # PONG./logra version
./logra set mykey myvalue
./logra get mykey
./logra del mykey
./logra compact| Command | Description |
|---|---|
PING |
Returns PONG (or echoes argument) |
GET key |
Get value by key |
SET key value |
Set a key-value pair |
DEL key [key ...] |
Delete one or more keys |
EXISTS key [key ...] |
Check if keys exist |
DBSIZE |
Return number of keys |
┌─────────────────────────────────────────────┐
│ redis-cli / client │
└──────────────────┬──────────────────────────┘
│ RESP over TCP
┌──────────────────▼──────────────────────────┐
│ server/server.go (goroutine-per-conn) │
│ server/resp.go (RESP2 parser) │
│ server/handler.go (command dispatch) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ db.go (LograDB) │
│ ├── sync.RWMutex (concurrent access) │
│ ├── Index (in-memory hash map) │
│ └── Storage (append-only files) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 0.dat 1.dat 2.dat ... (data files) │
│ Record: [CRC|KeySize|ValSize|TS|Key|Value] │
└─────────────────────────────────────────────┘
Each record is binary-encoded:
Header (20 bytes):
[CRC32 (4)] [KeySize (4)] [ValueSize (4)] [Timestamp (8)]
Body:
[Key (KeySize bytes)] [Value (ValueSize bytes)]
Deletions are stored as tombstones (ValueSize = 0), cleaned up during compaction.
Run compact to merge data files, drop tombstones, and reclaim space:
./logra compactCompaction is crash-safe. If interrupted, it recovers automatically on the next startup.
Benchmarked using redis-benchmark against the Logra RESP server on the same machine.
Environment: Linux, Go 1.25.5, AMD/Intel multi-core
| Command | 50 clients | 100 clients | 200 clients |
|---|---|---|---|
| PING (inline) | 86,207 rps | - | - |
| PING (bulk) | 87,184 rps | - | - |
| SET | 80,000 rps | 76,864 rps | 74,460 rps |
| GET | 78,927 rps | 75,358 rps | 74,738 rps |
| Command | 50 clients |
|---|---|
| SET | 74,627 rps |
| GET | 75,188 rps |
| Command | rps |
|---|---|
| SET | 85,690 rps |
| GET | 282,486 rps |
| Command | 50 clients | 100 clients | 200 clients |
|---|---|---|---|
| SET | 0.319 ms | 0.671 ms | 1.367 ms |
| GET | 0.327 ms | 0.663 ms | 1.327 ms |
| Operation | ops/sec | ns/op | allocs/op |
|---|---|---|---|
Has (index lookup) |
10,772,500 | 103 ns | 1 |
Set (new key) |
203,256 | 5,819 ns | 18 |
Get (100B value) |
119,391 | 9,907 ns | 18 |
Get (1KB value) |
112,873 | 10,469 ns | 18 |
Get (10KB value) |
85,257 | 14,314 ns | 18 |
logra/
├── cmd/
│ ├── logra/ # CLI entry point
│ └── logra-server/ # RESP server entry point
├── server/
│ ├── resp.go # RESP2 protocol parser/serializer
│ ├── handler.go # Command dispatch
│ ├── server.go # TCP listener, goroutine-per-conn
│ ├── resp_test.go
│ └── server_test.go
├── internal/
│ ├── index/ # In-memory hash map index
│ ├── storage/ # Append-only file storage + record encoding
│ └── compact/ # Log compaction
├── db.go # LograDB core (Open, Get, Set, Delete, Has)
├── db_test.go
├── db_bench_test.go
├── e2e_test.go
├── Makefile
└── go.mod
make build Build the CLI binary
make build-server Build the server binary
make run-server Start RESP server (ADDR=:6379 DB=logra_data)
make run Run CLI (CMD=version)
make test Run all tests
make test-unit Run unit tests only
make test-e2e Run E2E tests only
make test-race Run tests with race detection
make bench Run Go benchmarks
make coverage Generate coverage report
make fmt Format code
make vet Run go vet
make clean Remove build artifacts
- File-level locking (
flock) - Cross-process safety usinggofrs/flock(scaffolded, not yet enabled) - Docker support - Dockerfile and docker-compose for single-command deployment
- Write buffer pool - Reuse
[]bytebuffers withsync.Poolto reduce GC pressure on write-heavy workloads - Batch writes - Group multiple SET operations into a single fsync for higher throughput
- TTL / key expiration - Support
SET key value EX secondsand background expiry goroutine - Snapshotting - Periodic point-in-time snapshots for backup/restore
- MGET / MSET - Multi-key operations in a single round-trip
- Range queries - Ordered index (B-tree or skip list) for key range scans
- mmap reads - Memory-mapped file reads to skip syscall overhead
- io_uring - Async I/O on Linux for storage operations
- Index persistence - Dump index to disk to avoid full scan on startup
- Compaction scheduling - Automatic background compaction based on tombstone ratio