VeltrixDB / Resources / VeltrixDB vs Redis
Comparison guide · Honest & data-driven

VeltrixDB vs Redis — when RAM stops scaling.

Redis is excellent software. But when the keyspace outgrows RAM, the memory bill compounds and fork-based persistence shows up in the tail. Here is where each one wins — with every VeltrixDB number labelled as measured, internal or calculated.

SS
Shubham Sharma
Founder · VeltrixDB
Published · May 2026 Read time · 12 minutes Updated · Oct 2026 · v1.12
01
The core difference

Redis keeps everything in RAM. VeltrixDB doesn't have to.

Redis's speed comes from one thing: all data lives in memory. That's a real advantage — and a real cost ceiling. VeltrixDB keeps only the index and a LIRS cache in DRAM and stores values on NVMe, so hot keys are answered from memory and cold data is priced like disk, not like RAM.

This is not a "Redis killer" comparison. Redis is the right choice for millions of workloads. But there is a specific class of problem — large keyspaces under sustained write pressure — where keeping every byte in RAM, and forking to persist it, gets expensive and noisy.

When Redis is under heavy write load, the fork behind an AOF rewrite or RDB save can make P99 spike. Engineers work around this with replica reads, fork tuning and alert rules. VeltrixDB does not fork to persist: every write goes to a group-commit WAL and is fdatasync'd before it is acknowledged.

VeltrixDB cache hit
~92 ns
in-process lookup · sharded LIRS · no network
Write amplification
~1×
design, not measured · rises with GC relocation
Packed record
~152 B
128 B value, batched writes · calculated
02
Latency

The P50 is similar. The P99 shape is not.

Both databases are fast. The difference is in the tail — and in what each design has to do in the background to stay durable. Every VeltrixDB cell below says how it was measured.

Metric Redis (AOF on) VeltrixDB Source & conditions
Read over network · avg / P99 ~100–300 µs / 1–3 ms 461 µs / 2,771 µs VeltrixDB: measured, YCSB 0.17, 200 threads, 100M keys, all cache hits (pure-Go build). Redis: production reports
Read P99 under sustained writes 1–50 ms (spikes) ~510 µs VeltrixDB: internal, unverified 3-node GKE run, not reproduced with a published harness. Redis: AOF rewrite / fork pauses, production reports
Durable single write · P99 varies with AOF 47.3 ms VeltrixDB: measured, YCSB load, WAL + fdatasync before every ACK, 5 ms fixed flush windows (before adaptive group commit, v1.12), 18,064 ops/s
Batch writes (1024-key MPUT) pipelined, single-threaded 3.54M keys/s · P99 4.2 ms VeltrixDB: measured, 8 clients, 1M-key space, macOS, v1.7. 1.77M keys/s on a 4-CPU CI runner (tmpfs, same-host client)
In-process cache lookup — ~92 ns VeltrixDB: the sharded LIRS lookup alone, no network. Not comparable with a network round trip

Redis numbers above reflect production reports from teams running AOF with appendfsync everysec under mixed read/write load; they are not from a side-by-side run on the same hardware. Your numbers depend on instance size, fork latency, and background save frequency.

!

The GC death spiral — and why VeltrixDB can't get stuck in it. VeltrixDB's own value-log GC backs off when read latency rises, to protect reads. Done naively, that is a spiral: GC pauses, garbage accumulates, reads stay slow, GC stays paused. The GC escalates in three tiers, and at 65% garbage it bypasses the read-pressure pause unconditionally, so it cannot stay paused forever (a stale pause also clears after 4 minutes without reads). Redis has no value-log GC — its tail risk is the fork. veltrixdb_vlog_gc_emergency_runs_total counts every time that happens.

03
Storage cost

VeltrixDB's RAM scales with keys, not value bytes.

VeltrixDB moves values to NVMe, but the whole index stays in RAM. So the RAM saving depends on value size: with small values it is small; with 1 KB values it is roughly 4–7×. The table below is per-key arithmetic, not a measurement.

Keys · valueRedis RAM (estimate)VeltrixDB RAM (index)VeltrixDB NVMe (packed values)
100M · 128 B~25 GB~14–23 GB~15 GB
1B · 128 B~250 GB~142–232 GB~160 GB
100M · 1 KB> 100 GB~14–23 GB~137 GB
1B · 1 KB> 1 TB~142–232 GB~1.4 TB

