Skip to content

perf(cache): use the quick_cache backend for the session metadata cache - #8013

Merged
wjones127 merged 1 commit into
lance-format:mainfrom
LuQQiu:lu/metadata-cache-quick
Jul 27, 2026
Merged

perf(cache): use the quick_cache backend for the session metadata cache#8013
wjones127 merged 1 commit into
lance-format:mainfrom
LuQQiu:lu/metadata-cache-quick

Conversation

@LuQQiu

@LuQQiu LuQQiu commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Problem

#7953 moved the session index cache off moka because moka records every cache hit into a single global read-op channel (record_read_op: channel length check + housekeeper try-lock CAS + try_send), which negatively scales with concurrent readers. The session metadata cache stayed on moka as a deliberate scope cut. It is the one remaining moka cache on a query-serving path: manifests, file/column metadata, deletion vectors, and row-id sequences are all read during query planning, at a lower rate than the index cache but on every query.

Change

Both Session::new and Session::with_index_cache_backend now build the metadata cache with QuickCacheBackend::with_capacity(metadata_cache_size) — the same backend and sizing formula as the index cache, with no new configuration.

The existing shard formula (min(cpus / 2, capacity / 4 GiB), floor 1) already fits metadata capacities. Because quick_cache splits its weight budget evenly across shards with no borrowing, and silently refuses entries heavier than a shard's share, the per-shard share is what bounds the largest admissible entry:

  • 1 GiB (default DEFAULT_METADATA_CACHE_SIZE) → 1 shard, share = full capacity
  • 36 GiB (large deployment) → 8 shards, share 4.5 GiB

The largest realistic metadata entries are manifests of wide many-fragment tables (DataFile carries fields + column_indices, ~8 B/column/file → ~100–200 MB pathological), plus whole-dataset RowIdIndex/RowAddrMask at tens of MB — all far below the ≥ 4 GiB share.

Low shard counts are fine here: warm hits in quick_cache take no lock (one atomic CLOCK reference bit), so shard count only affects admission/eviction contention, and the #7953 sweep measured a single-shard quick_cache at 6.1 Mops/s pure-get with 256 readers (10x moka). The metadata cache sees tens of reads per query versus ~2000 for the index cache.

quick_cache's own default shard count (available_parallelism × 4, 2048 on a 320-core host) is deliberately not used: it would shrink the share to ~18 MiB at 36 GiB capacity and silently refuse ordinary manifests.

What stays on moka

The io_uring handle cache (needs TTL for fd lifecycle) and the mem_wal SSTable cache (needs predicate invalidation) keep moka — both depend on moka-only features and have read rates where the read-op channel never contends. LanceCache::with_capacity's default backend is also unchanged; the two hot session caches select quick_cache explicitly.

Tests

cargo check -p lance, cargo test -p lance-core cache (25 passed), cargo test -p lance --lib session:: (12 passed). Correctness does not depend on eviction order (entries reload via single-flight), and invalidate_prefix has no production call sites.

🤖 Generated with Claude Code

Same motivation as the index cache switch (lance-format#7953): moka records every hit
into a global read-op channel, which negatively scales with readers. The
shard formula already handles metadata capacities: >= 4 GiB per shard, so
even the largest entries (manifests of wide many-fragment tables, ~100-200
MB) stay admissible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@wjones127
wjones127 merged commit a15c0bc into lance-format:main Jul 27, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants