Skip to content

feat(l1): add durable writeback and prefetch eviction - #17

Open
voipmonitor wants to merge 3 commits into
release/v0.5.2-glm52-dcp-basefrom
feat/lmcache-l1-writeback-20260729
Open

feat(l1): add durable writeback and prefetch eviction#17
voipmonitor wants to merge 3 commits into
release/v0.5.2-glm52-dcp-basefrom
feat/lmcache-l1-writeback-20260729

Conversation

@voipmonitor

@voipmonitor voipmonitor commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Ports the useful L1 writeback work from Florian's closed PR #5 onto the GLM-5.2 release base, then tightens the durability and adapter-lifecycle contracts found during review.

  • optionally persists L1 victims to a synchronous L2 adapter before deletion
  • adds bounded periodic L1-to-L2 backup and emergency prefetch eviction
  • fails closed when no synchronous L2 adapter is available
  • preserves keys on reserve/read, partial-store, adapter, and backend failures
  • serializes flushes with dynamic adapter removal so native resources cannot close in use
  • validates incompatible writeback settings and exposes status/circuit-breaker diagnostics

The native durable-store contract consumed here is provided by #15. No writeback behavior is enabled by default.

Provenance

Validation

  • ruff check and ruff format on changed files
  • 18/18 focused writeback tests
  • 164/164 L1, prefetch, distributed storage-manager, L2-key, and writeback tests
  • tests ran with the LMCache native extension rebuilt from this exact branch

Summary by CodeRabbit

  • New Features

    • Added optional L1-to-L2 write-back when cached data is evicted, preserving data durability.
    • Added periodic background flushing of L1 data to L2 while keeping L1 copies available.
    • Added emergency eviction to free L1 capacity when large prefetch operations run out of space.
    • Added configurable controls for write-back, flush intervals, and prefetch eviction behavior.
  • Bug Fixes

    • Failed or partial L2 persistence now preserves readable L1 data and retries with backoff.
    • Added validation for invalid or incompatible eviction settings.

flobernd and others added 2 commits July 29, 2026 17:24
L1 eviction discarded objects outright, so chunks that never went
through the async store path were lost under memory pressure, and a
restart cost a full cold prefill for every session. Three new opt-in
EvictionConfig knobs (all default off, CLI args included):

- write_back_on_evict: eviction actions target L2_CACHE; readable
  objects are synchronously persisted in bounded 128-key batches and
  deleted from L1 only once every key in the batch is durable
  (per-key results from the native adapter). Repeated flush failures
  open a circuit breaker with exponential backoff instead of retrying
  a dead L2 every second.
- periodic_flush_interval: below-watermark backup flush that copies
  evictable L1 keys to L2 without deleting them, with a rotating
  cursor over the keyspace. Restart or session expiry then restores
  from L2 instead of re-prefilling.
- emergency_evict_for_prefetch: the prefetch controller may
  synchronously evict LRU keys (write-back first) when a large L2->L1
  restore cannot reserve L1 buffers, and retries OOM reservations
  once. Non-WARM requests only, capped at 60% of L1 per restore.

New L1Manager helpers get_evictable_keys() and num_objects() keep the
controllers on the public interface.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@voipmonitor, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c5796fa-cb43-448c-8b05-ee5c6ff932cf

📥 Commits

Reviewing files that changed from the base of the PR and between 0c897aa and 01743ad.

📒 Files selected for processing (5)
  • lmcache/v1/distributed/l1_manager.py
  • lmcache/v1/distributed/storage_controllers/eviction_controller.py
  • lmcache/v1/distributed/storage_controllers/prefetch_controller.py
  • lmcache/v1/distributed/storage_manager.py
  • tests/v1/distributed/test_l1_write_back_eviction.py
📝 Walkthrough

Walkthrough

The distributed storage stack adds opt-in L1-to-L2 write-back eviction, periodic backup flushing, emergency eviction for prefetch restores, new configuration flags and validation, runtime adapter synchronization, and comprehensive tests.

Changes

L1 write-back and prefetch capacity management

