Skip to content

Fix caching & memory infrastructure review findings - #185

Open
schenksj wants to merge 2 commits into
mainfrom
fix/caching-memory-infra-review
Open

Fix caching & memory infrastructure review findings#185
schenksj wants to merge 2 commits into
mainfrom
fix/caching-memory-infra-review

Conversation

@schenksj

@schenksj schenksj commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Addresses the findings from the caching/memory-infrastructure review across the L2 disk cache, global cache, split cache manager, memory pool, and Java SplitCacheManager. Native cargo check is clean and mvn compile builds successfully.

Correctness (high severity)

  • Coalesced L2 reads no longer serve corrupt data (§1.2): failed sub-range reads are pushed into gaps and refetched from L3 instead of being silently reported as fully-cached (zero-filled/truncated); overlaps are clipped to the cursor; combine_segments guards result length.
  • find_overlapping rewritten (§1.3) to correctly return nested/overlapping cached ranges.
  • Lost-wakeup race fixed (§1.4): the writer decrements queued_bytes and notify_all under the backpressure mutex.
  • Name-uniqueness enforced (§1.5) so two configs sharing a name can't clobber each other in the name-keyed native registry.
  • LRU seeded from the manifest at startup (§1.6) so cold splits from prior runs stay evictable.
  • MmapCache invalidated on evict/replace (§1.7).
  • SplitCacheManager.close() reference-counted + idempotent (§1.10), fixing shared-singleton teardown, a resurrection race, and the getGlobalInstance() refcount bypass.
  • GlobalCacheConfig knobs now take effect (§1.1): written into SearcherConfig so caches and aggregation limits are actually sized; dead component caches removed.

Honesty / API

  • Compression is a no-op by design; the Java surface is deprecated/documented (§1.8).
  • force_eviction really clears the in-memory query caches so flush* APIs work; CacheFlushStats.itemsEvicted fixed (§1.11).
  • Cache-key secrets are hashed, not embedded verbatim (§3.4).

Other

  • L2 disk cache get-or-create by root path (§2.2).
  • Storage-resolver cache unified, bounded (FIFO), cleared on last close, handles S3+Azure together; Azure wired into the manager; credential key includes endpoint/path-style (§2.3, §2.4, §1.9).
  • Drop non-blocking (no self-join deadlock); manifest sync outside the lock; compress_data uses Cow; queued bytes count toward eviction (§2.1, §3.2, §3.3, §2.7).
  • Memory pool: deficit-targeted JVM acquire; skip churny sub-threshold releases (§5.1, §5.2).
  • Deleted the dead byterange_cache module (§4).

Documented (with rationale) two known limitations where a full fix was higher-risk than warranted: the query path uses the global (not credential-scoped) SearcherContext, and the per-search permit provider bypasses global concurrency budgeting (§1.9, §2.6).

Review

Two adversarial review rounds. A self-join deadlock introduced by an initial Drop-joins-threads change was caught and reverted; the getGlobalInstance() refcount bypass was caught and fixed. Remaining areas verified correct.

🤖 Generated with Claude Code

Addresses the correctness, efficiency, honesty, and dead-code findings from
the caching/memory infrastructure review across the L2 disk cache, global
cache, split cache manager, memory pool, and Java SplitCacheManager.

Disk cache (L2):
- Coalesced reads no longer silently zero-fill/truncate: failed sub-range
  reads are pushed into gaps (refetched from L3), overlaps are clipped to the
  cursor, and combine_segments guards result length.
- Rewrite find_overlapping to correctly return nested/overlapping ranges
  (the old backward binary scan could drop an overlapping range).
- Close the lost-wakeup race in size-based write backpressure: the writer
  decrements queued_bytes and notifies under the backpressure mutex.
- Seed the LRU table from the persisted manifest at startup so cold splits
  from prior runs remain evictable; rewrite the LRU to O(1) HashMap.
- Invalidate mmap'd files on eviction (remove_under_dir) and after replace.
- Make Drop non-blocking: signal shutdown + non-blocking wake, no self-join
  (the background threads transiently upgrade the Weak, so Drop can run on
  them; joining there would deadlock / panic dropping the runtime).
- compress_data returns Cow (zero-copy uncompressed path); manifest sync
  snapshots+compact-serializes outside the write lock; count queued bytes
  toward the eviction trigger.

Compression: deprecate/document the no-op compression API (data is stored
uncompressed by design for access-latency reasons).

Split cache manager:
- Enforce cache-name uniqueness (native registry is name-keyed) and make
  SplitCacheManager reference-counted + idempotent on close(), fixing the
  shared-singleton teardown and a resurrection race.
- Implement force_eviction (real clearing) so flush* APIs work; fix
  CacheFlushStats.itemsEvicted. Get-or-create the L2 disk cache by root path.
  Wire Azure config into the manager. Warn on first-wins L1 sizing. Remove
  dead per-manager stat atomics.

Global cache:
- Flow GlobalCacheConfig knobs into SearcherConfig so the caches and the
  aggregation limits are actually sized (previously silent no-ops); remove
  the dead component cache instances.
- Unify and bound the storage-resolver cache, clear it on last-manager close,
  and build a resolver from S3 + Azure together (mixed cloud). Include
  endpoint/path-style in the credential context key.

Memory pool: target the deficit on JVM acquire; skip churny sub-threshold
releases (still releasing the final grant when fully drained).

Dead code: delete the unused byterange_cache module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 5, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

try_acquire now sizes the JVM request by the deficit (new_used - granted),
but the success check still required acquired >= size. When an earlier
watermark-batched grant already backed part of current usage, deficit < size,
so acquiring exactly the deficit (which makes granted == new_used) was
wrongly rejected — denying e.g. a 128MB index_writer allocation even though
the JVM accountant granted the full request.

Validate against the deficit instead of size (deficit == 0 is the in-grant
headroom top-up case and is always fine). Fixes
NativeMemoryManagerTest.testCategoryBreakdownWithJvmPool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@schenksj
schenksj deployed to test-runners July 5, 2026 16:43 — with GitHub Actions Active
@claude

claude Bot commented Jul 5, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

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