Consolidating the #9577 frontier-eviction PRs — findings + proposed next steps #9776
Replies: 3 comments 5 replies
|
Hi, The proposal from my side fixes a more technical issue. As you noted you dont need a large graph to recreate the issue i have. All you need is a dense graph with a lot of connections between nodes, our tests set has ~10k nodes with almost 200k edges. With the way dgraph currently operates this causes the the Yen K Algorithm to explode on memory usage, i am talking multiple dozens of GB of RAM. The cause for that was the fact that the exploration happens independently of the verification of paths. Meaning that if you have enough CPU Cores to run the exploration really fast, it fills the queue at high speed, where emptying it is fairly slow. So the idea of my implementation was to have a way to throttle down the exploration, so that a system with "moderate" amounts of RAM can run the search without crashing on OOM. Basically exchanging RAM with execution speed. The implementation was specifically targeted at changing exploration (as you noted), because it seemed to be the easiest lever to change in my specific issue, especially with the idea of not changing the original behaviour of the rest of the functionality. Edit:
This mostly is important in the sense that for my usecase the correctness of all returned paths is more important than the overall speed, meaning ANY eviction is potentially a path that would have been returned. |
|
To add to my comment. I guess from reading the other PRs, mine is probably better solved as an option to the dql call. That way a user can decide on query time if they want to have higher speed or a deterministic but slower result. |
|
Hi @shiva-istari, thanks you very much for the detailed review and for summarizing the PRs. |
Uh oh!
There was an error while loading. Please reload this page.
Hi all — thanks for the work on the
maxfrontiersizek-shortest-path fix (#9577). We now have four open PRs targeting the same bug (#9576, #9599, #9607, #9678), plus a correctness harness (dgraph-benchmarks#95), and we'd like to consolidate down to one.This post lays out what we found reviewing all four against the current source of your branches (fetched today), plus a benchmark cross-check. Based on it we're proposing to close two and move one forward — but we don't want to close anything over your heads, so please read the section on your PR and tell us if we've misread it. Corrections very welcome.
The short version
Nothing here is a criticism of the effort — #9576 and #9607 both move in the right direction; the issue is specific and mechanical, detailed below.
Code review — verified against current PR source (fetched today)
cap=0guardedpq.Pop()(removes the last heap slot, an arbitrary leaf)TrimToMax)removeMax)removeMax)@hr-alebel — as we read the current branch, the eviction path is unchanged: both sites still call
pq.Pop()directly, which returnsold[n-1]— the last element of the backing array, an arbitrary heap leaf, not the highest-cost node it should evict (the original bug). The PR's contribution is the watermark that throttles frontier expansion (it stops exploring new levels once the frontier reaches ~60% of the cap) — which changes how fast the frontier grows, not what gets evicted. So the optimum can still be dropped on eviction. Did we miss a change to the eviction itself? If the intent is expansion-throttling as a complement rather than a fix to eviction, that's useful context — let us know.@veeceey —
removeMax()picks the right target (highest cost), but two things as we read it: (1) it's evict-then-push, so a higher-cost newcomer can displace a lower-cost existing node before it's even compared; and (2)MaxFrontierSize=0is unguarded, so atcap=0it evicts before every push and the query stalls. #9599/#9678 avoid both by pushing first and guarding the zero cap. (A reviewer also flagged the O(n) scan at capacity.) Does that match your understanding?@Schwarf — this looks correct to us:
pq.Pop()gone,TrimToMaxevicts the max, push-then-trim,cap<=0guarded. We saw you pushed the trimming regression test on 2026-07-06 — thanks, that's still active. Your defense of push-then-trim over trim-before-push (vs #9607) matches exactly what we found independently.@shaunpatterson — also correct:
removeMaxevicting the max, push-then-evict,MaxFrontierSize>0guarded, and the broadest test coverage of the four (unit tests forremoveMax/eviction plus integration tests running k-shortest queries at binding frontiers).#9599 and #9678 are mechanically equivalent in the eviction path (both: drop
pq.Pop(), push then evict the max node, guard the zero cap). The maintainers already flagged the #9599/#9607 overlap in review; our read resolves it — push-then-evict is the correct order.Benchmark cross-check (directional — see caveat)
The harness (dgraph-benchmarks#95) compares Dgraph's output to a gonum v0.17
YenKShortestPathsoracle as sorted path-cost vectors, sweepingmaxfrontiersize. On roadCOL (DIMACS Colorado, 435,666 nodes / 1,057,066 edges), 100 banded targets fromgid=1,numpaths=2:%= correct-of-returned;t= timeouts (excluded from denominator).Two things the table does show clearly: (1) the bug reproduces on every branch under a binding cap, and (2) it's eviction-specific — uncapped, results reach 100% correct (37/37 on #9599), climbing 23% → 71% → 100% as the cap rises.
One framing point worth stating so we're all talking about the same thing: a bounded frontier is approximate by design — losing some optimal paths under a tight cap is expected. The #9577 defect is narrower — evicting the wrong element so the optimum is lost far more than a correct bounded eviction would. The correct-eviction PRs (#9599/#9678) fix that; they don't (and can't) make a tight cap lossless.
A note on the existing tests
#9678's integration tests already run k-shortest queries at binding frontiers — nice. But all the PR tests (both #9599's and #9678's) run on ≤5-node hand-built graphs with light assertions (the one binding k-shortest test only checks the optimal weight appears, via
require.Contains, not the full k-cost set). On a 5-node graph the frontier barely binds, so they pass without exercising the failure we see on 400K-node graphs.Worth stressing: the miss isn't size, it's that the tests aren't adversarial — none is built so that a wrong eviction and a correct one produce different answers. You don't need a big graph to reproduce #9577; you need a small hand-built one where (a) the frontier provably exceeds a small
maxfrontiersizeso eviction fires, and (b) the optimum-bearing node is the one buggy eviction discards. A neat way to assert it without an external oracle: pick a frontier that binds but is still large enough that a correct algorithm returns the full answer, then assert capped result == uncapped result on that graph — correct code passes, buggy code fails. That test runs in milliseconds and would go red onmain/#9576/#9607 and green on #9599/#9678.Proposed next steps — your input wanted before we act
capped == uncapped, so it goes red on buggy eviction and green on the fix. Runs in milliseconds, no dataset, safe for CI.Please weigh in — especially @hr-alebel and @veeceey, since we don't want to close your PRs without giving you the chance to point out anything we've missed. Thanks again for all the work on this.
All reactions