Skip to content

fix(cache): count and bound bytes held outside the memory index - #44

Open
zfarrell wants to merge 1 commit into
mainfrom
fix/count-in-flight-cache-bytes
Open

fix(cache): count and bound bytes held outside the memory index#44
zfarrell wants to merge 1 commit into
mainfrom
fix/count-in-flight-cache-bytes

Conversation

@zfarrell

@zfarrell zfarrell commented Sep 2, 2026

Copy link
Copy Markdown

Addresses part of #43: the memory tier's budget counted an entry only once it had landed in the index, so every intermediate the hydrate → insert → squeeze cycle held on the way there was invisible to it.

BudgetAccounting now tracks in-flight bytes — disk decodes, squeeze output awaiting its write and insert, and entries pending admission — via an RAII reservation, exposed as in_flight_memory_bytes and a peak alongside memory_usage_bytes (transients die between gauge scrapes, so the peak is what a scrape can catch). Reservations report and bound; they never gate admission, per the issue's staging. squeeze_victims no longer fans out with buffer_unordered(usize::MAX): victims are grouped so their summed indexed sizes stay under max_memory_bytes / 32, which bounds bytes rather than count — a squeeze's output cannot exceed its input, so the indexed sizes are a sound bound.

Measured on 8 liquid victims of 6,241 B: peak in-flight is 18,723 B bounded vs 55,770 B unbounded (≈ 9× one entry, i.e. all eight outputs plus the pending entry resident together — the issue's claim, now under test). Only victims whose squeeze writes to disk pile up; an arrow victim's transcode never awaits, so its future completes in one poll and never overlaps a sibling. The concurrent squeeze path — the one that ships, since builders.rs only disables it under cfg(test) — now has coverage: entries read back byte-identical, the tally matches the sum of indexed entry sizes, and in-flight drains to zero.

Directions 2 (conditional hydration) and the gating half of 1 are deliberately left out; they need the gauge's numbers from the fixture first. I have no access to that fixture or cluster, so the issue's acceptance criteria — the get_arrow_array_with_filter → squeeze profile edge, anon peak, and the 240k warm p50 — are unverified here and need a run by someone who does.

What this is not expected to do: move the anon peak on the fixture. The observed OOM is dominated by the arrow→liquid transcode, and that arm (squeeze.rs:116-119) returns bytes_to_write: None — no write, so no await, so those futures were already polled to completion one at a time and never overlapped. Grouping has nothing to bound there. What should change is the visibility: peak_in_flight_memory_bytes should come back distinctly non-zero, because squeeze_victim_inner reserves the victim's indexed size across exactly the window the profile attributes 43-49% of live heap to. If it instead reads near zero on a run that OOMs, the reservation is missing the transient that actually dominates and the accounting needs rework before direction 2 is designed on top of it.


let victims = 8;
let cache = LiquidCacheBuilder::new()
.with_max_memory_bytes(entry_bytes * victims)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: this test never exercises a group holding more than one victim (not blocking).

squeeze_in_flight_limit() returns entry_bytes * 8 / 32, which is entry_bytes / 4. insert_inner reserves entry_bytes for the pending entry at core.rs:359 before calling squeeze_victims. group_victims_by_in_flight_bytes then saturates limit to 0 at core.rs:541. Grouping therefore produces eight groups of one victim, and the concurrent path runs sequentially.

The other new test builds multi-victim groups but asserts nothing about the peak. So no test covers the bound when a group holds several victims.

Raise max_memory_bytes here so limit admits several victims. Then assert the peak stays under squeeze_in_flight_limit() plus the pending entry.

/// outputs in memory at once, which is unbounded in bytes however few
/// victims there are: eight 60 MB batches is half a gigabyte of transcode
/// output outside the index. A squeeze's output cannot exceed its input, so
/// each victim's indexed size bounds what it will add, and grouping by that

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: the stated bound does not hold on every squeeze path (not blocking).

TranscodeSqueezeEvict returns a memory_squeezed_liquid entry and a full bytes_to_write buffer at policies/squeeze.rs:111-114 and again at policies/squeeze.rs:146-149. squeeze_victim_inner resizes the reservation to the sum of both at core.rs:611-614. That sum can exceed the entry's indexed size, so a group can hold more than squeeze_in_flight_limit().

Reword the comment to call the indexed size an approximate bound rather than a hard one.

@claude claude 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.

Reviewed the full diff. Accounting is balanced: every reservation has a matching release on both the success and the error paths, and reset_usage correctly leaves in_flight_memory_bytes alone so outstanding reservations cannot underflow it. Two nits left inline. CI checks were still queued at review time, so test results are unverified.

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.05109% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/datafusion/src/cache/mod.rs 0.00% 6 Missing ⚠️
src/core/src/cache/core.rs 93.15% 4 Missing and 1 partial ⚠️
src/datafusion-server/src/admin_server/handlers.rs 0.00% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

📊 Benchmark Comparison

Current: eadbd752 (Liquid) vs Baseline: eadbd752 (DataFusionDefault)

Query Cold Time Δ Warm Time Δ CPU Time Δ
Q1 3.0ms (2.0ms) +50.0% 0.000ms (0.000ms) +0.0% 0.000ms (0.000ms) +0.0%
Q2 10.0ms (6.0ms) +66.7% 3.0ms (4.0ms) -25.0% 5.8ms (5.8ms) +0.0%
Q3 15.0ms (12.0ms) +25.0% 5.2ms (12.0ms) -56.2% 2.0ms (23.5ms) -91.5%
Q4 17.0ms (11.0ms) +54.5% 3.2ms (11.2ms) -71.1% 2.0ms (23.2ms) -91.4%
Q5 57.0ms (47.0ms) +21.3% 44.0ms (50.2ms) -12.4% 3.0ms (25.2ms) -88.1%
Q6 147.0ms (100.0ms) +47.0% 71.5ms (103.2ms) -30.8% 23.2ms (81.5ms) -71.5%
Q7 1.0ms (1.0ms) +0.0% 0.000ms (0.000ms) +0.0% 0.000ms (0.000ms) +0.0%
Q8 7.0ms (5.0ms) +40.0% 4.8ms (5.2ms) -9.5% 7.2ms (6.0ms) +20.8%
Q9 90.0ms (86.0ms) +4.7% 73.2ms (82.5ms) -11.2% 4.0ms (43.5ms) -90.8%
Q10 95.0ms (89.0ms) +6.7% 68.5ms (89.0ms) -23.0% 5.0ms (63.2ms) -92.1%
Q11 43.0ms (26.0ms) +65.4% 18.5ms (24.2ms) -23.7% 30.5ms (35.2ms) -13.5%
Q12 41.0ms (31.0ms) +32.3% 19.2ms (29.5ms) -34.7% 31.8ms (44.0ms) -27.8%
Q13 186.0ms (112.0ms) +66.1% 98.0ms (113.2ms) -13.5% 50.5ms (84.5ms) -40.2%
Q14 292.0ms (140.0ms) +108.6% 143.0ms (146.2ms) -2.2% 90.5ms (111.2ms) -18.7%
Q15 244.0ms (107.0ms) +128.0% 121.0ms (104.2ms) +16.1% 90.5ms (98.5ms) -8.1%
Q16 138.0ms (116.0ms) +19.0% 144.2ms (115.8ms) +24.6% 4.5ms (26.5ms) -83.0%
Q17 539.0ms (230.0ms) +134.3% 306.5ms (232.2ms) +32.0% 108.0ms (107.5ms) +0.5%
Q18 557.0ms (225.0ms) +147.6% 311.5ms (226.0ms) +37.8% 117.2ms (107.2ms) +9.3%
Q19 890.0ms (469.0ms) +89.8% 497.5ms (459.0ms) +8.4% 117.2ms (149.8ms) -21.7%
Q20 15.0ms (13.0ms) +15.4% 3.2ms (11.5ms) -71.7% 6.5ms (24.5ms) -73.5%
Q21 907.0ms (172.0ms) +427.3% 290.2ms (175.0ms) +65.9% 501.2ms (278.5ms) +80.0%
Q22 1.18s (172.0ms) +587.2% 309.5ms (164.5ms) +88.1% 170.5ms (345.5ms) -50.7%
Q23 2.52s (475.0ms) +430.3% 996.2ms (477.5ms) +108.6% 507.8ms (769.0ms) -34.0%
Q24 27.67s (898.0ms) +2981.3% 832.5ms (938.5ms) -11.3% 620.5ms (2.56s) -75.7%
Q25 179.0ms (72.0ms) +148.6% 14.8ms (60.0ms) -75.4% 35.8ms (122.5ms) -70.8%
Q26 103.0ms (45.0ms) +128.9% 18.0ms (46.0ms) -60.9% 46.8ms (84.2ms) -44.5%
Q27 163.0ms (56.0ms) +191.1% 25.5ms (62.0ms) -58.9% 75.5ms (126.0ms) -40.1%
Q28 1.05s (216.0ms) +387.0% 296.8ms (215.8ms) +37.5% 358.8ms (285.5ms) +25.7%
Q29 1.76s (987.0ms) +78.6% 1.06s (982.0ms) +7.9% 576.8ms (350.0ms) +64.8%
Q30 33.0ms (26.0ms) +26.9% 22.0ms (30.2ms) -27.3% 5.0ms (21.5ms) -76.7%
Q31 274.0ms (112.0ms) +144.6% 70.0ms (103.8ms) -32.5% 39.8ms (146.5ms) -72.9%
Q32 566.0ms (104.0ms) +444.2% 104.2ms (101.5ms) +2.7% 55.0ms (151.5ms) -63.7%
Q33 395.0ms (360.0ms) +9.7% 358.2ms (359.5ms) -0.3% 8.5ms (77.8ms) -89.1%
Q34 1.08s (438.0ms) +146.8% 530.2ms (437.5ms) +21.2% 350.2ms (274.0ms) +27.8%
Q35 1.13s (424.0ms) +166.5% 515.2ms (415.0ms) +24.2% 357.0ms (267.0ms) +33.7%
Q36 114.0ms (105.0ms) +8.6% 98.0ms (103.8ms) -5.5% 4.0ms (24.0ms) -83.3%
Q37 322.0ms (97.0ms) +232.0% 83.5ms (97.0ms) -13.9% 41.0ms (71.0ms) -42.3%
Q38 72.0ms (47.0ms) +53.2% 33.8ms (44.8ms) -24.6% 18.8ms (24.8ms) -24.2%
Q39 265.0ms (49.0ms) +440.8% 13.2ms (46.0ms) -71.2% 11.8ms (70.0ms) -83.2%
Q40 850.0ms (183.0ms) +364.5% 231.8ms (181.0ms) +28.0% 89.5ms (124.8ms) -28.3%
Q41 22.0ms (18.0ms) +22.2% 10.8ms (19.0ms) -43.4% 6.0ms (17.0ms) -64.7%
Q42 20.0ms (17.0ms) +17.6% 10.2ms (17.5ms) -41.4% 6.8ms (14.5ms) -53.4%
Q43 20.0ms (18.0ms) +11.1% 10.2ms (15.5ms) -33.9% 6.0ms (10.5ms) -42.9%

⚠️ LiquidCache is slower on 14 queries (warm)

  • Q23: warm +108.6% (996.2ms vs 477.5ms)
  • Q22: warm +88.1% (309.5ms vs 164.5ms)
  • Q21: warm +65.9% (290.2ms vs 175.0ms)
  • Q18: warm +37.8% (311.5ms vs 226.0ms)
  • Q28: warm +37.5% (296.8ms vs 215.8ms)
  • Q17: warm +32.0% (306.5ms vs 232.2ms)
  • Q40: warm +28.0% (231.8ms vs 181.0ms)
  • Q16: warm +24.6% (144.2ms vs 115.8ms)
  • Q35: warm +24.2% (515.2ms vs 415.0ms)
  • Q34: warm +21.2% (530.2ms vs 437.5ms)
  • Q15: warm +16.1% (121.0ms vs 104.2ms)
  • Q19: warm +8.4% (497.5ms vs 459.0ms)
  • Q29: warm +7.9% (1.06s vs 982.0ms)
  • Q32: warm +2.7% (104.2ms vs 101.5ms)

Compared Liquid vs DataFusionDefault on the same runner
Cold Time: first iteration; Warm Time: average of remaining iterations.

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