This is a distributed key-value store system implementing a master-slave replication pattern with Write-Ahead Logging (WAL) for durability. The system is built using Go, Connect-RPC, and Protocol Buffers, designed to provide high availability and eventual consistency.
- WRITE
flowchart LR
A[Write request] --> B[Compute Layer]
B --> C[In-Memory Engine]
B -->|Replicate to Slave| CS[In-Memory Engine]
C --> D[Storage Layer]
D --> E[WAL]
E --> F[DISK]
CS --> DS[Storage Layer]
DS --> ES[WAL]
ES --> FS[DISK]
- READ
flowchart LR
A[Read request] --> B[Compute Layer]
C --> B
B --> C[In-Memory Engine]
D --> B
C -->|ON FAIL| D[Storage Layer]
E --> B
D -->|ON FAIL| E[WAL]
The system consists of several key components:
- Master Node
- Slave Nodes
- Write-Ahead Log (WAL)
- In-Memory Storage Engine
- Replication System
Master Node
├── In-Memory Storage (sync.Map)
├── Write-Ahead Log (WAL)
└── Compute Layer
└── TCP Server (Connect-RPC)
Slave Node
├── In-Memory Storage (sync.Map)
├── Write-Ahead Log (WAL)
└── Compute Layer
└── TCP Server (Connect-RPC)
Purpose: Handles all write operations and coordinates replication to slave nodes.
Key Responsibilities:
- Processes write operations (SET, DELETE)
- Maintains the primary copy of data
- Logs operations to WAL
- Keeps an ongoing syncloop that syncs with slaves on an interval
Location: internal/master/master.go
Key Methods:
AddSlave(addr)
: Add a slave connectionRemoveSlave(addr)
: Remove a slave connectionReplicateEntry(entry)
: Manual entry replication
Purpose: Maintains a replicated copy of the data and handles read operations.
Key Responsibilities:
- Handles read operations
- Periodically pulls updates from master
- Maintains local WAL
- Provides read-only access to data
Location: internal/slave/slave.go
Key Methods:
Get(key)
: Read operationsReplicate(entries)
: Receives entries that are up to be replicated into current slave node
Purpose: Provides methods to be used by nodes to manage in memory data.
Location: internal/server/server.go
Key Features:
- Atomic data management using sync.Map
- Why sync.Map over a regular Map? Because we're creating a cache storage that cannot be edited and can only be written or viewed. It's one of 2 cases when a sync.Map is more optimized, than a regular map. https://pkg.go.dev/sync#Map
Key Methods:
Set(key, value)
: Write operationsDelete(key)
: Delete operationsGet(key)
: Get record by key
Purpose: Ensures durability of operations and enables recovery after crashes.
Location: internal/wal/wal.go
Key Features:
- Sequential write operations
- Entry format includes timestamps and operation types
- Supports recovery operations
- Manages log file rotation
Key Methods:
AppendEntry(entry)
: Logs new operationsRecover()
: Rebuilds state from logGetEntriesSince(timestamp)
: Retrieves entries for replication
Purpose: Manages in-memory data storage using sync.Map for thread-safe operations.
Location: internal/storage/storage.go
Key Features:
- Thread-safe operations using sync.Map
- Support for TTL (Time-To-Live)
- Memory management and eviction
Location: api/kvstore.proto
service KVStore {
rpc Get(GetRequest) returns (GetResponse);
rpc Set(SetRequest) returns (SetResponse);
rpc Delete(DeleteRequest) returns (DeleteResponse);
rpc GetUpdates(GetUpdatesRequest) returns (GetUpdatesResponse);
}
-
Write Operation:
Client -> Master Node -> WAL -> In-Memory Storage -> Slave Nodes (via push-based replication)
-
Read Operation:
Client -> Any Node (Slave) -> In-Memory Storage
-
Replication Flow:
Master -> Slave (Push updates) Slave -> Master (Updates Response) Slave -> Local WAL -> In Memory DB
server:
port: 8080
address: "0.0.0.0"
wal:
directory: "/var/log/kvstore/master"
sync_interval: "1s"
max_file_size: "100MB"
storage:
max_entries: 1000000
eviction_policy: "lru"
server:
port: 8081
address: "0.0.0.0"
master:
address: "master:8080"
sync_interval: "1s"
wal:
directory: "/var/log/kvstore/slave"
sync_interval: "1s"
- Go 1.19 or later
- Protocol Buffer compiler
- Connect-go
# Generate ConnectRPC Protocol Buffer code
buf generate
# Build master and slave binaries
go build -o master ./cmd/master
go build -o slave ./cmd/slave
- Start the master node:
./master -config master-config.yaml
- Start slave nodes:
./slave -config slave-config.yaml
- Reads WAL entries on startup
- Rebuilds in-memory state
- Resumes normal operation
- Reads local WAL entries
- Rebuilds local state
- Resumes normal operation This project is licensed under the MIT License - see the LICENSE file for details.