Skip to content

perf(graph-db): store relation identities once, on the locator - #2268

Merged
ScriptedAlchemy merged 1 commit into
masterfrom
fleet/graph-edge-identity
Sep 26, 2026
Merged

ScriptedAlchemy merged 1 commit into
masterfrom
fleet/graph-edge-identity

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Refs #1103. Continues #2257. Relation identities are now stored once, on the locator node, in a compact form. The sealed graph store shrinks by 15–27% at the same base, and retrieval results are unchanged.

What was wrong

Encoding

field before (format 3) after (format 4)
locator RELATION_ID / _FROM / _TO verbatim string, 71 bytes each for symbol:<64 hex> U+0001 ‖ kind ‖ base64url(digest), 50 bytes for a symbol id and 48 for an edge id; any other identity stays verbatim
native edge RELATION_ID / _FROM / _TO a second verbatim copy removed; the edge resolves its locator through the existing RELATION_EDGE unique index and reads those columns from it
unique keys (entity, relation, projection, publication, quarantine) Bytes (47 B), stored as 102 B of marked hex base64url text of the same binary key, 63 B
example: code-graph target relation (edge entity → symbol) identity strings 211 B on the locator + 211 B on the edge = 422 B, plus a 102 B key 148 B on the locator and 0 on the edge, plus a 63 B key
  • One owner per identity. Traversal, fan-outs, and the key walk read the locator's identity scalars with get_node_property, a single-column read with no node materialization. Owner scalars and payload stay on the edge. Relation lookups keep their endpoint checks: source and target come from the locator and are verified against native adjacency, as before.
  • Unambiguous decode. validate_opaque now rejects graph identifiers that start with U+0001, so a compact identity can never be confused with a verbatim one. Only the canonical lowercase spelling compacts, so decoding restores the exact identity string.
  • Order and digest inputs are unchanged. Projection paging, deletion pages, and sealed and recovered proofs still sort by the decoded identity strings. The recovered digest hashes the same decoded rows. None of the daemon: initial index of this repository is OOM-killed under a 6 GB memory cap #2123 row-source work (f1f15ed9) had merged when this was rebased (14caed2d7d).

Format revision: graph-db 3 → 4, with the same typed FormatSuperseded rebuild: the old container and its sealed generations are deleted and republished from source. Nothing is converted.

Bytes per store per source byte

Base is master 6554ff1901. After is this branch at the same base. Both use the perf CLI, a fresh isolated profile, and one capped daemon: 6 GB, or 12 GB with 4 workers for the clone. Source is graph_statistics.source_total_bytes.

corpus (source) graph store before graph store after text artifact segments profile before → after
768-file journey (6,796,827 B) 194.5 MB (28.62×) 165.9 MB (24.40×), −14.7% 122.4 MB (18.02×), unchanged 17.8 MB (2.62×), unchanged 344.1 → 317.3 MB (50.62× → 46.69×)
rsbuild (2,110,240 B) 34.3 MB (16.24×) 26.4 MB (12.51×), −22.9% 23.8 MB (11.28×), unchanged 5.9 MB (2.81×), unchanged 76.9 → 69.1 MB
starship (11,543,857 B) 122.6 MB (10.62×) 89.4 MB (7.75×), −27.1% 51.6 MB (4.47×), unchanged 8.2 MB (0.71×), unchanged 191.8 → 158.6 MB
repo clone (94,242,814 B) 1,540.3 MB (16.34×) 1,136.5 MB (12.06×), −26.2% 513.8 MB (5.45×), unchanged 113.5 MB (1.20×), unchanged 2,194.1 → 1,790.3 MB (23.28× → 19.00×)

Clone peak memory fell from 12.41 GB to 11.42 GB, and time to ready from 486 s to 412 s.

Identical results

This uses the #2185 fixture (search, context, callers, callees per symbol), driven through the CLI. Per-request fields and the profile-keyed generation id are normalized. Cost receipts are compared verbatim.

corpus result against same-base master
rsbuild 80/80 identical
repo clone 80/80 identical
starship 78/78 identical (20 symbols, 2 with no graph node)
768-file journey, after the post-activation warm (both sides) 80/80 identical
768-file journey, fresh run 79/80: the first query's context graph lane raced the post-activation warm (a known race, see #2257)
upgrade in place 80/80 identical to warm master responses

Upgrade in place: a j768 profile built by the master binary was restarted with this binary. It reached ready in 15 s, and status shows fresh. The typed rebuild republished from source (log: graph generation has neither a complete staged row set nor a usable sealed artifact; republish from the canonical manifest). The new sealed store is 165,868,823 B, byte-identical in size to a fresh index. The code-index artifacts (segments, text artifact, manifests) are byte-identical, and only graph files differ.

