DEV Community

Cover image for How We Built the Fastest In-Memory Key-Value Store in Pure Go (Hitting 6.87M ops/sec Without CGO)
Anshu Garg
Anshu Garg

Posted on Originally published at garganshu9468.github.io

How We Built the Fastest In-Memory Key-Value Store in Pure Go (Hitting 6.87M ops/sec Without CGO)

For over a decade, the consensus in systems engineering has been unanimous: if you want raw, bare-metal networking throughput, you write it in C, C++, or Rust.

Managed runtimes with garbage collection—especially Go—were considered "great for microservices and cloud infrastructure," but fundamentally handicapped for extreme low-latency, multi-million-ops-per-second storage engines.

Standard Redis processes commands through a single-threaded event loop. While simple and lock-free, single-threaded architectures leave 95% of modern multi-core server CPUs completely idle.

When we set out to build VortexKV, our goal was ambitious:

Can we build a drop-in Redis replacement in 100% pure Go (zero CGO, zero external C dependencies) that not only matches Redis, but shatters its concurrent throughput—hitting over 6.8 Million ops/sec while keeping p50 latency under 120 microseconds?

Here is the exact architecture, the bottlenecks we hit, and the engineering breakthroughs that made it possible.


The Benchmark Numbers

Before diving into code, here are the audited benchmark numbers running on modern hardware (verified via standard redis-benchmark):

Workload Configuration Operations / Sec p50 Latency Bottleneck
Direct Concurrency (Non-pipelined, C=50) 206,611 ops/sec 119 µs Network RTT
Medium Pipeline (P=16, C=50) 1,984,127 ops/sec 271 µs Socket buffer
Peak Pipelined GET (P=64, C=50) 2,631,579 ops/sec 1.07 ms CPU memory bus
Peak Pipelined PING (P=64, C=100) 9,259,259 ops/sec 175 µs Hardware theoretical max

Anyone can verify these numbers on their own machine in 60 seconds:

git clone https://github.com/GargAnshu9468/vortexkv.git
cd vortexkv
./scripts/reproduce_benchmarks.sh
Enter fullscreen mode Exit fullscreen mode

Why Standard Go (net.Listen) Fails at 1M+ ops/sec

The idiomatic way to write network servers in Go is simple:

ln, _ := net.Listen("tcp", ":7379")
for {
    conn, _ := ln.Accept()
    go handleConnection(conn) // Goroutine-per-connection
}
Enter fullscreen mode Exit fullscreen mode

This model is elegant for web servers. But at 500,000+ commands per second, it hits a performance cliff:

  1. Goroutine Stack Overhead: Even a 2KB stack per goroutine causes cache line pollution across L1/L2 CPU caches when thousands of connections churn.
  2. Go Runtime Scheduler Preemption: Cooperative scheduling introduces micro-jitter and context-switching overhead.
  3. Write Syscall Amplification: Writing each small Redis response (e.g. +OK\r\n or +PONG\r\n) incurs an independent kernel write syscall. Syscalls are expensive.

To hit 6.8M+ ops/sec, we had to rethink the networking engine from the metal up.


1. Pinned Multi-Reactor with runtime.LockOSThread()

Rather than spawning unbounded goroutines, VortexKV implements a hardware-accelerated Multi-Reactor pattern (using Linux epoll and macOS/BSD kqueue).

A central acceptor reactor handles incoming client connections and distributes them across a fixed pool of worker reactors (one worker per available CPU core):

