Skip to content

perf: move version field to lazy side table#284

Merged
kacy merged 1 commit intomainfrom
perf/version-side-table
Feb 25, 2026
Merged

perf: move version field to lazy side table#284
kacy merged 1 commit intomainfrom
perf/version-side-table

Conversation

@kacy
Copy link
Copy Markdown
Owner

@kacy kacy commented Feb 25, 2026

summary

moves the version: u64 field from every Entry in the keyspace to a lazily-populated side table (AHashMap<CompactString, u64>) on Keyspace. this field was only meaningful for WATCH/EXEC optimistic locking (<1% of workloads) but consumed 8 bytes on every single entry.

  • key_version() now inserts into the side table on first call (simulating WATCH registration)
  • bump_version() only bumps the version if the key is already tracked — on the hot path this is a fast hash-miss on an empty map
  • version entries are cleaned up on delete, expire, evict, flush, and clear
  • ENTRY_OVERHEAD reduced from 128 to 120 bytes

estimated savings: ~8 bytes per key in sharded mode (208 → ~200 B/key).

what was tested

  • all 507 unit tests pass (cargo test -p emberkv-core)
  • full workspace builds and passes all tests (cargo test --all)
  • cargo clippy --all clean (no new warnings)
  • entry_overhead_not_too_small test validates the new constant against actual struct sizes

design considerations

the alternative was keeping version on Entry but using a smaller type (u32). that only saves 4 bytes with alignment and still writes 4 bytes on every mutation. the side table approach saves 8 bytes per entry and eliminates the unconditional write on the hot path entirely — mutations now only do a hash lookup against self.versions which is almost always empty (fast miss).

the tradeoff is that WATCH/EXEC now has slightly different bookkeeping (lazy population on first key_version() call), but the semantics are identical: WATCH captures a snapshot version, EXEC detects changes.

the version field (u64, 8 bytes) existed on every entry but was only
read during WATCH/EXEC, which is <1% of production workloads. every
mutation unconditionally bumped it.

moved version tracking to a lazily-populated AHashMap on keyspace:
- key_version() inserts into the side table on first call (like WATCH)
- bump_version() only bumps if the key is tracked (almost never)
- version entries are cleaned up on delete/expire/evict/flush

this shrinks Entry from 52 to 44 bytes and reduces ENTRY_OVERHEAD
from 128 to 120, saving ~8 bytes per key in sharded mode.
@kacy kacy merged commit 92e57f1 into main Feb 25, 2026
7 checks passed
@kacy kacy deleted the perf/version-side-table branch February 25, 2026 12:10
kacy added a commit that referenced this pull request Feb 25, 2026
sharded string overhead is 180 B/key (was 208), hash is 215 B/key
(was 243) after the entry struct optimizations in PRs #284-287.
kacy added a commit that referenced this pull request Feb 25, 2026
sharded string overhead is 180 B/key (was 208), hash is 215 B/key
(was 243) after the entry struct optimizations in PRs #284-287.
kacy added a commit that referenced this pull request Feb 25, 2026
refresh all throughput, latency, and encryption numbers after entry
struct optimization PRs (#284-287). remove concurrent mode references
since it's being deprecated. add data type throughput section from
bench-datatypes.sh. fix memtier header (50k → 10k req/client).
kacy added a commit that referenced this pull request Feb 25, 2026
refresh all throughput, latency, and encryption numbers after entry
struct optimization PRs (#284-287). remove concurrent mode references
since it's being deprecated. add data type throughput section from
bench-datatypes.sh. fix memtier header (50k → 10k req/client).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant