Skip to content

atharvaarbat/concurrent-kv-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ConcurrentKV — Multi-threaded Key-Value Store

A Redis-inspired, high-performance key-value store written in Go, featuring a custom binary TCP protocol, lock-free MPSC queue, LRU eviction, and WAL-based crash recovery.

Features

  • 🚀 High Performance: ~49,000 ops/sec on a single connection (local benchmark)
  • 🧵 Multi-threaded Architecture: 8-worker thread pool with lock-free MPSC queue
  • 📦 Custom Binary Protocol: Compact, length-prefixed TCP protocol with pipelining support
  • 💾 LRU Eviction: O(1) eviction using doubly-linked list + hash map
  • 📝 Write-Ahead Logging: Crash recovery via WAL replay on startup
  • 🔑 Core Commands: GET, SET, DEL, EXPIRE with TTL support
  • 🪟 Windows Compatible: Built and tested on Windows 11

Architecture

┌─────────────┐
│   Clients   │
└──────┬──────┘
       │ TCP (Binary Protocol)
┌──────▼──────────────────────────────┐
│          TCP Server                 │
│  ┌────────────────────────────┐     │
│  │  Connection Handlers (N)   │     │
│  │  - Read requests           │     │
│  │  - Enqueue to MPSC queue   │     │
│  │  - Write ordered responses │     │
│  └──────────┬─────────────────┘     │
│             │                       │
│  ┌──────────▼─────────────────┐     │
│  │  Lock-Free MPSC Queue      │     │
│  └──────────┬─────────────────┘     │
│             │                       │
│  ┌──────────▼─────────────────┐     │
│  │  Worker Pool (8 goroutines)│     │
│  │  - Process commands        │     │
│  │  - Update KV Store         │     │
│  │  - Append to WAL           │     │
│  │  - Return responses        │     │
│  └──────────┬─────────────────┘     │
│             │                       │
│  ┌──────────▼─────────────────┐     │
│  │  KV Store                  │     │
│  │  - map[string]*entry       │     │
│  │  - LRU List (head↔tal)     │     │
│  └──────────┬─────────────────┘     │
│             │                       │
│  ┌──────────▼─────────────────┐     │
│  │  Write-Ahead Log (WAL)     │     │
│  │  - Append-only file        │     │
│  │  - Periodic fsync          │     │
│  └────────────────────────────┘     │
└─────────────────────────────────────┘

Wire Protocol

All integers are little-endian. Messages are length-prefixed:

[4 bytes message length][payload]

Request Format

GET

[1 byte CMD=0x01][2 bytes key length][key bytes]

SET

[1 byte CMD=0x02][2 bytes key length][4 bytes TTL seconds][2 bytes value length][key bytes][value bytes]
  • TTL = 0 means no expiration

DEL

[1 byte CMD=0x03][2 bytes key length][key bytes]

EXPIRE

[1 byte CMD=0x04][2 bytes key length][4 bytes TTL seconds][key bytes]

Response Format

GET Response

[1 byte status][if status=0x00: 4 bytes value length][value bytes]
  • 0x00 = found
  • 0x01 = not found

SET/DEL/EXPIRE Response

[1 byte status]
  • 0x00 = success
  • 0x01 = failure

Project Structure

concurrentkv/
├── cmd/
│   └── benchmark/
│       └── main.go          # Benchmark client
├── protocol/
│   └── protocol.go          # Binary protocol encoding/decoding
├── queue/
│   └── mpsc.go              # Lock-free MPSC queue
├── server/
│   ├── tcp.go               # TCP connection handling
│   └── workerpool.go        # Worker pool with command processing
├── store/
│   ├── kv.go                # Key-value store with TTL support
│   └── lru.go               # O(1) LRU eviction list
├── wal/
│   └── wal.go               # Write-ahead logging
├── main.go                  # Server entry point
├── go.mod
└── README.md

Quick Start

Prerequisites

  • Go 1.22 or later
  • Windows 11, Linux, or macOS

Build