func (w *ReactorWorker) Loop() {
    // Pin this goroutine to a dedicated OS thread
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()

    events := make([]KEvent, 1024)
    for !w.stopped {
        n, err := w.poll(events)
        for i := 0; i < n; i++ {
            w.handleEvent(events[i])
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Why runtime.LockOSThread() matters:

By binding the event reactor worker permanently to an OS thread, the OS scheduler never migrates the loop between CPU cores.

  • L1/L2 Instruction & Data Cache Preservation: CPU caches stay hot.
  • Zero Cache Line Bouncing: Kernel socket notifications land on the exact core that owns the connection.

2. Zero-Allocation Cyclic Ring Buffers

Under extreme network load, Go's Garbage Collector (GC) is your biggest enemy. If every socket read allocates a new make([]byte, 4096), GC pauses quickly degrade p99 latency into milliseconds.

VortexKV equips every active client connection with a dedicated cyclic circular ring buffer:

type ConnectionRing struct {
    buf    []byte
    head   int
    tail   int
    mask   int
}

func (r *ConnectionRing) ReadFromSocket(fd int) (int, error) {
    // Read directly into preallocated ring buffer without heap allocs
    // Wrap-around handled via bitwise mask: (pos & mask)
}
Enter fullscreen mode Exit fullscreen mode

Because socket payloads are processed, decoded, and executed in-place within the ring buffer, the steady-state read pipeline produces zero heap allocations.


3. Batch Socket Write Coalescing

In Redis pipelines, a client sends 64 or 128 commands back-to-back in a single TCP packet.

If a server responds by issuing 64 individual write() syscalls back to the socket, the Linux kernel spends more time switching between user space and kernel space than actually moving data.

VortexKV implements smart socket write coalescing:

func (c *Client) QueueResponse(resp []byte) {
    c.writeBuf.Append(resp)

    // If the socket receive queue still has pending commands,
    // coalesce responses in memory instead of flushing immediately
    if c.hasPendingReads() && c.writeBuf.Len() < MaxBatchSize {
        return
    }

    // Flush all queued responses in a single vectorized kernel write
    c.flush()
}
Enter fullscreen mode Exit fullscreen mode

When processing pipelined workloads, up to 128 responses are consolidated into a single kernel writev / send syscall. This single optimization boosted pipelined throughput from 1.8M ops/sec to over 6.87M ops/sec!


4. 64 Mutex-Striped Shards with Cacheline Padding

Standard Redis is single-threaded to avoid lock contention. But to utilize all 16 or 32 cores on modern hardware, you need concurrency.

If you protect your keyspace with a global sync.RWMutex, CPU cores fight over the same memory cache line, causing devastating lock convoying.

VortexKV splits the global keyspace into 64 independent, lock-striped shards:

type KeyspaceShard struct {
    mu    sync.RWMutex
    data  map[string]*vortexObject
    // Cacheline padding: prevents CPU False Sharing
    _pad  [64]byte
}

type Engine struct {
    shards [64]*KeyspaceShard
}
Enter fullscreen mode Exit fullscreen mode

The Secret: Cacheline Padding (_pad [64]byte)

Modern x86 and ARM CPUs synchronize memory in 64-byte chunks (cache lines). If two mutexes reside in the same 64-byte cache line, Core 0 updating Shard 0 invalidates the cache line for Core 1 updating Shard 1—even though they are locking completely different data!

By padding each shard with [64]byte, every mutex occupies its own dedicated cache line. Contention drops to near-zero.


More Than Just a Cache: AI Vectors & Streams

Because we built the storage engine in pure Go, we could embed modern capabilities that traditional Redis lacks:

🧠 Native HNSW AI Vector Search

Skip external vector databases. Store high-dimensional embeddings and execute nearest-neighbor queries directly in VortexKV:

# Store vector embeddings
redis-cli -p 7379 VADD embeddings doc1 0.95 0.05 0.0 0.0

# Top-1 Cosine Similarity search
redis-cli -p 7379 VSEARCH embeddings 1 cosine 0.90 0.10 0.0 0.0
# Returns: "doc1", "0.999512"
Enter fullscreen mode Exit fullscreen mode

🌊 Event Streams with Consumer Groups & PEL

Full support for distributed event streaming with XADD, XREADGROUP, XACK, and Pending Entries Lists.

🌌 Embedded Cyberpunk Web Studio (:7380)

The binary embeds a visual web command deck with 2D/3D keyspace visualization, live latency monitors, slowlog stream, and ACL configuration.


Running VortexKV in 30 Seconds

VortexKV is completely open-source under the MIT license.

Via One-Line Installer (macOS / Linux):

curl -fsSL https://raw.githubusercontent.com/GargAnshu9468/vortexkv/main/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

Via Docker:

docker run -d -p 7379:7379 -p 7380:7380 ianshugarg/vortexkv:latest
Enter fullscreen mode Exit fullscreen mode

Connect with your favorite Redis client:

redis-cli -p 7379 PING
# PONG
Enter fullscreen mode Exit fullscreen mode

Conclusion & Lessons Learned

Building high-throughput network engines in Go isn't about avoiding the language—it's about understanding the runtime:

  1. Pin workers with LockOSThread() to avoid CPU core thrashing.
  2. Use preallocated ring buffers to starve the garbage collector.
  3. Batch kernel write syscalls when queues drain.
  4. Pad concurrent structs with 64 bytes to stop cache line bouncing.

If you love systems engineering, performance optimization, and pure Go, check out the code and consider leaving a star!

GitHub Repository: https://github.com/GargAnshu9468/vortexkv

🌐 Live Interactive Web Demo: https://garganshu9468.github.io/vortexkv/

📖 Official Wiki & Docs: https://github.com/GargAnshu9468/vortexkv/wiki

Top comments (0)