The VeltrixDB RAM column is the index: ~142 B per key with the native index (measured at 5M keys), plus ~90 B per key for the ordered index that serves RANGE / SCANCUR unless you start with --disable-ordered-index — plus whatever LIRS cache you configure. The Redis column is an estimate of values plus per-key overhead; we have not measured it. Nothing at 1 billion keys has been measured reproducibly.

With 128-byte values the index is about as large as the values, so VeltrixDB saves little RAM there; its advantage at that size is NVMe durability without fork-based persistence. The disk side relies on block packing: batched writes (MultiPut, and GC relocation) put up to ~26 records into one 4 KB VLog block, ~152 B per 128-byte value. A single Put stays on the unpacked path and takes a whole 4 KB block. Price RAM and NVMe for your own instance types rather than relying on a fixed ratio.

If your dataset fits comfortably in RAM and you're not cost-constrained — Redis is the simpler choice. If your keyspace is growing, your values are large, and the RAM bill is growing with them, that's the signal to benchmark VeltrixDB against your workload.

04
Write amplification & SSD wear

Every value should land on NVMe once — until GC moves it.

Write amplification is how many times a byte gets written to storage for every one byte of user data. At 1× you're paying for your data once. At 30× you're paying thirty times — in SSD wear, in NVMe IOPS, and in latency tail.

Redis (AOF rewrite) 2–5× — AOF rewrite rewrites the full dataset to a new file
RocksDB / LevelDB 10–30× — LSM compaction sorts and rewrites every key multiple times across levels
VeltrixDB ~1× by design for batched writes — values are appended once to the Value Log and never sorted; the WAL carries only a header and the key. GC copies still-live values forward when it reclaims a region, and a single small Put pads to a whole 4 KB block, so the real figure is higher. Not measured.

VeltrixDB uses WiscKey key-value separation: the per-shard in-memory index (8192 shards) holds a 64-byte entry per key — offset, disk index, on-disk and plaintext sizes, CRC32C, write time, TTL, flags. Values go to an append-only Value Log on NVMe. There is no LSM compaction; the value-log GC relocates live values out of mostly-dead regions and skips keys written in the last 30 s. With a low overwrite rate, write amplification stays close to 1×; heavy overwrites make GC do more copying.

✓

What this means for SSDs (arithmetic, not a benchmark). At 100K batched writes/sec of 128 B values, 1× write amp writes 12.8 MB/s to NVMe (unbatched single Puts would pad each to 4 KB). At 10× (a typical LSM figure) that's 128 MB/s — proportionally more SSD wear and more IOPS to provision.

05
When Redis wins

Redis is the right choice in these situations.

This is not a takedown of Redis. It is the right tool for a large number of workloads. Knowing when not to migrate is as important as knowing when to.

  • Your full dataset fits comfortably in RAM — if you have 5M keys at 256 bytes, Redis's DRAM-only path has zero NVMe latency on any read. The advantage of VeltrixDB's cache tier disappears.
  • You need pub/sub, streams, sorted sets, or Lua scripting — Redis has a rich data structure model built over decades. VeltrixDB is a key-value store. VeltrixDB has hashes (with per-field TTL), lists, sets, counters, CAS, TTLs, ordered key range scans, secondary indexes and one-shot transactions — but none of those four. If your application relies on ZRANGEBYSCORE, XREAD, PUBLISH or Lua, stay on Redis.
  • Your team already runs Redis and has no latency complaints — operational familiarity is real value. Don't fix what isn't broken.
  • You need a fully-managed hosted service today — Redis Cloud, ElastiCache, Upstash, and Momento all exist and are production-hardened. VeltrixDB is self-hosted only today; a managed service is planned, not available.
  • Your workload is write-once, read-many and fits in RAM — with no overwrites there is little background rewriting on either side, so VeltrixDB's tail-latency design has little to fix.
06
When VeltrixDB wins

The specific problems VeltrixDB was built to solve.

