dKV is a distributed, consistent key-value store library written in Go, leveraging RAFT consensus. Features include high performance, linearizable consistency, fault tolerance, flexible deployment, time-based operations, distributed locking, and modular architecture.
. Quick Start . Configuration . Benchmarks . Architecture Overview . Detailed Usage . License .
The recommended way to install the dKV client is using the installation script:
curl -s https://raw.githubusercontent.com/ValentinKolb/dKV/refs/heads/main/install.sh | bashThe script automatically detects your operating system and architecture, and installs the appropriate binary.
Installation Options
# Install to a custom location
curl -s https://raw.githubusercontent.com/ValentinKolb/dKV/refs/heads/main/install.sh | bash -s -- --path=/your/custom/path
# Install from source instead of using pre-compiled binaries
curl -s https://raw.githubusercontent.com/ValentinKolb/dKV/refs/heads/main/install.sh | bash -s -- --source
# Show help and all available options
curl -s https://raw.githubusercontent.com/ValentinKolb/dKV/refs/heads/main/install.sh | bash -s -- --helpOnce installed, you can use dKV (requires a server running):
dkv kv set --shard=100 foo bar
dkv kv get --shard=100 fooIt is recommended to use docker to deploy the server. The following command starts a local single node server:
export DKV_TIMEOUT=10 # Set config values for the server as env vars
docker run -p 8080:8080 ghcr.io/valentinkolb/dkv:latest --transport-workers=100 # <- set dkv config flags hereMultiple example docker compose files are provided:
Alternatively you can use the CLI to start a server:
dkv serve --timeout="10" --transport-workers=100To see all available configuration options for the server, run:
dkv serve --help
# Also:
dkv kv --help # for kv operations
dkv lock --help # for lock operationsAll command line flags can be set via environment variables by prefixing them with DKV_:
# --timeout=5 will become:
DKV_TIMEOUT=5
# --log-level=debug will become:
DKV_LOG_LEVEL=debugFor in detail benchmarks and comparisons see here.
dKV follows a nested architecture with clean interfaces between components:
+--------------------------------------------------+ +-----------------+
| RPC Shell | ←-→ | CLI Client |
| | +-----------------+
| +--------------------------------------------+ |
| | Lock Manager (optional) | | +-----------------+
| | | | ←-→ | Golang Client |
| | +--------------------------------------+ | | +-----------------+
| | | Consistency Component (RAFT) | | |
| | | | | | ↑ Load Balancing
| | | +--------------------------------+ | | | ↓ & High Avail.
| | | | Data Core (Maple) | | | |
| | | | | | | | +-----------------+
| | | +--------------------------------+ | | | ←-→ | Other dkv Nodes |
| | +--------------------------------------+ | | # ↑ ↓ #
| +--------------------------------------------+ | ←-→ | Other dkv Nodes |
+--------------------------------------------------+ +-----------------+
dKV provides linearizable consistency for distributed operations. Operations execute atomically in a total order that
respects logical time. With the distributed store (dstore), all operations reflect the most recently committed state
across the cluster. A write on any node is visible to subsequent reads from any node. RAFT consensus ensures data
integrity even with node failures (up to N failures in a 2N+1 node cluster).
Local stores (lstore) offer the same guarantees but only on a single node.
The lock manager provides distributed mutual exclusion when using a distributed store. Locks prevent concurrent access to the same resource across the cluster, with ownership verification ensuring only the lock holder can release it.
At the center of dKV is a high-performance key-value database implementing the KVDB interface.
The Maple reference implementation provides optimized storage with multi-level sharding, lock-free data structures, and efficient garbage collection.
Encapsulating the data core, the consistency component provides linearizable operations through the IStore interface with two implementations:
dstore: Distributed store using RAFT consensuslstore: Local store with high performance, not replicated
An optional wrapper component providing distributed mutex functionality using the ILockManager interface, built on top of any IStore implementation.
The outermost component enables remote communication with interchangeable transports, serialization formats, and adapters:
+--------------------------------+
| Client | Go Client / Cli
+--------------------------------+
| ↑ |
| +------------------------+ |
| | IStore / ILockManager | |
| +------------------------+ |
| | (De-) Serialization | | RPC Client
| +------------------------+ |
| | Client Transport | |
| +------------------------+ |
| ↑ ↓ | Network
| +------------------------+ |
| | Server Transport | |
| +------------------------+ |
| | (De-) Serialization | | RPC Server
| +------------------------+ |
| | Server Adapters | |
| +------------------------+ |
| ↓ |
+--------------------------------+
| IStore / ILockManager | RAFT Cluster
+--------------------------------+
The server supports both local and distributed instances at the same time, allowing flexible deployment configurations. Here's how to set up a three-node cluster:
docker compose -f examples/compose-multi-node.yml upThis creates a cluster with three nodes (if called three time for different ID's), each running:
- Shard
100: Local store (not replicated) - Shard
200: Distributed store (replicated across all nodes) - Shard
300: Distributed lock manager (using the distributed store)
Start server via cli
# Start a three-node cluster (run for IDs 1, 2, and 3)
ID=1 # Change this for each node
dkv serve \
--shards="100:lstore,200:dstore,300:lockmgr(dstore)" \
--replica-id "node-${ID}" \
--cluster-members="node-1=localhost:63001,node-2=localhost:63002,node-3=localhost:63003" \
--data-dir="/tmp/data/node-${ID}" \
--serializer="binary" \
--transport="tcp" \
--transport-endpoint ":808${ID}" \
--transport-workers=100 \
--transport-tcp-nodelay \
--transport-tcp-keepalive=1 \
--transport-tcp-linger=10 \
--log-level="info" \
--timeout=5Local operations are limited to the node they're performed on:
# Set value in local store on one node
dkv kv set --shard=100 --endpoints="localhost:8081" foo bar
# Reading from same node succeeds
dkv kv has --shard=100 --endpoints="localhost:8081" foo # Result: found=true
# Reading from different node fails (local store is not replicated)
dkv kv has --shard=100 --endpoints="localhost:8082" foo # Result: found=falseDistributed operations leverage RAFT consensus to provide linearizable consistency across the entire cluster. When a write succeeds on any node, it's guaranteed that all subsequent reads from any node will immediately reflect this change - even if reading from a different node than where the write occurred:
# Set value in distributed store
dkv kv set --shard=200 --transport-endpoints="localhost:8081" foo bar
# Load balancing across multiple nodes
dkv kv set --shard=200 --transport-endpoints="localhost:8081,localhost:8082,localhost:8083" foo bar
# Reading from any node returns the same data (replicated store)
dkv kv get --shard=200 --transport-endpoints="localhost:8082" foo # Result: found=trueThe local and distributed store supports all operations defined in the IStore interface:
Set(key, value): Store a value with a keySetE(key, value, expireIn, deleteIn): Store with optional expiration and deletion timesSetEIfUnset(key, value, expireIn, deleteIn): Atomic compare-and-swap operationExpire(key): Mark a key's value as expired (still visible with Has())Delete(key): Completely remove a key-value pairGet(key): Retrieve a value by key (not expired)Has(key): Check if a key exists (even if value is expired)GetDBInfo(): Retrieve metadata about the database (not available via RPC - jet)
These operations can be performed either via the CLI or programmatically both remote via RPC and locally.
The lock manager provides powerful distributed mutual exclusion capabilities when combined with a distributed store backend.
When using lockmgr(dstore), the lock guarantees are automatically extended across the entire cluster,
ensuring that if a process on any node acquires a lock, no other process anywhere in the cluster can acquire the same lock simultaneously.
This cluster-wide protection is built on the RAFT consensus protocol's linearizability consistency guarantees:
# Acquire a distributed lock (will be exclusively held across the entire cluster)
dkv lock acquire --shard=300 --transport-endpoints="localhost:8081" foo # Result: acquired=true, ownerId=ID_VALUE
# Release the lock (making it available to any other process in the cluster)
dkv lock release --shard=300 --transport-endpoints="localhost:8081" foo ID_VALUE # Result: released=truedKV includes built-in benchmarking capabilities to evaluate performance:
# Run performance benchmarks
dkv kv perf --threads=10 --skip="mixed" --large-value-size=128 --keys=500 --csv="./benchmark.csv"You can integrate dKV directly into your Go applications. First, install the library:
go get github.com/ValentinKolb/dKVFor single-node applications, the local store provides optimal performance:
package main
import (
"log"
"github.com/ValentinKolb/dKV/lib/store/lstore"
"github.com/ValentinKolb/dKV/lib/db/engines/maple"
"github.com/ValentinKolb/dKV/lib/lockmgr"
"github.com/ValentinKolb/dKV/lib/db"
)
func main() {
// Create a database factory function
dbFactory := func() db.KVDB { return maple.NewMapleDB(nil) }
// Create a local store
store := lstore.NewLocalStore(dbFactory)
// Perform key-value operations
_ = store.Set("keys/foo", []byte("bar"))
value, exists, _ := store.Get("keys/foo")
// Create a lock manager using the store
lockMgr := lockmgr.NewLockManager(store)
// Perform locking operations
acquired, ownerId, _ := lockMgr.AcquireLock("locks/foo", 30)
if acquired {
// Use the resource safely
lockMgr.ReleaseLock("locks/foo", ownerId)
}
}While it's possible to create a distributed store directly with the dKV library in Go, this approach requires in-depth knowledge of the Dragonboat RAFT library and isn't generally recommended. A reference implementation of a RAFT cluster is available in the RPC server code for those interested in the details. For most use cases, simply using the RPC server is the recommended approach for distributed storage capabilities.
Client applications can communicate with dKV servers using the RPC shell:
package main
import (
"log"
"github.com/ValentinKolb/dKV/rpc/common"
"github.com/ValentinKolb/dKV/rpc/serializer"
"github.com/ValentinKolb/dKV/rpc/transport/tcp"
"github.com/ValentinKolb/dKV/rpc/client"
)
func main() {
// Create client configuration
config := common.ClientConfig{
TimeoutSecond: viper.GetInt("timeout"),
Transport: common.ClientTransportConfig{
RetryCount: 2,
Endpoints: []string{"localhost:8080"},
SocketConf: common.SocketConf{
WriteBufferSize: 512 * 1024,
ReadBufferSize: 5125 * 1024,
// ...
},
TCPConf: common.TCPConf{
TCPNoDelay: true,
// ...
},
},
}
// Create serializer and transport
t := tcp.NewTCPClientTransport()
s := serializer.NewBinarySerializer()
// Create store client
rpcStore, _ := client.NewRPCStore(100, config, t, s)
// Perform key-value operations
_ = rpcStore.Set("foo", []byte("bar"))
value, exists, _ := rpcStore.Get("foo")
// Create lock manager client
rpcLock, _ := client.NewRPCLockMgr(200, config, t, s)
// Perform locking operations
acquired, ownerId, _ := rpcLock.AcquireLock("foo", 30)
if acquired {
// Use the resource safely
rpcLock.ReleaseLock("foo", ownerId)
}
}Custom configurations can be created for server applications:
package main
import (
"log"
"github.com/ValentinKolb/dKV/rpc/common"
"github.com/ValentinKolb/dKV/rpc/serializer"
"github.com/ValentinKolb/dKV/rpc/transport/tcp"
"github.com/ValentinKolb/dKV/rpc/server"
)
func main() {
// Create server configuration
config := common.ServerConfig{
TimeoutSecond: 5,
LogLevel: "info",
Shards: []common.ServerShard{
{ShardID: 100, Type: common.ShardTypeLocalIStore},
{ShardID: 200, Type: common.ShardTypeLocalILockManager},
},
Transport: common.ServerTransportConfig{
TCPConf: common.TCPConf{
// ...
},
SocketConf: common.SocketConf{
// ...
},
WorkersPerConn: 100,
Endpoint: ":8080",
},
}
// Create and start the server
s := server.NewRPCServer(
config,
tcp.NewTCPServerTransport(1024 * 1024), // 1 MB buffer size
serializer.NewBinarySerializer(),
)
// Start the server
if err := s.Serve(); err != nil {
log.Fatalf("Server error: %v", err)
}
}Distributed under the MIT License. See LICENSE for details.
- This library is part of my Bachelor Thesis at the University of Ulm.
- Dragonboat: RAFT consensus implementation.
- xsync: High-performance concurrent data structures for Go.
- The dKV logo uses the original Gopher design created by Renee French