Layer / File(s) Summary
Configuration and evictable-key queries
lmcache/v1/distributed/config.py, lmcache/v1/distributed/l1_manager.py, tests/v1/distributed/test_l1_write_back_eviction.py
Adds write-back configuration fields, CLI plumbing, validation, and synchronized L1 object queries with configuration tests.
Synchronous write-back and periodic backup
lmcache/v1/distributed/storage_controllers/eviction_controller.py, tests/v1/distributed/test_l1_write_back_eviction.py
Persists evicted L1 objects synchronously to durable L2 adapters, preserves keys on failed or partial writes, applies backoff, supports emergency eviction, and periodically backs up without deleting L1 copies.
Prefetch make-room and OOM retry
lmcache/v1/distributed/storage_controllers/prefetch_controller.py, tests/v1/distributed/test_l1_write_back_eviction.py
Adds optional emergency eviction and one retry pass for failed non-WARM prefetch reservations.
Runtime adapter synchronization
lmcache/v1/distributed/storage_manager.py
Synchronizes active L2 adapters after initialization, addition, and deletion, and wires emergency prefetch eviction when a synchronous adapter is available.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PrefetchController
  participant L1EvictionController
  participant L1Manager
  participant L2Adapter
  PrefetchController->>L1EvictionController: emergency_evict_bytes(target_free_bytes)
  L1EvictionController->>L1Manager: select and reserve evictable objects
  L1EvictionController->>L2Adapter: store_objects_sync(keys, objects)
  L1EvictionController->>L1Manager: delete durably persisted objects
  PrefetchController->>L1Manager: retry failed reserve_write keys
Loading

Suggested reviewers: maobaolong, yaojiayi, deng451e

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main change: durable L1 writeback plus prefetch eviction support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lmcache-l1-writeback-20260729

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@voipmonitor

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
lmcache/v1/distributed/storage_controllers/prefetch_controller.py (1)

127-136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

strict=False hides a mismatch the type already guarantees.

MemoryLayoutDesc.__post_init__ (lmcache/v1/distributed/api.py:257) already rejects unequal shapes/dtypes, so strict=True here costs nothing and turns a future invariant break into a loud failure instead of a silently truncated byte count.

♻️ Suggested tweak
-    for shape, dtype in zip(layout_desc.shapes, layout_desc.dtypes, strict=False):
+    for shape, dtype in zip(layout_desc.shapes, layout_desc.dtypes, strict=True):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lmcache/v1/distributed/storage_controllers/prefetch_controller.py` around
lines 127 - 136, Update _layout_nbytes to use strict=True when zipping
layout_desc.shapes and layout_desc.dtypes. Preserve the existing byte-count
calculation while ensuring any future length mismatch raises immediately instead
of truncating the iteration.
tests/v1/distributed/test_l1_write_back_eviction.py (1)

237-267: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Timing-sensitive negative assertion.

time.sleep(0.05) then assert not replaced.is_set() is the only guard that set_l2_adapters actually blocks. It is a negative assertion so it will not flake spuriously on slow CI, but it also cannot prove blocking beyond 50 ms. Acceptable as-is; noting for awareness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/v1/distributed/test_l1_write_back_eviction.py` around lines 237 - 267,
The review identifies the timing-based negative assertion in
test_adapter_replacement_waits_for_active_flush as acceptable; no code change is
required. Preserve the existing synchronization and assertions.
lmcache/v1/distributed/config.py (1)

314-319: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider also validating write-back against a configured L2 tier.

write_back_on_evict fails closed at runtime: with no L2 adapter exposing store_objects_sync, EvictionDestination.L2_CACHE is registered but every flush aborts, so L1 stops evicting entirely and fills up (only a logger.warning in StorageManager.__init__). When config.l2_adapter_config.adapters is empty this is statically knowable and could be rejected here rather than degrading silently at runtime.

♻️ Optional validation
     if eviction.emergency_evict_for_prefetch and not eviction.write_back_on_evict:
         raise ValueError("emergency_evict_for_prefetch requires write_back_on_evict")