# Clone the repository
git clone https://github.com/yourusername/concurrentkv.git
cd concurrentkv

# Build the server
go build -o concurrentkv

# Or run directly
go run main.go

Run the Server

# Default settings (port 31337, 100k max keys)
./concurrentkv

# Custom configuration
./concurrentkv -addr :8080 -maxkeys 50000 -wal data.wal

Command-line flags:

  • -addr: TCP listen address (default: :31337)
  • -maxkeys: Maximum keys in store before LRU eviction (default: 100000)
  • -wal: WAL file path (default: concurrentkv.wal)

Run the Benchmark

In another terminal:

go run ./cmd/benchmark

Expected output:

Completed 50000 SETs in 1.02s, 48967 ops/sec

Performance

Local Benchmark (Windows 11, Intel i7, 16GB RAM)

Metric Value
Throughput (single client) ~49,000 ops/sec
Latency (average) ~20 µs
Workers 8
Concurrency Model MPSC lock-free queue

Comparison with Redis 7

Server Ops/sec (1 client, SET) Architecture
ConcurrentKV 49,000 Go, multi-threaded, WAL
Redis 7 98,000 C, single-threaded, event loop

ConcurrentKV achieves ~50% of Redis throughput while providing multi-threaded execution and built-in LRU eviction.

Implementation Details

Lock-Free MPSC Queue

The MPSC (Multiple Producer, Single Consumer) queue uses atomic compare-and-swap operations on a singly-linked list:

  • Enqueue: Producers atomically append nodes to the tail
  • Dequeue: Single consumer removes from the head
  • Lock-free: No mutexes, minimal contention
  • Bounded: Effectively unbounded (limited by memory)
type MPSCQueue struct {
    head atomic.Pointer[node]
    tail atomic.Pointer[node]
}

O(1) LRU Eviction

The LRU cache combines:

  • HashMap (map[string]*entry): O(1) key lookups
  • Doubly-linked list: O(1) move-to-front and evict-from-back
type lruList struct {
    head *lruNode  // sentinel
    tail *lruNode  // sentinel
    size int
}

Write-Ahead Logging (WAL)

  • Format: Binary, append-only log file
  • Operations logged: SET, DEL, EXPIRE
  • Sync policy: Periodic fsync (1 second intervals) + per-entry write
  • Recovery: Replays all operations on startup

Pipelining Support

The server supports full pipelining—clients can send multiple requests without waiting for responses. Responses are returned in the same order as requests were sent.

Usage Examples

Manual TCP Client Example (Python)

import socket
import struct

def send_set(sock, key, value, ttl=0):
    key_bytes = key.encode()
    val_bytes = value.encode()
    payload = struct.pack('<BHI H', 
        0x02,           # SET command
        len(key_bytes),
        ttl,
        len(val_bytes)
    ) + key_bytes + val_bytes
    
    msg = struct.pack('<I', len(payload)) + payload
    sock.sendall(msg)
    
    # Read response
    resp_len = struct.unpack('<I', sock.recv(4))[0]
    resp = sock.recv(resp_len)
    return resp[0] == 0x00  # Success status

# Connect and test
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 31337))

send_set(sock, 'hello', 'world')
print("SET successful!")
sock.close()

Limitations

  • Expiry on recovery: Keys with TTL are restored without expiration after crash recovery (simplified implementation)
  • Memory usage: All values stored in memory; no eviction to disk
  • Network protocol: Custom binary protocol (not Redis-compatible)
  • Security: No authentication or TLS support
  • Clustering: Single-node only; no replication or sharding

Future Improvements

  • Lock-free hash map for the store (sharded or sync.Map)
  • Zero-copy TCP parsing
  • WAL record batching for higher throughput
  • Full TTL recovery on crash restart
  • Redis protocol compatibility layer
  • Configurable worker count
  • Prometheus metrics endpoint
  • Graceful shutdown handling

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

License

MIT License

Author

Atharva Arbat


Built as a systems programming project to explore concurrency patterns, custom protocols, and persistence in Go.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages