Skip to content

[Fusion] Match duplicate lookup variables on raw leaf values - #10614

Open
tobias-tengler wants to merge 13 commits into
mainfrom
tte/fusion-raw-lookup-variable-keys
Open

tobias-tengler wants to merge 13 commits into
mainfrom
tte/fusion-raw-lookup-variable-keys

Conversation

@tobias-tengler

@tobias-tengler tobias-tengler commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Stacked on #10613 (keep duplicate lookup targets as result cursors), which is stacked on #10612. The base branch is tte/fusion-duplicate-lookup-target-cursors, so the diff shows only this change.

To deduplicate lookup variables, the gateway writes the variable JSON of every lookup target and compares it with the variable sets created so far. For the federation benchmark query this means 324 lookup targets but only 7 distinct variable sets, so most targets write and hash their variable JSON only to join a variable set that already exists.

This PR writes a key of the raw requirement values (value kind and raw leaf bytes) in the simple-requirement fast paths and for Apollo representations with a leaf-only shape, and looks it up before writing the variable JSON. A target whose key matches an earlier target joins that variable set without writing or hashing JSON. Requirement values that are not non-null leaves keep the existing JSON comparison.

Method Version Mean Error Ratio Allocated Alloc Ratio
One requirement #10613 138.43 µs 2.474 µs 1.00 952 B 1.00
One requirement PR 114.42 µs 0.815 µs 0.83 952 B 1.00
Three requirements #10613 250.80 µs 57.926 µs 1.00 952 B 1.00
Three requirements PR 164.02 µs 28.699 µs 0.65 952 B 1.00

CreateVariableValueSets for 156 product targets on three nested paths with 5 distinct keys. Variable creation with one requirement is 17% faster, well beyond the error interval, at the same allocations. The three-requirement case moves the same way, but its base run is too noisy to put a number on it.

When one lookup result is merged into a primary target and several additional
targets, the primary target is completed from the source data and, when every
completed value is a non-null, non-enum leaf of an object with the same
selection set, the completed rows are copied into each additional target
instead of completing the same source data again. Targets that do not qualify,
merges with errors and pocketed errors keep the existing completion.
Additional targets of a lookup variable set are kept as cursors into the
result document instead of building a path per target. The merge uses the
cursors directly while no value that references an object or a list was
replaced since the targets were collected, and resolves the target from the
root by its path otherwise. The paths of a CompactPathSegment are created on
first access, so error attribution, telemetry and custom clients keep working.
The simple-requirement fast paths and Apollo representations with a leaf-only
shape write the kind and raw bytes of the requirement values as a key and look
it up before writing the variable JSON. A target whose key matches an earlier
target joins that variable set without writing or hashing its JSON. Values
that are not non-null leaves keep the existing JSON comparison.
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Patch coverage

94.6% of changed lines covered (529/559)

File Covered Changed Patch %
…/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClient.cs 5 6 83.3% 🟡
…/Execution/Results/FetchResultStore.RepresentationValue.cs 40 44 90.9% 🟡
…/src/Fusion.Execution/Execution/Results/FetchResultStore.cs 197 214 92.1% 🟡
…/Fusion/src/Fusion.Execution/Text/Json/ArenaBufferWriter.cs 159 165 96.4% 🟡
src/HotChocolate/Fusion/src/Fusion.Execution/JsonSegment.cs 60 62 96.8% 🟡
…/src/Fusion.Execution/Execution/OperationPlanContext.cs 3 3 100.0% 🟢
…/Execution/Results/FetchResultStore.Pooling.cs 12 12 100.0% 🟢
…/Fusion.Execution/Text/Json/CompositeResultDocument.Text.cs 18 18 100.0% 🟢
…/src/Fusion.Execution/Text/Json/CompositeResultElement.cs 4 4 100.0% 🟢
…/src/Fusion.Execution/Transport/Http/FileEntryBuilder.cs 6 6 100.0% 🟢
…/Utilities/src/Utilities.Buffers/ChunkedArrayWriter.cs 13 13 100.0% 🟢
…/Utilities/src/Utilities.Buffers/MemorySequenceSegment.cs 12 12 100.0% 🟢
Uncovered changed lines (JSON)
{
  "sha": "e1e863bc4357294765a88bb754767f9801714d7a",
  "files": [
    { "path": "src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClient.cs", "ranges": [[525, 525]] },
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.RepresentationValue.cs", "ranges": [[385, 385], [448, 450]] },
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs", "ranges": [[3417, 3418], [3441, 3443], [3453, 3453], [3455, 3456], [3472, 3473], [3489, 3493], [3561, 3562]] },
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/ArenaBufferWriter.cs", "ranges": [[232, 233], [282, 283], [301, 302]] },
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/JsonSegment.cs", "ranges": [[57, 57], [79, 79]] }
  ]
}

Project coverage: 59.2% (313907/530240 lines)

@tobias-tengler
tobias-tengler changed the base branch from main to tte/fusion-duplicate-lookup-target-cursors October 19, 2026 06:44
Base automatically changed from tte/fusion-duplicate-lookup-target-cursors to main October 21, 2026 05:51
Takes main's duplicate lookup target copy (#10612) and target cursors
(#10613) and keeps this branch's raw leaf key matching on top. The element
overload of TryCreateVariableValues reports the dedup index again for the
leaf key registration.
- The leaf key table is initialized only by the requirement fast paths and
  the leaf-shape representation path that use it.
- Clean trims both variable dedup tables when a lookup with many targets
  made them grow.
- The variable set array is rented before the leaf key check, and a missing
  shape property marks the representation key invalid like other misses.
- Tests cover strings and numbers with the same text, nullable null values,
  the two- and three-requirement paths, and leaf-shape representations.
- Documentation states contracts.
@michaelstaib
michaelstaib marked this pull request as ready for review October 21, 2026 10:48
Copilot AI balanced review requested due to automatic review settings October 21, 2026 11:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The variable writer of the result store now writes into the memory arena
of the request, so a pooled store no longer holds a 128 KB chunk between
requests. Each subscription event writes its variables with a new writer
on the event arena, so variables no longer accumulate for the lifetime of
the subscription. Cleaning the store releases the arena memory of the
variable and leaf key writers.
A writer that stays within its first segment no longer rents a segment
table, and the table starts with 16 entries.
Variables written to the request arena can split a JSON token across
segments. The file upload paths of the HTTP transport and the in-memory
client now read such tokens from the value sequence. Trace variable sets
now own a copy of their values, so a trace stays readable after the
result store returns to the pool.
JsonSegment now wraps the sequence of its bytes instead of a writer and
a position. The arena and chunked writers hand out sequences over their
memory, equality and hashing compare bytes, and the entity reuse lookup
compares value sets by content. Traced variable values are copied into
the arena of the plan context, so the trace stays valid when the
variable writer writes its memory again. The result store keeps a
single variable writer again, since segments no longer read through it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants