Skip to content
kvbench

Read-heavy: mostly reading keys you already wrote

Caches, lookup tables, and read-mostly indexes. tamnd/kv reads at 6.8 million keys per second, ahead of every other embedded Go engine measured.

This is the cache and lookup-table workload: you write the data once (or rarely), and after that the job is almost entirely get. Session stores, feature flags, denormalised read models, anything that sits in front of a slower system of record.

When reads dominate, the engine's storage shape barely matters except for one thing: how few steps a get takes. The hash-log engines win here because a read is one in-memory index lookup and one disk seek, nothing more.

The numbers

Pure random reads, 100,000 keys, 1 KB values, 8 concurrent clients, on the Apple M4:

Engine Shape Reads/sec p99 latency
tamnd/kv hash-log 6,848,000 6 us
pogreb hash-log 4,008,000 16 us
buntdb in-memory B-tree 3,572,000 15 us
goleveldb LSM 1,032,000 129 us
bbolt B+tree 865,000 233 us
pebble LSM 856,000 222 us
badger LSM 594,000 442 us
sqlite B-tree 45,000 1.8 ms

tamnd/kv reads roughly 1.7x faster than the next engine and 150x faster than sqlite, with the tightest tail by a wide margin: a 6 microsecond p99 against 129 microseconds for the first LSM. That gap is the hash-log shape doing what it is built for.

The ordering holds on every machine, only the absolute rate drops with fewer or slower cores:

Engine M4 EPYC 4-core EPYC 6-core EPYC 8-core
tamnd/kv 6,848,000 1,670,000 1,013,000 978,000
pogreb 4,008,000 1,121,000 816,000 544,000
buntdb 3,572,000 1,229,000 833,000 712,000
goleveldb 1,032,000 406,000 240,000 188,000

A read-only workload (YCSB-C, Zipfian-skewed so a few hot keys take most of the traffic) tells the same story: tamnd/kv leads at 5,379,000 reads/sec, ahead of buntdb at 3,994,000 and pogreb at 3,826,000.

What to pick

  • tamnd/kv if reads are the whole job and the keyset fits in memory. Nothing here reads faster.
  • pogreb or buntdb if you want the same read-first profile with a different trade: pogreb is a simpler on-disk hash-log, buntdb keeps everything in RAM and is strong on writes too.
  • goleveldb if you also need ordered scans (the hash-log engines have none) and can accept reads an order of magnitude slower.

What to avoid

  • sqlite for pure key/value reads. It is 150x slower here. Its strengths are SQL and durable batching, not raw get.
  • The hash-log engines (tamnd/kv, pogreb) if your read keyset does not fit in RAM, because the index is in memory, or if you need ordered scans, which they do not support.

One caveat that does not show in a read-only table: if this workload also does frequent updates to the same hot keys, read the mixed scenario before committing to tamnd/kv, because update churn is its weak spot.