+    if eviction.write_back_on_evict and not config.l2_adapter_config.adapters:
+        raise ValueError("write_back_on_evict requires at least one L2 adapter")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lmcache/v1/distributed/config.py` around lines 314 - 319, Extend the
validation near the existing eviction checks to reject write_back_on_evict when
no L2 adapter is configured: if eviction.write_back_on_evict is enabled and
config.l2_adapter_config.adapters is empty, raise a clear ValueError. Preserve
the current periodic_flush_interval and emergency_evict_for_prefetch
validations.
lmcache/v1/distributed/storage_manager.py (1)

174-183: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicates the injection already performed by _sync_l1_writeback_adapters.

Config validation guarantees emergency_evict_for_prefetch implies write_back_on_evict (config.py:317-318), so wants_l1_write_back is always True here and _sync_l1_writeback_adapters() (Line 168) has already called set_l1_eviction_controller under the same has_l2_flush_adapter() condition. Only the warning is new; the if body is dead.

♻️ Suggested simplification
-        if eviction_config.emergency_evict_for_prefetch:
-            if self._eviction_controller.has_l2_flush_adapter():
-                self._prefetch_controller.set_l1_eviction_controller(
-                    self._eviction_controller
-                )
-            else:
-                logger.warning(
-                    "emergency_evict_for_prefetch has no synchronous L2 "
-                    "adapter; inactive until one is added"
-                )
+        if (
+            eviction_config.emergency_evict_for_prefetch
+            and not self._eviction_controller.has_l2_flush_adapter()
+        ):
+            logger.warning(
+                "emergency_evict_for_prefetch has no synchronous L2 "
+                "adapter; inactive until one is added"
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lmcache/v1/distributed/storage_manager.py` around lines 174 - 183, Remove the
redundant emergency_evict_for_prefetch conditional injection block after
_sync_l1_writeback_adapters has run. Keep the existing warning only if needed
for the no-synchronous-L2-adapter case, and rely on _sync_l1_writeback_adapters
for set_l1_eviction_controller rather than calling it again.
lmcache/v1/distributed/storage_controllers/eviction_controller.py (1)

510-533: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Backup log double-counts across adapters and trusts ok over persisted.

total_persisted/total_bytes accumulate over every adapter, so with two sync-capable adapters the log reports 2× the key count. Also if ok: adds persisted without checking it equals len(readable_keys), unlike the strict check in _flush_one_l2_batch_then_delete. Log-only impact, but the numbers are misleading for operators.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lmcache/v1/distributed/storage_controllers/eviction_controller.py` around
lines 510 - 533, Update the periodic L2 backup aggregation around
_snapshot_l2_adapters so totals represent one backup operation rather than
summing duplicate results from every adapter. Reuse the strict success criterion
from _flush_one_l2_batch_then_delete: only count a sync result when it is
successful and persisted equals len(readable_keys), and ensure the log reports
the corresponding key and byte totals without adapter double-counting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lmcache/v1/distributed/l1_manager.py`:
- Around line 796-805: Change get_evictable_keys to support bounded,
cursor-aware retrieval so it scans and returns only the requested batch while
holding the L1 lock, rather than materializing the full keyspace. Update
_backup_to_l2_no_delete_locked to use this API and preserve its rotating-cursor
behavior across calls, including correct wraparound when fewer than
_BACKUP_FLUSH_BATCH_SIZE keys remain.

In `@lmcache/v1/distributed/storage_controllers/eviction_controller.py`:
- Around line 320-330: Update get_eviction_actions to replace the fixed 64-key
padding with request-scaled padding so small tracked keyspaces do not force
ratio to 1.0 and evict all of L1. Before performing the eviction scan, check
_sync_flush_backoff_until and has_l2_flush_adapter(); while the backoff is
active or no adapter exists, return without generating actions because
_flush_to_l2_then_delete cannot free space.

In `@lmcache/v1/distributed/storage_controllers/prefetch_controller.py`:
- Around line 924-936: The emergency eviction path can block the single
_prefetch_loop thread on unbounded synchronous L2 synchronization. Update
_make_room_for_restore and _retry_oom_reservations, or the
emergency_evict_bytes/store_objects_sync path they invoke, to enforce a
deadline/timeout for synchronous L2 stores and associated reserve_read/delete
operations, or defer the eviction flush off the prefetch loop while preserving
eviction correctness.
- Around line 1126-1139: Prevent a short retentions result from raising inside
the request-processing path: either validate the output of
_policy.select_l1_retentions in _transition_to_load_phase and complete the
request cleanly on mismatch, or remove strict=True in the retention_by_key zip
to preserve the existing lenient behavior. Ensure failures still reach
_complete_request so in-flight state, locks, and reservations are released.

