Can Go Compete with C for In-Memory Databases? Benchmarking Tellstone, Redis, and Valkey
Introduction
There are several threads on reddit challenging the question: Go's performance (with/without GC) good enough to be a competitor to C or Rust. Especially when it comes to maximum performance, predictable latencies, and raw throughput.
As I'm working with Go since 2013 I know all the statements and all the concerns about this. BUT I asked myself if there is a possibility to proof them all.
To test the boundaries of the language, I built Tellstone (TSD): a stripped‑down, RESP‑compatible in‑memory database written completely in Go. I now have a solid foundation, and I wanted to checkout if TSD has a chance to grow in an environment where performance matters. To check that out, I benchmarked it against redis and valkey on my own system.
The results were fascinating, and soon I'll check out if it really can beat them also in the real world!
TL;DR
- Goal: Compare a zero‑allocation Go engine (Tellstone) against Redis and Valkey.
- Result: In this benchmark configuration, Tellstone reached 2.40M ops/sec, compared to approximately 2.00M ops/sec for Redis and Valkey.
- Key Techniques: Multi‑reactor networking via gnet, lock‑free sharded storage, zero‑allocation RESP parser, and GC disabled on the hot path.
Table of Contents
- The Contenders & Tellstone’s Architecture
- The Benchmark: Pushing Sockets to the Limit
- Results: Throughput Explosion
- Latency Analysis: The Back‑pressure Effect
- Why Go Wins Over C on Modern CPUs
- Next Milestones: Pushing Go to Its Absolute Limit
The Contenders & Tellstone’s Architecture
TBH redis and valkey are optimized and battle proofed systems that were created over decades! A standard TCP Go implementation won't work out to get the same performance as these projects do.
Go systems usually degrade under extreme workloads due to two main pain points: CPU overhead from system calls handling millions of concurrent network sockets, and latency spikes caused by Stop‑The‑World (STW) garbage collection sweeps.
I tried to design Tellstone with the zero-allocation philosophy which should theoretically reduce these limitations:
| Feature | Tellstone Implementation |
|---|---|
| Multi‑Reactor Networking via gnet | Event‑driven networking framework gnet binds native epoll instances directly to dedicated CPU cores (instead of a goroutine per connection). |
| Lock‑Free Hot Path & Sharded Storage | 256 independent shards; power‑of‑two masking (hash & (shardCount‑1)) directs reads/writes to separate locks, dropping mutex contention to near‑zero. |
| Zero‑Allocation RESP Parser | RESP parsing operates directly on gnet's receive buffers using slice views, avoiding heap allocations for request parsing. Serialization buffers are recycled via sync.Pool. |
| Deactivating GC on the Hot Path | The request path is designed to avoid heap allocations. During benchmarking, automatic GC is disabled via debug.SetGCPercent(-1), removing GC work from the measured hot path and helping keep latency stable. |
The Benchmark: Pushing Sockets to the Limit
I wanted to guarantee reproducibility for the benchmarking.
I also isolated the server(s) and the benchmark loader
using OS‑level core pinning (taskset on WSL2/Ubuntu):
-
CPU Isolation
- Server (Tellstone, Redis, Valkey) → Cores 0‑15
- Workload generator (
memtier_benchmark) → Cores 16‑31
-
Workload Configuration
- 8 client threads, 100 connections each → 800 parallel sockets
- Pipeline size 8 (batching to amortize syscalls)
- Request mix 90 % GET / 10 % SET (read‑heavy cache)
- Keyspace 200 k keys, Zipf distribution (realistic hot‑key pattern)
- Persistence disabled on Redis/Valkey (
--save ""and--appendonly no) to focus on pure in‑memory performance.
Hardware Stack:
-
CPU: AMD Ryzen 9 9950X (16 Cores / 32 Threads)
-
RAM: 30 GiB (Allocated to WSL2 Instance)
-
NUMA Architecture: 1 NUMA Node (UMA)
-
SMT (Hyperthreading): Enabled (2 threads per core)
-
BIOS/UEFI Profile: Default Profile / Controlled by Host
OS & Kernel Tuning:
-
Environment: WSL2 on Windows 11 (Host: "Beast")
-
OS Distribution: Ubuntu LTS (inside WSL2)
-
Kernel Version: 6.6.87.2-microsoft-standard-WSL2
-
CPU Governor: Not applicable (Fully managed by Windows Host / WSL2 Architecture)
-
Transparent Huge Pages (THP): madvise
Software Stack:
-
Go Version: go1.26.2 linux/amd64
-
Go Compiler Flags: Default build flags
-
gnet Version: v2.9.8
-
Redis Version: 7.2.12
-
Valkey Version: 8.8.0
Persistence:
Redis:
- save ""
- appendonly no
Valkey:
- save ""
- appendonly no
Tellstone:
- In-memory only
Results: Benchmark Results
These results represent a single workload on a specific hardware and software configuration. They should not be interpreted as universal rankings of database performance. Different datasets, value sizes, workloads, persistence settings, networking stacks, or deployment environments may produce different results.
| Metric | Tellstone (TSD) | Redis (v7.x) | Valkey (v8.x) |
|---|---|---|---|
| Throughput (Ops/sec) | 2,404,495 | 1,996,341 | 2,018,534 |
| Relative Throughput | 120.4 % | 100 % (baseline) | 101.1 % |
| p50 Latency | 3.103 ms | 3.167 ms | 3.135 ms |
| p99 Latency | 5.183 ms | 5.279 ms | 5.151 ms |
| p99.9 Latency | 7.359 ms | 7.231 ms | 7.007 ms |
Throughput (RPS) — Higher is Better
====================================
Tellstone: ██████████████████████████████ (2.40M RPS)
Valkey: █████████████████████████ (2.01M RPS)
Redis: ████████████████████████ (1.99M RPS)
All measurements were performed inside WSL2 rather than on bare-metal Linux. All systems were evaluated under the same environment.
Latency Analysis: The Back‑pressure Effect
Percentiles show Tellstone remains stable under pressure. At p50 it clocks 3.103 ms, beating both Redis and Valkey. Even at p99 and p99.9, Tellstone stays neck‑and‑neck with the hand‑optimized C engines.
“Average Latency” Phenomenon
Memtier reports a high average latency ≈ 163 ms for Tellstone, versus ~3.2 ms for Redis/Valkey. This is network saturation back‑pressure: Tellstone pushes 2.4 M RPS, 20 % more load than Redis, saturating the kernel’s TCP stack. A small fraction of connections incur queuing delay inside the OS socket buffers before the reactor can process them. The engine‑level percentiles stay low, proving the processing itself is in the microsecond range; the bulk of the “average” latency is likely caused by OS queue time.
Why Tellstone performs well
| Reason | Explanation |
|---|---|
| Multi-reactor networking | gnet distributes sockets across multiple epoll loops, spreading load across all pinned cores. |
| Sharded storage | Independent shards reduce lock contention and improve parallelism. |
| Cache locality | Zero-allocation request processing keeps hot data structures compact and cache-friendly. |
| Minimal GC work | The hot path avoids heap allocations, and automatic GC is disabled during benchmarking. |
Next Milestones: Pushing Go to Its Absolute Limit
Achieving > 2.4 M RPS is a fantastic milestone, but the journey continues. I’m exploring:
- Transition to
io_uring– Evaluate experimental Go networking libraries that leverage kernel‑bypass socket batching. - Strict Shared‑Nothing Model – Each reactor exclusively owns a set of shards, eliminating the final lock barrier.
- Asynchronous Non‑Blocking Snapshots – Lock‑free snapshot engine using copy‑on‑write to stream state to disk without pausing the event loops for even a single microsecond.
These results suggest that, with careful engineering, Go can achieve throughput comparable to—and in this benchmark exceeding—well-established C implementations for this class of workload.
Repository: https://github.com/Saxy/Tellstone (run the benchmark suite, inspect the code).
Every benchmark shown in this article can be reproduced via the included Taskfile.
task bench:compare \
TARGET=tellstone \
THREADS=8 \
CONNS=100 \
PIPELINE=8
task bench:compare \
TARGET=redis \
THREADS=8 \
CONNS=100 \
PIPELINE=8
task bench:compare \
TARGET=valkey \
THREADS=8 \
CONNS=100 \
PIPELINE=8