Ask a delivery team what limits its key-value tier and the answer is usually a bill, not a benchmark. When growth is capacity-bound rather than compute-bound, every step up in retained data triggers another shard, another replica and another pair of machines to patch — while the application gains little extra serving capacity from it. The published Lavik numbers describe a different lever: application values live on NVMe SSD instead of DRAM, the interface stays Redis-compatible, and the engine is measured at 1.14 million GETs per second, faster than Redis on DRAM in that comparison. What follows is the topology, the cost model, the migration steps, the misconceptions and the FAQ — the parts an engineering team needs to turn an architectural claim into a release plan.
All of it rests on a single capacity comparison: 300 E2s_v4 Redis VMs against two L16as_v4 data VMs plus one E2s_v4 Meta-only voter, where a Meta member runs on each VM and only the primary and follower retain the dataset.
A human-facing service typically keeps a profile, a session and a feature set per user. An agent product multiplies that shape: a research assistant, a coding assistant and several background workers each hold execution state, observations and working context, and a worker that runs only occasionally still has to be readable the moment it resumes. The drivers of capacity are therefore agent count, state size and how long data remains useful — not always traffic.
Run the arithmetic at one billion retained agents: 1 KB of features is roughly 1 TB of values; 10 KB reaches 10 TB; 100 KB reaches 100 TB. Holding two copies pushes those totals to 2 TB, 20 TB or 200 TB before keys, indexes or replication are counted. Applications still expect low-latency reads and updates through familiar Redis clients and data structures, and keeping every retained value in expensive DRAM is what makes that combination hard to afford. Lavik exists to make NVMe SSD capacity practical for latency-sensitive work.
Value payload for one billion retained agents · decimal units
| Bytes per agent | Unique value payload | Two copies, values only |
|---|---|---|
| 1 KB | 1 TB | 2 TB |
| 10 KB | 10 TB | 20 TB |
| 100 KB | 100 TB | 200 TB |
Take 300 Standard_E2s_v4 Azure VMs, each with two vCPUs, 16 GiB RAM and no local SSD, arranged as 150 primaries and 150 replicas. That purchase delivers 600 vCPUs and 4,800 GiB of RAM, of which 2,400 GiB sits on the primary side. When RAM fills first, each expansion buys additional CPU, networking and operating systems largely to obtain more memory — while what the application actually needs is more retained profiles or longer session lifetimes. That mismatch is the entry point for Redis cost optimization: choose storage density independently of the processors needed to serve traffic.
Both layouts are quoted in the same region and purchasing model — East US 2, Linux pay-as-you-go, in US dollars — using Microsoft's published rate of $0.126 per hour for Standard_E2s_v4 and $1.376 per hour for Standard_L16as_v4. Across 730 hours per month, the 300 E2s_v4 VMs serving Redis cost $27,594, while the Lavik side — two L16as_v4 data VMs and one smaller voter — totals $2,100.94 per month. Each data VM ships 16 vCPUs, 128 GiB RAM and 3.84 TB of local NVMe SSD; the voter needs no local SSD and no dataset copy.
Adding flash does not by itself solve latency. Redis describes Flex as giving flash access in milliseconds and advises against broad access patterns, large working sets and frequent movement between RAM and flash; for an application with a sub-millisecond latency budget, a millisecond flash path is too slow. Agent traffic that repeatedly revisits different profiles and contexts makes this distinction consequential, because the engine must stay efficient on a dataset far larger than memory.
Measured against the Redis layout, the monthly VM bill falls by $25,493.06 — 92.4% — for an annualized saving of $305,916.72. Lavik's three VMs expose 34 vCPUs and 272 GiB RAM across the deployment, with the two NVMe-equipped nodes handling data and the smaller VM completing the failover quorum; Redis's 300 VMs expose 600 vCPUs, much of which a memory-bound workload may not need. The prices below cover self-hosted VMs only — OS disks, backups and traffic are additional deployment costs.
Azure East US 2 · Linux pay-as-you-go · USD · 730 hours per month; unit-cost columns are normalized to 1M average QPS, that is 1 million QPS.
| Deployment | USD / VM-hour | Monthly USD | Annual USD | USD / million operations at 1M average QPS |
|---|---|---|---|---|
| Redis · 300 × Standard_E2s_v4 | $0.126 | $27,594.00 | $331,128.00 | $0.01050 |
| Lavik · 2 × L16as_v4 + 1 × E2s_v4 | $1.376 (L16as_v4); $0.126 (E2s_v4) | $2,100.94 | $25,211.28 | $0.00080 |
SPDK makes the storage path explicit. Lavik submits NVMe operations through userspace queue pairs and polls for completions, removing the kernel block-I/O submission path with its syscall and wakeup overhead. Asynchronous requests keep storage work in flight while workers make progress on other commands, and polling trades CPU time for prompt completion handling — how well that time is used depends on queue depth, completion batching and CPU placement. Modern NVMe devices supply the parallel queues and internal concurrency; the engine still has to deliver enough independent work to exploit them.
Lavik's Bycorf workers use coroutines, owner-local mutable state and explicit dispatch between workers, so other runnable work proceeds while one command waits on storage. Ownership keeps common operations close to the state they modify and reduces coordination between cores, which matters at a million operations per second, where small amounts of scheduling, synchronization or allocation overhead accumulate into substantial CPU demand. Designing execution and storage together lets the engine use SSD parallelism without turning every I/O into a stalled request-processing thread.
The optimizations run through the whole request path. Eligible disk GETs can use an admitted index snapshot on the connection worker, avoiding owner dispatch when the fast path applies; parsing handles batches of up to 128 commands; small pipeline replies coalesce into 64 KiB writes; per-worker append streams avoid a staging-buffer allocation for every logical slot. The Redis-compatible interface stays familiar to applications while the implementation underneath is built around efficient concurrent storage access. Lavik's request path, in short: coroutine workers, an in-memory key index and asynchronous SPDK I/O keep NVMe storage busy.
Large objects receive the same attention. Lavik 0.1.0 supports grouped storage for Strings, Hashes, Sets, Lists, Sorted Sets and Streams, with large strings using 8 KiB segments and collections using independently addressed groups, so a focused operation works on the relevant part instead of rewriting an entire object. For an agent profile with frequently changing features and much larger retained state, that layout is the difference between a small update and unnecessary work across the whole record.
In the published head-to-head, Lavik 0.1.0 SPDK is measured against Redis 8.10.2 and Valkey 9.1.2 on the same 16-vCPU Azure server. A separate load generator issues uniform random GETs and SETs over private networking, with 1 KiB values and pipeline depth one. Redis and Valkey serve their values from DRAM; Lavik uses its NVMe storage path. Throughput and client-observed latency are shown together so a team can pick an operating point against its own budget.
At 1,280 connections Lavik delivers 1,145,772 GET QPS and 971,950 SET QPS: GET throughput 12.5% higher than Redis and 18.1% higher than Valkey, SET throughput 2.2% higher than Redis, with GET P99 at 3.999 ms versus Redis's 4.415 ms. This is the architectural result that makes consolidation worth considering — NVMe-backed capacity arriving with higher throughput than an in-memory server, rather than forcing the application to exchange performance for cheaper storage.
Same 1,280-connection runs · QPS rounded to whole operations; all latency columns in milliseconds
| System / operation | QPS | Average | P50 | P95 | P99 | P99.9 |
|---|---|---|---|---|---|---|
| Lavik 0.1.0 SPDK GET | 1,145,772 | 1.117 | 1.031 | 1.511 | 3.999 ms | 7.135 |
| Lavik 0.1.0 SPDK SET | 971,950 | 1.317 | 1.119 | 2.783 | 4.479 | 7.199 |
| Redis 8.10.2 GET | 1,018,735 | 1.256 | 1.087 | 2.479 | 4.415 ms | 7.103 |
| Redis 8.10.2 SET | 951,053 | 1.346 | 1.135 | 2.943 | 4.511 | 7.231 |
| Valkey 9.1.2 GET | 970,288 | 1.319 | 1.119 | 2.511 | 4.415 ms | 7.455 |
| Valkey 9.1.2 SET | 808,103 | 1.583 | 1.455 | 2.559 | 4.047 | 7.199 |
Latency also depends on how hard a server is driven. At the billion-key operating point with 80 connections, Lavik delivers 385,514 GET QPS at 0.439 ms P99.9; at 640 connections it reaches more than a million GETs per second. An infrastructure team can read those curves as a menu of throughput and tail-latency combinations, and keeping headroom for bursts is more useful than sizing a production deployment solely from the highest point on a throughput chart.
The billion-key dataset contains 1.024 TB of raw values — about 7.6 times the server's RAM — and reaches 1,069,126 GET QPS, so performance extends well beyond a memory-sized dataset. The smaller-instance results tell the same capacity story: on two vCPUs and 16 GiB nominal RAM, growing the dataset from one million to 100 million 1 KiB values changes peak GET throughput only from 215,521 to 215,463 QPS, and SET retains 95.2% of its smaller-dataset peak. That matters for a compact service and a large agent platform alike: capacity can grow without a matching increase in serving nodes.
GET and SET across 80–1,280 connections. Ten-million-key tests use 30-second windows, 10M-key comparisons use 30-second windows, and billion-key tests use 60-second windows and distinct client seeds.
Agent state must also be written. In the separate io_uring YCSB experiment, Lavik processes 529,501 operations per second for a 50/50 read/update mix and 577,373 for a 95/5 mix. These are structured-record operations — HGETALL reads and LAVIK.HREPLACE updates against 100 million records with ten 128-byte fields — which show the design is relevant beyond a pure GET workload, including feature records whose contents change as an agent progresses through a task.
Separate io_uring YCSB experiment · 100M records, 10 × 128 B fields, 256 workers, no replicas
| Lavik workload | Total QPS | READ P99.9 ms | UPDATE P99.9 ms |
|---|---|---|---|
| 50% HGETALL / 50% HREPLACE | 529,501 | 5.083 | 2.831 |
| 95% HGETALL / 5% HREPLACE | 577,373 | 1.818 | 1.578 |
One million operations per second corresponds to 86.4 billion operations per day if sustained. Measured serving performance at that level, combined with NVMe storage density, is a strong case for reevaluating a memory-bound fleet: start from the actual traffic envelope — command mix, peak demand, bursts and latency target — allocate compute to that envelope, and let NVMe SSD provide the capacity for everything the application needs to remember.
Memory-driven growth also expands the topology engineers must run. Redis Cluster distributes 16,384 hash slots and uses a full-mesh cluster bus whose specification gives each node N−1 outgoing and N−1 incoming bus connections, so across the cluster there are N(N−1) initiated connections:
Those are cluster-bus connections, separate from application traffic. Adding memory through more nodes enlarges this coordination fabric as agent state expands. The cluster uses gossip, heartbeat probes and failure reports to maintain membership and health; local failure suspicion must be reconciled with reports from other nodes, and failover requires authorization and configuration epochs. During a network partition or rolling restart, engineers still have to work out which primaries, replicas and paths remain reachable — and more members mean more placements and relationships to diagnose even when request-processing capacity is sufficient. Resharding adds data movement, ownership changes and client redirects; maintenance adds patching, restarts, monitoring and replica placement.
A storage-dense Lavik deployment reduces the number of Data processes needed for capacity and gives each one room to grow, with the Meta service managing topology and authority while native replication maintains followers. For a capacity-bound application, shrinking the fleet addresses two expenses at once: the servers purchased for memory and the operational work that comes with managing them.
The logarithmic capacity model used before overhead counts one logical copy. Redis uses a 16 GiB primary and one replica per pair; each independent Lavik deployment adds two 3.84 TB data VMs and one smaller Meta-only voter, with all three VMs counted; the logarithmic chart repeats independent three-VM deployment units as capacity grows.
The source shard count does not have to dictate the target's physical layout, and the migration guide provides release-pinned configuration and steps for turning the consolidation design into an executable migration.
| Dimension | Redis fleet | Lavik deployment |
|---|---|---|
| Virtual machines | 300 E2s_v4 VMs (150 primaries, 150 replicas) | two L16as_v4 data VMs plus one E2s_v4 Meta-only voter |
| Serving compute | 600 vCPUs | 34 vCPUs |
| Memory | 4,800 GiB of RAM, 2,400 GiB on the primary side | 272 GiB RAM |
| Capacity basis | 2,400 GiB RAM (≈ 2.577 TB) | 3.84 TB NVMe SSD per data node |
| Monthly / annual VM cost | $27,594.00 / $331,128.00 | $2,100.94 / $25,211.28 |
| Cost per TB-month | $10,707.88 | $547.12 |
| Measured GET throughput | Redis 8.10.2: 1,018,735 | Lavik 0.1.0: 1,145,772 GET QPS |
| Quorum / coordination | 16,384 hash slots, full-mesh cluster bus | three Meta members using Raft |
| Licensing note | — | Apache 2.0 open-source license |
No. The deployment is served through a Redis-compatible path, so applications keep their familiar Redis clients and data structures while values move from DRAM to NVMe SSD.
Capacity-bound ones: retained state keeps climbing while CPU utilization and traffic stay below the fleet's serving capacity. The growth drivers to measure are agent count, state size and retention horizon rather than request volume alone.
Flash alone is not the answer — the published guidance on Redis Flex warns against broad access patterns and RAM/flash movement for exactly that budget. Lavik's path combines DRAM-resident index and buffers with SPDK userspace I/O, and the measured GET P99 at 1,280 connections is 3.999 ms against Redis's 4.415 ms.
At 1,280 connections the run records 971,950 SET QPS, 2.2% above Redis. The io_uring YCSB experiment adds structured-record work: 529,501 operations per second for a 50/50 read/update mix and 577,373 for a 95/5 mix.
Fewer nodes to place, patch, monitor and reshard. Redis Cluster's bus grows as N(N−1) initiated connections — 89,700 at 300 nodes — while the consolidated topology runs three members: two data nodes and a voter.
No. Meta uses Raft for topology and authority, while Data replication is asynchronous, so recovery-point and recovery-time targets must be set from that and verified through drills at the intended dataset size.
Lavik is distributed under an Apache 2.0 open-source license.
Benchmark setup: Azure Standard_L16aos_v4, Japan West zone 2, AMD EPYC 9V74, 16 logical CPUs, eight physical cores, 125.8 GiB RAM and six 1.92 TB NVMe devices. The F32als_v6 client uses 16 threads over private accelerated mlx5 networking and Linux TCP. All runs use a single server without Data replicas, with AOF disabled on Redis and Valkey.
When the footprint moves from 1 TB to 10 TB and beyond, the price of each additional terabyte stops being an infrastructure detail and becomes a product constraint. The alternative the published numbers put on the table is a Redis-compatible NVMe key-value store measured above Redis on DRAM, with efficient large-object storage and an Apache 2.0 open-source license. Start from the commands your service issues, load a representative dataset, and measure the latency budget that matters to your users before you provision anything. Moving from 300 Redis nodes to three Lavik nodes is not shrinkage for its own sake: it is a way to size compute to the workload's real demand and leave room for the data to keep growing.