In `@lmcache/v1/distributed/storage_manager.py`:
- Around line 1156-1171: Update _sync_l1_writeback_adapters to clear the
prefetch controller’s L1 eviction controller when has_l2_flush_adapter() is
false, while preserving the existing wiring when a flush adapter exists. Modify
PrefetchController.set_l1_eviction_controller to accept None so the field can be
explicitly disconnected after the last sync-capable adapter is removed.

In `@tests/v1/distributed/test_l1_write_back_eviction.py`:
- Around line 338-344: Update the TestPrefetchMakeRoom setup to provide a
fixture that creates the PrefetchController via _make_prefetch_controller and
closes its _submission_efd and _adapter_ctrl_efd notifiers during teardown
without calling stop(). Change the five affected tests to accept the
prefetch_controller fixture instead of invoking the helper directly, ensuring
notifier resources are released after each test.

---

Nitpick comments:
In `@lmcache/v1/distributed/config.py`:
- Around line 314-319: Extend the validation near the existing eviction checks
to reject write_back_on_evict when no L2 adapter is configured: if
eviction.write_back_on_evict is enabled and config.l2_adapter_config.adapters is
empty, raise a clear ValueError. Preserve the current periodic_flush_interval
and emergency_evict_for_prefetch validations.

In `@lmcache/v1/distributed/storage_controllers/eviction_controller.py`:
- Around line 510-533: Update the periodic L2 backup aggregation around
_snapshot_l2_adapters so totals represent one backup operation rather than
summing duplicate results from every adapter. Reuse the strict success criterion
from _flush_one_l2_batch_then_delete: only count a sync result when it is
successful and persisted equals len(readable_keys), and ensure the log reports
the corresponding key and byte totals without adapter double-counting.

In `@lmcache/v1/distributed/storage_controllers/prefetch_controller.py`:
- Around line 127-136: Update _layout_nbytes to use strict=True when zipping
layout_desc.shapes and layout_desc.dtypes. Preserve the existing byte-count
calculation while ensuring any future length mismatch raises immediately instead
of truncating the iteration.

In `@lmcache/v1/distributed/storage_manager.py`:
- Around line 174-183: Remove the redundant emergency_evict_for_prefetch
conditional injection block after _sync_l1_writeback_adapters has run. Keep the
existing warning only if needed for the no-synchronous-L2-adapter case, and rely
on _sync_l1_writeback_adapters for set_l1_eviction_controller rather than
calling it again.

In `@tests/v1/distributed/test_l1_write_back_eviction.py`:
- Around line 237-267: The review identifies the timing-based negative assertion
in test_adapter_replacement_waits_for_active_flush as acceptable; no code change
is required. Preserve the existing synchronization and assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 015c0d82-80d2-48bf-99c7-4d64477e659f

📥 Commits

Reviewing files that changed from the base of the PR and between 9cebd40 and 0c897aa.

📒 Files selected for processing (6)
  • lmcache/v1/distributed/config.py
  • lmcache/v1/distributed/l1_manager.py
  • lmcache/v1/distributed/storage_controllers/eviction_controller.py
  • lmcache/v1/distributed/storage_controllers/prefetch_controller.py
  • lmcache/v1/distributed/storage_manager.py
  • tests/v1/distributed/test_l1_write_back_eviction.py

Comment thread lmcache/v1/distributed/l1_manager.py
Comment thread lmcache/v1/distributed/storage_controllers/eviction_controller.py
Comment thread lmcache/v1/distributed/storage_controllers/prefetch_controller.py
Comment thread lmcache/v1/distributed/storage_controllers/prefetch_controller.py
Comment thread lmcache/v1/distributed/storage_manager.py
Comment thread tests/v1/distributed/test_l1_write_back_eviction.py Outdated
@voipmonitor

Copy link
Copy Markdown
Author

Review follow-up in 01743ad also applies the valid nitpicks: strict layout zip, removal of duplicate controller injection, and first-success accounting for periodic backup. I intentionally did not reject writeback when the static config has no L2 adapter: runtime adapter addition is supported, fail-closed behavior is deliberate until a compatible adapter is attached, and adapter synchronization activates/deactivates emergency eviction dynamically. Focused result: 24 passed.

@voipmonitor

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

2 participants