fix(compact): preserve base-tier adjacency when source node is dirty - #346
Open
temporaryfix wants to merge 1 commit into
Open
fix(compact): preserve base-tier adjacency when source node is dirty#346temporaryfix wants to merge 1 commit into
temporaryfix wants to merge 1 commit into
Conversation
`LayeredStore::edges_from` and `neighbors` short-circuited the base
layer whenever the source node was marked dirty. `ensure_in_overlay`
(the only path that flags a node dirty in this context) copies labels
and properties into the overlay but never copies adjacency, so a
snapshot-tier edge whose endpoint was later touched by any overlay
write — a property update, or merely being the endpoint of an
unrelated new edge — silently vanished from reads.
In GQL this surfaces as property-anchored edge patterns such as
`MATCH (a {id: $x})-[:T]->(b {id: $y})` returning zero rows once any
overlay write has occurred, because the planner may walk from either
anchor and one of them is now dirty.
Drop the `is_node_dirty` guard in both methods. Per-edge deletions
remain authoritative via `deleted_from_base_edges`, and promoted edges
(those whose properties were modified, so they live at the same
`EdgeId` in base and overlay) are still collapsed by the existing
`dedup_by_key(eid)` pass at the end of `edges_from`.
Regression coverage:
* `test_base_edge_visible_from_promoted_endpoint` — new unit test
that promotes both endpoints via unrelated overlay writes and
asserts the original base edge is reachable from both directions.
* `post_compact_property_anchored_edge_survives_unrelated_overlay_write`
— new GQL integration test mirroring the production repro.
* `test_edges_from_dirty_source_merges_layers` and
`test_neighbors_from_promoted_node_with_new_overlay_edges` —
strengthened to assert that base-tier adjacency survives
promotion, not just that the new overlay edge is visible.
All four new/strengthened tests fail on the pre-fix code.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Contributor
Author
|
Would you like me to target the release branch once you have triaged this? |
temporaryfix
added a commit
to temporaryfix/grafeo
that referenced
this pull request
May 15, 2026
Local pickup of the fix proposed upstream as GrafeoDB#346 (issue GrafeoDB#345).
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #345.
Summary
LayeredStore::edges_fromandneighborsshort-circuited the base layer whenever the source node was marked dirty.ensure_in_overlay(the only path that flags a node dirty in this context) copies labels and properties into the overlay but never copies adjacency, so a snapshot-tier edge whose endpoint was later touched by any overlay write — a property update, or merely being the endpoint of an unrelated new edge — silently vanished from reads.In GQL this surfaces as property-anchored edge patterns such as
MATCH (a {id: $x})-[:T]->(b {id: $y})returning zero rows once any overlay write has occurred, because the planner may walk from either anchor and one of them is now dirty.See #345 for the full root-cause analysis, minimal reproduction, and observed impact.
Fix
Drop the
is_node_dirtyguard in both methods. Safety of the change:deleted_from_base_edges.EdgeIdin base and overlay) are collapsed by the existingdedup_by_key(eid)pass at the end ofedges_from.delete_node_edgeswalksbase.load().edges_from(...)directly and was never affected by the guard.sum_of(edges_from(n).len())is now consistent withedge_count()for dirty source nodes (it wasn't before).The minor cost: traversals from a dirty source node now read both base and overlay adjacency rather than just overlay. That's the necessary cost of returning the correct answer.
Regression coverage
test_base_edge_visible_from_promoted_endpoint— promotes both endpoints via unrelated overlay writes and asserts the original base edge remains reachable from both directions, via bothedges_fromandneighbors.post_compact_property_anchored_edge_survives_unrelated_overlay_write— GQL integration test that mirrors the production repro for both promotion directions.test_edges_from_dirty_source_merges_layersandtest_neighbors_from_promoted_node_with_new_overlay_edges— the existing tests asserted only the new overlay edge and explicitly documented the bug in their comments. They now assert the merge behavior their names promise.All four new/strengthened tests fail on the pre-fix code (confirmed by running them against
mainbefore applying the fix).Test plan
cargo test -p grafeo-core --features "compact-store lpg"— 2017 + 27 + 5 + 61 tests pass.cargo test -p grafeo-engine --features "compact-store lpg gql" --test post_compact_overlay_visibility— 6/6 pass (5 existing + 1 new).cargo test -p grafeo-engine --features "compact-store lpg gql"acrosspost_compact_index,post_compact_numeric_content,compact_store_integration,compact_roundtrip_proptest,compact_store_tiered_spill,snapshot,database_api,backward_compatibility,mvcc_integration,versioning_coverage,temporal_properties,savepoint_undo,label_rollback,set_property_rollback,grafeo_file,golden_format,schema_wal_replay,wal_recovery— all pass.query_correctness.rsandmerge_rollback.rs(both call removed/renamed methodsexecute_sql/execute_cypher) are reproducible on cleanmainand are unrelated to this change.🤖 Generated with Claude Code
Summary by cubic
Preserves base-tier adjacency when a node becomes dirty so existing snapshot edges remain visible. Restores correct results for property-anchored edge queries in GQL after overlay writes to either endpoint.
neighborsandedges_from, even when the source node is dirty; merge with overlay, respect per-edge deletions, and dedup byEdgeId.Written for commit 70c9b92. Summary will update on new commits.