Repository navigation
feat(storage,api): persisted property-equality index for node id anchors (#1983) - #1998
Conversation
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. 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. Comment |
This comment has been minimized.
This comment has been minimized.
…e anchors (#1983) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…y-equality index (#1983) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… encoded rows (#1983) Classify GraphForge.rebuild_property_indexes in the non-Cypher surface inventory (release-tested, Rust facade; no Python or Node member) and refresh the frozen digests and counts. Classify indexes/property/ as properties in storage attribution. Collect the index rows while the property fragments are encoded and write the index files once as artifacts of the same generation, instead of re-reading the published fragments (which copied multi-object fragments to scratch and broke the write-once accounting). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
72ee8a0 to
99ccb74
Compare
Closes #1983
What
A persisted property-equality index so a node anchor
(:L {id: $x}), orWHERE n.id = $xon one label, resolves its candidate node UUIDs from the index instead of reading the route's property column.crates/graphforge-storage/src/property_equality_index.rs: manifest (indexes/property/<stem>.eq: XXH64 line then JSON) plus sorted Arrow IPC shards of 4096(value, node_uuid)rows (<stem>.shards/*.eqs). A lookup binary-searches the manifest and reads only the shards whose range holds the value, each checked against the manifest XXH64. Bytes are a pure function of the route content (no clock or identity).property_readers.rs) takes candidates from the index when a current one exists, else from the existing footer-statistics path. The plan's filter still decides each row, so semantics are unchanged.EXPLAIN(physical plan) names it:PropertyOverlayExec: route=_untyped, equality=id=Int(5), index=property_equality(id).seal_and_publish, import session onto an empty project; in-memory and durable) writes the index inside the same generation, registered in the generation inventory.GraphForge::rebuild_property_indexesrebuilds from the current generation and publishes a new one.graph_admission::classify); the lookup checks the manifest checksum and every shard it reads. A failure returnsGF_VALIDATION; the scan is not substituted and nothing is rebuilt by a read.Decisions
id(Int64 or Utf8 stored type). The import session / load mapping has no key declaration (id_columnis a benchmark-converter notion), soidonly.rebuild_property_indexesbuilds it. Covered bya_project_without_the_index_scans_until_rebuilt_and_after_a_write(durable, reopen, write, rebuild).is_stored_equality; DataFusion rewritesid = truetoid) and still use the scan.property_scan.rs(Display and anis_currentcheck at construction),property_overlay/inventory.rs(+ newroute_digest.rs, because inventory.rs was at the source-size bound),graph_admission.rs(classification),graph_construction_encoding/bulk.rs(one call before the adjacency registration). Not touched:session.rs,expand_exec.rs,property_filter_approval.rs,left_enrichment.rs.Verification (host load 15 to 40, shared)
cargo nextest run -p graphforge-api --test property_equality_index --test property_read_pushdown: 23 passed, 0 failed (8 new). Covers: index in the generation inventory and named by EXPLAIN (literal and WHERE); read bytes bounded at 1/2/4 partitions with 12,288 and 24,576 nodes (bound2*largest_fragment + 1 MiB, growth under 256 KiB, a miss reads under 1 MiB); unindexed (Float) property scans and answers; in-memory project; string ids exact, boolean ids exact; pre-index durable project scan / rebuild / reopen / write / rebuild; byte-identical index across two builds; corrupted shard and corrupted manifest each refused withGF_VALIDATIONwhile a non-index statement still runs.lookupreturningOk(None),an_anchored_lookup_reads_the_match_not_the_labelfails:partitions=1, 6 fragments, largest 2094115: read 6521862 bytes, bound 5236806. With the index it passes.cargo test -p graphforge-api --test bdd: API BDD 118 passed, 0 failed; openCypher TCK 3898 of 3898 passing (0 regressed).cargo nextest run -p graphforge-storage: 2024 tests, all passed (the one long RSS test was re-run alone: passed).make check: exit 0.Measurement
See the comment on #1983: the index is used and the bare anchor drops from about 2.0 s to about 0.7 s on SF1, but IC2, IC4 and TCR1 do not move because their floor is elsewhere (node-topology label filter and enrichment join, owned by #1984).
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.