DEV Community

Cover image for Why we stopped waiting for vector indexes to catch up
murtaza yusufali
murtaza yusufali

Posted on

Why we stopped waiting for vector indexes to catch up

Redis Stack's vector search sits behind the same core Redis has always had: mostly single-threaded, with I/O threading bolted on top. On a modern cloud box with dozens of cores, that means one core does the work while the rest sit idle, no matter how many concurrent clients are hammering the index.

We build KiviDB, a Redis-compatible in-memory database written in Rust, on a shard-per-core architecture instead. I wanted to see what that buys you in practice, so we reran our HNSW vector search benchmarks against Redis Stack under real concurrency, across the full recall curve, not just one cherry-picked operating point.

Setup

  • Hardware: AWS Graviton, c7gn.12xlarge - a dedicated server instance plus a separate client instance in the same placement group.
  • Dataset: glove-25-angular, 1,183,514 vectors, cosine distance.
  • Index: HNSW, M=16.
  • Versions: KiviDB v1.0.3 vs. Redis Stack 7.4.7.
  • Harness: the vector-db-benchmark fork we maintain, itself based on Qdrant's original tooling.
  • Load: 100 concurrent clients, best of 3 repetitions. ## Precision vs. throughput, across every tier

Most vector database comparisons quote one QPS number at one precision target, which tells you almost nothing about how the system behaves outside that exact point. So here's the full sweep, from the high-throughput end to max recall:

Tier KiviDB precision KiviDB QPS KiviDB p50 KiviDB p99 Redis Stack precision Redis Stack QPS Redis Stack p50 Redis Stack p99
High throughput 0.942 42,360 1.91ms 4.52ms 0.912 2,508 30.40ms 59.68ms
Mid recall 0.961 36,023 2.38ms 4.92ms 0.937 2,265 34.12ms 67.25ms
High recall 0.989 23,409 3.82ms 7.66ms 0.979 1,563 49.84ms 98.67ms
Max recall 0.998 13,229 6.56ms 14.91ms 0.995 988 80.27ms 159.44ms

Lower latency at every recall tier

A few things stand out here beyond the headline multiplier:

  • KiviDB wins on precision at every tier too, not just throughput. At the "high throughput" tier, KiviDB is running at 0.942 precision while Redis Stack is at 0.912, so the QPS gap isn't a recall-for-speed trade.
  • Even at KiviDB's max recall setting (0.998), it still out-throughputs Redis Stack's best result anywhere on the curve, 13,229 QPS at 0.998 precision vs. Redis Stack's peak of 2,508 QPS at 0.912. That's the whole curve, not one point.
  • Latency scales far more gently for KiviDB. Going from the fastest tier to max recall, KiviDB's p50 grows about 3.4x (1.91ms → 6.56ms). Redis Stack's p50 grows about 2.6x over the same range (30.40ms → 80.27ms), but it's starting from a p50 that's already 16x higher than KiviDB's at the fast end. Summed up: at 100 concurrent clients, EF 64, KiviDB serves up to 16.9x the search QPS of Redis Stack, with higher recall at every EF tier in the sweep.

Ingest: no background phase

This is the part that doesn't show up in a QPS table but matters just as much operationally. Redis Stack builds its HNSW index asynchronously, the upload finishes, and then there's a separate indexing phase before the data is actually searchable. KiviDB indexes synchronously: when a write returns, that vector is already in the graph.

For the full 1.18M-vector dataset on the same uncontended hardware:

KiviDB Redis Stack
Upload throughput 5,262 vec/s 2,606 vec/s
Time-to-queryable 225s (synchronous) 455s + ~8s index wait

That's 2.0x faster wall-clock ingest, and the difference compounds if your workload ingests continuously rather than loading once and querying forever, there's no separate "wait for the index to catch up" step to reason about.

Write latency, and how it compares beyond Redis Stack

Vector search aside, here's how the core write/read path compares against Redis 8.4 and Dragonfly, using memtier_benchmark on the same hardware and flags:

KiviDB Dragonfly Redis 8.4
Write avg / p99 0.17ms / 0.35ms 0.52ms / 3.71ms 4.93ms / 20.22ms
Read avg / p99 0.16ms / 0.32ms 0.36ms / 0.74ms 2.72ms / 4.70ms
Mixed 1:1 avg / p99 0.17ms / 0.34ms 0.37ms / 0.79ms 3.27ms / 5.59ms

Why the gap is this large

Redis Stack's vector index inherits Redis's execution model, so query fan-out across concurrent clients is bottlenecked by however much of one core is left over. KiviDB shards the keyspace, including vector indexes, across cores, so 100 concurrent clients aren't queuing behind the same execution thread, they're spread across shards that run genuinely in parallel.

The trade-off is complexity we had to solve as builders, not something you have to think about as a user: cross-shard query coordination, consistent hashing for shard assignment, and making sure HNSW graph updates stay correct under concurrent writes landing on different shards. Happy to go deeper on the shard-coordination internals in a follow-up if there's interest.

Worth being upfront about what this benchmark doesn't tell you: it's a single dataset (glove-25-angular, 25 dimensions) at one vector count. If your workload is higher-dimensional or much larger, run it yourself before trusting these numbers for capacity planning.

Try it

Same Redis protocol on the vector search commands, so if you've already got Redis client code, it mostly just works:

docker run -p 6379:6379 quay.io/kividbio/kividb:latest
Enter fullscreen mode Exit fullscreen mode

Full benchmark report and reproduction steps: kividb.io/vector-database. Command reference and docs at kividb.io/docs. If you run this on your own hardware and get different numbers, I'd genuinely like to hear about it.

Top comments (0)