VeltrixDB earns its place when your dataset is outgrowing your RAM budget, when your production dashboards show Redis P99 spikes you can't explain, or when you're running on Kubernetes with local NVMe.

  • Your dataset doesn't fit in RAM — or fitting it costs too much — VeltrixDB's LIRS cache keeps hot keys in DRAM (default -cache 256 MB; size it to your hot set) and a miss costs one pread of a 4 KB VLog block. Your hit rate depends on your access skew — measure it with veltrixdb_cache_hit_rate.
  • Unpredictable P99 under sustained write pressure — if your Redis P99 spikes during AOF rewrite or background save: VeltrixDB never forks, and its admission-controlled three-tier GC is designed to keep background work out of the read tail. That is a design goal; benchmark it on your workload rather than taking a ceiling on faith.
  • High-concurrency bulk writes — MultiPut with 1024-key batches measured 3.54M keys/s (8 clients, 1M-key space, macOS, v1.7) and 1.77M keys/s on a 4-CPU CI runner (tmpfs, same-host client). VLog appends reserve offsets with an atomic add, and a batch is one pwrite plus one fdatasync.
  • Kubernetes with local NVMe SSDs — a Kubernetes Operator and a Helm chart (with an nvme-prep DaemonSet that formats local SSDs as XFS) live in separate repositories; the main repository ships the kubectl-veltrix plugin and Prometheus rules.
  • Write amplification and SSD cost matter — values are written once and never re-sorted, so there is no LSM-style 10–30× rewrite; GC relocation and 4 KB padding of single Puts are the extra bytes.
  • You need predictable latency at scale — not just at P50 — an internal 3-node GKE run (unverified, not reproduced with a published harness) recorded zero GC emergency runs over 30 minutes of sustained 80R/20W load. The reproducible numbers are on the performance page.
07
Feature matrix

Everything side by side, nothing hidden.

An honest matrix. We've marked items we're still building as roadmap rather than pretending they exist.

Feature Redis VeltrixDB
In-memory lookup (< 1 µs, in-process)✓✓ ~92 ns cache hit
No fork for persistence⚠ AOF rewrite / RDB fork✓ group-commit WAL
NVMe storage tier—✓
Values never re-sorted (WiscKey)—✓
Value-log GC that cannot be permanently pausedn/a · no value-log GC✓ three-tier
Hashes / lists / sets✓✓ HSET (per-field TTL), LPUSH, SADD …
Pub/Sub✓—
Sorted sets / ZRANGEBYSCORE✓—
Ordered range scans✓ sorted sets / SCAN✓ RANGE / SCANCUR (local node in clusters)
Streams (XREAD/XADD)✓—
Lua scripting✓—
Atomic CAS / INCR / DECR / SETNX✓✓
Multi-key transactions✓ MULTI / EXEC, WATCH✓ one-shot TXN, version check, read-committed
Secondary indexes⚠ Redis Query Engine / RediSearch✓ IDXCREATE / IDXQUERY (equality)
Vector · full-text · hybrid search⚠ Redis Query Engine / RediSearch✓ built in — see §08
TTL / key expiry✓✓ PUTEX / binary MPUT per-entry TTL
Encryption at rest (AES-256-GCM)✓✓
TLS in transit✓✓
Multi-node replication✓✓ Raft, or replicated (eventual default / quorum / strong)
Kubernetes Operator⚠ third-party✓ separate repository
Prometheus metrics✓✓
Change data capture (CDC)—✓ built-in
Go / Python / Node / Rust / C++ SDKs✓✓ Go in the main repo; others in Veltrixdb-client
RESP protocol compatibility✓roadmap · not implemented
Managed cloud service✓planned · self-hosted today
09
Migration from Redis

A client change, not a connection-string swap.

VeltrixDB does not speak RESP, so redis-cli and Redis client libraries cannot connect to it. Every call site moves to a VeltrixDB client or to its own text / binary protocol. Many Redis commands have a close equivalent; some have none.

Try it in 30 seconds

shell — Docker quickstart
# Linux / Windows (the image is linux/amd64)
docker run -p 9000:9000 ghcr.io/veltrixdb/veltrixdb:latest

# Mac (Apple Silicon) — runs under emulation
docker run --platform linux/amd64 -p 9000:9000 ghcr.io/veltrixdb/veltrixdb:latest

# Text protocol over nc — SET is accepted as an alias of PUT
echo -e "SET hello world\nGET hello\nPING" | nc localhost 9000

