mökv
is a distributed, in-memory key-value store. It utilizes Raft
for consensus, serf
for discvoery, gRPC
for communication, and TLS
for security.
Note
This is a project to learn more about distributed systems and Go
.
- Distributed Architecture: Data is replicated across multiple nodes for fault tolerance.
- In-Memory Storage: Provides fast read and write operations.
Raft
Consensus: Ensures data consistency across the cluster.gRPC
Interface: Offers a well-definedAPI
for interacting with the store.TLS
Encryption: Secures communication between nodes and clients.- Access Control: Uses
Casbin
for authorization, enabling fine-grained control over data access. - Metrics: Exposes
Prometheus
metrics for monitoring cluster health and performance. - Service Discovery: Uses
serf
for automatic node discovery and membership management. - Load Balancing: Implements
gRPC
client-side load balancing, directing write operations to the leader and read operations to followers.
To run mökv
:
-
Clone the repository:
git clone git@github.com:dynamic-calm/mokv.git cd mokv
-
Generate TLS Certificates:
make gencert
This command uses
cfssl
to generate the necessaryTLS
certificates in the$HOME/.mokv
directory. -
Compile the code:
make build
This will create an executable binary
mokv
in thebin/
directory.
Configuration is done through command-line flags or a configuration file. A sample configuration file (example/config.yaml
) is provided. Copy certs/model.conf
and certs/policy.csv
to $HOME/.mokv
.
Here's an example config.yaml
:
data-dir: /tmp/mokv-data
node-name: node1
bind-addr: 127.0.0.1:8401
rpc-port: 8400
start-join-addrs: []
bootstrap: true
acl-model-file: $HOME/.mokv/model.conf
acl-policy-file: $HOME/.mokv/policy.csv
server-tls-cert-file: $HOME/.mokv/server.pem
server-tls-key-file: $HOME/.mokv/server-key.pem
server-tls-ca-file: $HOME/.mokv/ca.pem
peer-tls-cert-file: $HOME/.mokv/root-client.pem
peer-tls-key-file: $HOME/.mokv/root-client-key.pem
peer-tls-ca-file: $HOME/.mokv/ca.pem
metrics-port: 4000
-
Start the first node:
bin/mokv --config-file example/config.yaml
-
Start additional nodes:
Modify the
example/config.yaml
file with the appropriatenode-name
,bind-addr
, andrpc-port
. Crucially, setstart-join-addrs
to the address of the first node (e.g.,127.0.0.1:8401
). Also setbootstrap: false
for the additional nodes. Then, run the command again:bin/mokv --config-file example/config2.yaml # Example config for the second node
Refer to
example/start_nodes.sh
for a convenient script to start a cluster.
mökv
exposes a gRPC
API
defined in internal/api/kv.proto
. You can use a gRPC
client to interact with the store.
service KV {
rpc Get(GetRequest) returns (GetResponse) {}
rpc Set(SetRequest) returns (SetResponse) {}
rpc Delete(DeleteRequest) returns (DeleteResponse) {}
rpc List(google.protobuf.Empty) returns (stream GetResponse) {}
rpc GetServers(google.protobuf.Empty) returns (GetServersResponse){}
}
mökv
combines Serf
for node discovery and Raft
for consistent data replication. Here's how the key components interact:
-
Serf
: Dynamic Membership:Serf
usesUDP
to monitor cluster membership. When a node joins, theserf.EventMemberJoin
event triggers theJoin
function (internal/kv/kv.go
), adding the node as aRaft
voter. This ensures theRaft
cluster reflects the current active nodes. -
Raft
: Consensus and the FSM:Raft
guarantees data consistency. One node isLeader
, handling all write operations. Write operations becomeRaft
log entries, replicated toFollowers
. The Finite State Machine (FSM
) is the core ofRaft's
operation:-
Applying Log Entries: When a log entry is committed (acknowledged by a quorum), the
Apply
method of theFSM
(ininternal/kv/kv.go
) is invoked. TheApply
method handles different request types:- Set Request: Updates the in-memory key-value store (
kv.store
) with the new key-value pair. - Delete Request: Removes the specified key from the in-memory store.
- Set Request: Updates the in-memory key-value store (
-
Data Flow for Writes:
gRPC
->Raft Leader
->Log Entry
->Replication to Followers
->FSM Apply
->kv.store
.
-
-
Persistence (
raft-boltdb
):mökv
usesraft-boltdb
to persist Raft's log, stable state, and periodic snapshots to disk. This enables recovery after node failures.- Snapshotting: The
FSM's
Snapshot
method creates a snapshot of the current in-memory state. - Restoring State: After a crash, the
FSM's
Restore
method loads the latest snapshot and replays any subsequent log entries, reconstructing the in-memorykv.store
to a consistent state. This entire process happens automatically whensetupRaft
is called during startup.
- Snapshotting: The
mökv
uses gRPC
for efficient communication between clients and the cluster, secured with TLS
client certificates for authentication and Casbin for authorization.
-
API Definition: The core
gRPC
service,KV
, is defined ininternal/api/kv.proto
, exposing methods likeGet
,Set
,Delete
,List
, andGetServers
. -
gRPC
Server: The server implementation resides ininternal/server/server.go
, handlinggRPC
requests. -
Interceptors:
gRPC
Interceptors are used to handle:- Logging: Each incoming request is logged for monitoring.
- Authentication: Authenticates clients using
TLS
client certificates. The certificate's Common Name (CN
) is extracted and used as the subject for authorization (see below). This is handled in theauthenticate
function ininternal/server/server.go
.
-
Authorization (
Casbin
): Theinternal/auth/auth.go
enforces access control. Casbin determines if the authenticated user (identified by theCN
) has permission to perform an action ("produce" for writes, "consume" for reads). -
Client-Side Load Balancing (Name Resolution and Picker):
mökv
uses client-side load balancing.-
Name Resolver (
internal/discovery/resolver.go
): The name resolver periodically callsGetServers
to discover availablemökv
nodes and their roles (Leader/Follower). It updates the list of available servers with theis_leader
attribute. -
Picker (
internal/discovery/picker.go
): The Picker directs requests based on the operation type and the leader status of available connections:- Writes (
Set
,Delete
): These are routed to the Leader node to ensure consistency. - Reads (
Get
,List
): These are balanced among available Follower nodes for improved read performance.
- Writes (
-