#2224 receipt. Every page of starship src/modules/mod.rs::handle callees (depth 1, no dispatch) is identical to master: 112 rows over 12 pages, 11 graph_sealed point reads per page, 2 adjacency queries and 226 rows per page, and the same bytes_hydrated on every page.

Tests: fail on master, pass here

To check that these fail on master, this commit's graph-db src and Cargo.toml were checked out at origin/master, and the tests below were run against them.

  • sealed_generation_bytes_stay_within_the_compact_identity_budget: a 2,000-symbol sealed generation with stable identities, published through the production path, must be at most 1,150,000 B, and it resolves through keys and edges. Master: 1,786,870 B (fails). Here: 1,037,302 B.
  • superseded_format_store_is_rebuilt_fresh_with_its_sealed_generations_discarded: a format-3 store, with a format-3 binary-keyed row next to a sealed generation, is rebuilt fresh. Master: opens it as current (fails).
  • compact_relation_identities_read_back_through_keys_and_edges: a raw format-4 store with a literal base64url key, compact locator identities, and an identity-free native edge. relation, outgoing_relations, and outgoing_relation_ids all return the literal relation. Master: refuses format 4 (fails).
  • persisted_scalar_identity_mismatch_is_corrupt_on_point_read now writes a literal base64url key (master fails). A schema unit test pins the compact form of graph_stable_identity("edge", "occ") (48 bytes) and its exact round trip, plus verbatim storage of non-canonical identities.

Checks (rebased onto 14caed2d7d, --profile perf)

suite passed
graph-db lib 143
graph_db_suite 150
code-index lib 258
code_index_suite 169
resident_accounting 1
code-index-runtime lib 516
query lib 263
search_quality_suite 71
canonical_execution_equivalence 4
retrieval_contract_spine 2
store-runtime lib 122
session-temporal-store lib 150

cargo clippy -p tracedecay-graph-db --all-targets --features test-helpers -- -D warnings is clean. It is the only crate this slice touches. cargo fmt --all -- --check is clean.

What remains above ~10× source

The clone graph store is still 12.1× source and j768 is 24.4×. These are estimates from row counts, not a column census:

  • Reified code edges. Each of the clone's 563k code edges is an edge: entity carrying its JSON CanonicalRelationEdgeV1 record, plus two relations (source, target), each a locator node and a native edge. That is about 1.13M locators for code edges alone, roughly 250 B each (key, identity, endpoints, dictionary codes), plus the edge entity rows.
  • JSON record payloads. Symbol records carry binding, lineage metadata, and unresolved calls, with 64-hex digests and occurrence ids spelled out in JSON. This is all of j768, which has 98k symbols and 0 edges (about 1.7 KB per symbol).
  • Entity id and key. Each entity carries its ENTITY_ID (71 B) plus its key (63 B).

Every relation stored its identity, source and target as 71-byte strings
twice: on its locator node and again on its native edge. The locator now
owns them. The edge keeps only its owner scalars and payload, and edge
reads resolve the locator through the existing RELATION_EDGE unique index
and read single columns from it.

A `<kind>:<64 lowercase hex>` identity is stored as U+0001, its kind and
its digest in unpadded base64url (50 bytes for a symbol id); any other
identity is stored verbatim, and graph identifiers may no longer start
with U+0001. Unique keys become base64url text of their binary form. Both
stay strings because the sealed compact store keeps `Bytes` values in its
string dictionary as marked hex, so the binary keys from #2257 took 102
bytes on disk, not 47; they now take 63.

Graph format 3 -> 4 with the same typed `FormatSuperseded` rebuild.
Paging and proofs still order by the decoded identity strings.

Refs #1103
@changeset-bot

changeset-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 97de4c3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T20:37:07.634808Z 97de4c3 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ScriptedAlchemy
ScriptedAlchemy merged commit fd9fd06 into master Sep 26, 2026
1 check passed
@ScriptedAlchemy
ScriptedAlchemy deleted the fleet/graph-edge-identity branch September 26, 2026 20:33

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97de4c3efd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +702 to +705
let mut locators = store
.find_nodes_by_property(RELATION_EDGE_PROPERTY, &value)
.into_iter()
.filter(|node| store.get_node_property(*node, &key).as_ref() == Some(&value));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate locator ownership before returning edge identities

If a persisted locator's namespace or projection scalar is corrupted while its RELATION_EDGE value and endpoint identities remain intact, this lookup accepts it solely by edge ID. Traversal and fan-out validate ownership on the native edge but then return the identity from this unchecked locator, so they can successfully expose a foreign or mismatched relation instead of reporting GraphDbError::Corrupt. Verify the locator label and ensure its namespace, projection, and kind agree with the edge before using its identity.

AGENTS.md reference: AGENTS.md:L189-L191

Useful? React with 👍 / 👎.

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