Command mapping

SET / GET / DELPUT (alias SET) / GET / DEL — text-protocol PUT takes the rest of the line as the value
SET … EX / SETEXPUTEX key ttl-seconds value, or the per-entry TTL of a binary MPUT (the binary PUT frame has no TTL field). There is no standalone EXPIRE / TTL on plain keys
SETNX / INCR / DECRSETNX, INCR key [delta], DECR key [delta]; compare-and-swap is CAS key expected new
MGET / pipelinesMGET; binary MPUT / MGET batches (one WAL write, one VLog pwrite, one fdatasync per disk)
HashesHSET key field value [ttl], HGET, HDEL, HGETALL, HKEYS, HLEN, HEXPIRE, HTTL
Lists / setsLPUSH RPUSH LPOP RPOP LRANGE LLEN; SADD SREM SMEMBERS SISMEMBER SCARD
SCAN / KEYSSCANCUR cursor limit, RANGE start end [LIMIT n] [REV] — ordered, local node only in a cluster
MULTI / EXEC / WATCHTXN n + n lines of SET / SETIF key version value / DEL: one-shot, optimistic, read-committed, not MVCC; a version conflict returns CONFLICT — retry
ZADD, PUBLISH, XADD, EVALNo equivalent. Keep those workloads on Redis

Copy the data

There is no Redis importer. veltrix-migrate import (in the main repo) reads JSONL from stdin, one {"k": key, "v": base64 value} per line, and PUTs each entry. For string keys, a short redis-py script produces that format. It does not carry TTLs, and other Redis types need their own mapping (e.g. hash fields to HSET):

shell + python — strings only
cat > dump_strings.py <<'EOF'
import base64, json, sys, redis
r = redis.Redis(host='localhost', port=6379)
for k in r.scan_iter(count=1000):
    if r.type(k) != b'string':
        continue
    v = r.get(k)
    if v is not None:
        sys.stdout.write(json.dumps({"k": k.decode(), "v": base64.b64encode(v).decode()}) + "\n")
EOF

go build -o veltrix-migrate ./cmd/veltrix-migrate      # in the VeltrixDB repo
python3 dump_strings.py | ./veltrix-migrate import --dst localhost:9000

veltrix-migrate sends one PUT at a time, so each value takes a whole 4 KB VLog block; for many small values, load with batched MPUTs instead (the Go client’s BinaryConn.MPut) to get block packing. In --mode=raft, point --dst at the leader.

SDK migration

Python — before and after (SDK in the separate Veltrixdb-client repo; not verified here)
# Before (redis-py)
import redis
r = redis.Redis(host='localhost', port=6379)
r.set('key', 'value')
val = r.get('key')

# After (veltrixdb-python)
import veltrixdb
r = veltrixdb.Client(host='localhost', port=9000)
r.put('key', 'value')
val = r.get('key')
Node.js — before and after (from the SDK's example.js; not verified here)
// Before (ioredis)
const Redis = require('ioredis')
const r = new Redis({ host: 'localhost', port: 6379 })
await r.set('key', 'value')

// After (veltrixdb-client)
const { VeltrixDBClient } = require('veltrixdb-client')
const c = new VeltrixDBClient({ host: 'localhost', port: 9000 })
await c.connect()
await c.put('key', Buffer.from('value'))

The search API (vectors, text, hybrid) is only in the Go client in the main repo; from other languages use the text-protocol search commands over a TCP socket. RESP compatibility is on the roadmap, with no date.

✓

Recommended migration strategy. Copy the data, then run VeltrixDB in shadow mode — dual-write to both Redis and VeltrixDB for two weeks, compare read results, validate latency against your actual key distribution. Cut over when the shadow reads match. It gives you real-world latency data before you commit; the migration playbook walks through the stages.

Measured: YCSB 0.17 · 100M keys · pure-Go build · June 2026 · batch writes on v1.7 (macOS, 4-CPU CI). Internal, unverified: 3 × GKE n2-highmem-64, 30 min 80R/20W, May 2026

Continue reading

See if VeltrixDB fits your workload.

Bring your current Redis key count, value size, and read/write ratio. We'll work through a cost estimate with you and help you benchmark your own data shape.