Skip to content

[Fusion] Fill fully reused entity lookups by row offset - #10657

Merged
michaelstaib merged 21 commits into
mainfrom
tte/entity-reuse-row-copy
Oct 22, 2026
Merged

michaelstaib merged 21 commits into
mainfrom
tte/entity-reuse-row-copy

Conversation

@tobias-tengler

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

Copy link
Copy Markdown
Member

Stacked on #10653, which adds request-scoped entity reuse to Apollo _entities lookups. This PR targets that branch and merges after it.

A lookup whose entities are all satisfied by the request-scoped entity reuse from #10606 sends no request, but it still collects its targets and producers, writes the variable set JSON for both, and fills every reused field by response name.

The duplicate-target leaf copy from #10612 has the same cost: it resolves every completed leaf by response name for each duplicate target.

This PR matches reuse targets to producers on the raw bytes of their key values and fills the reused fields by row offset, so a fully reused lookup writes no JSON, no variable sets and no representations. The fast path covers composite lookups and Apollo _entities lookups (single and batched) alike; a fully reused Apollo lookup completes without writing a representation and its dependents run. The row offsets are resolved once per plan. If any target has no producer, or the keys are not simple leaf values, nothing is written and the existing path runs unchanged, which for Apollo is the partial reuse of #10653. The duplicate-target leaf copy now assigns leaves by row offset as well. Plans, request counts and results are unchanged.

The numbers below were measured before the fast path was extended to Apollo lookups. They stand for the composite change; Apollo Federation was unchanged at that point because its plans had no entity reuse yet, so only the leaf copy applied. The Apollo extension has not been measured separately.

LookupFanOutBenchmark (added here) runs the whole benchmark query through the gateway executor against canned subgraph responses. Mean of two passes (order A B1 B2, then B2 B1 A), main = A, A1 = reuse fast path, A1+A2 = this PR.

Family Version Mean Ratio Allocated
Composite main 301.0 us 1.00 115.55 KB
Composite A1 289.9 us 0.96 113.85 KB
Composite A1+A2 275.5 us 0.92 113.85 KB
Apollo Federation main 319.7 us 1.00 112.25 KB
Apollo Federation A1 316.2 us 0.99 112.26 KB
Apollo Federation A1+A2 306.2 us 0.96 112.32 KB

k6 constant, .NET 10, 30 s warm-up and 3 x 30 s, A B B A. Screen holds (composite hold 2, fed hold 1):

Family Version RPS CPU us/req Alloc KB/req
Composite main 4870 437 35.7
Composite PR 4874 420 33.85
Apollo Federation main 4808 446 42.05
Apollo Federation PR 4773 446 41.9

Confirmation runs (one hold per cell, run CV in parentheses). Cells marked noisy have a run CV above 5% on at least one side and are not conclusive.

Cell Version RPS CPU us/req Alloc KB/req
Composite constant, A B (noisy) main 4557 (6.0%) 442 35.7
Composite constant, A B (noisy) PR 4434 (6.2%) 487 34.0
Composite constant, B A main 4845 (3.2%) 439 35.7
Composite constant, B A PR 4767 (2.4%) 414 33.9
Composite constant-latency, B A main 1967 (0.6%) 504 35.9
Composite constant-latency, B A PR 1974 (2.4%) 474 34.2
Composite burst, A B (noisy, outlier) main 5183 (2.0%) 474 36.5
Composite burst, A B (noisy, outlier) PR 4285 (17.5%) 534 34.6
Apollo Federation constant, B A (noisy) main 4684 (6.9%) 460 41.9
Apollo Federation constant, B A (noisy) PR 4364 (4.5%) 469 41.9

The stable signal is allocation on composite: -4.7% to -5.3% KB/req in every pair. CPU/req is -4% to -6% in the clean cells (screen hold, constant B A, constant-latency); the noisy cells (constant A B, burst, Apollo Federation constant) show no pattern that can be attributed to the change and were not repeated cleanly. RPS does not move because the composite scenario is not gateway-bound at this load. Apollo Federation was unchanged in these runs: its plans had no entity reuse before #10653, so only the leaf copy applied and it is below the noise. Harness noise is about 3% RPS and 1.5% CPU/req for composite, 2% and 1% for Apollo Federation.

Apollo lookups now match targets against producers while the
representations are written, instead of building throwaway variable
value sets and writing the representations a second time.

Add tests for both composite and Apollo lookups that cover batches
where one or every operation is reused, two mutations that change the
same entity, and subscription events that hold the same entity. Request
counts and representations are checked with asserts, only responses
use inline snapshots.
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Patch coverage

85.6% of changed lines covered (370/432)

File Covered Changed Patch %
…/Execution/Results/EntityReuseCandidateMap.cs 47 59 79.7% 🔴
…/Execution/Results/FetchResultStore.Pooling.cs 16 20 80.0% 🟡
…/src/Fusion.Execution/Execution/Results/FetchResultStore.cs 238 278 85.6% 🟡
…/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs 45 51 88.2% 🟡
…/Execution/Results/EntityReuseCandidateList.cs 3 3 100.0% 🟢
…/src/Fusion.Execution/Execution/Results/EntityReuseLayout.cs 10 10 100.0% 🟢
…/Execution/Results/FetchResultStore.RepresentationValue.cs 11 11 100.0% 🟢
Uncovered changed lines (JSON)
{
  "sha": "f145f066a80131e6c69bc714c1d98f8ce0e79c8c",
  "files": [
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/EntityReuseCandidateMap.cs", "ranges": [[326, 327], [333, 334], [344, 345], [349, 350], [370, 371], [386, 387]] },
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.Pooling.cs", "ranges": [[226, 229]] },
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs", "ranges": [[897, 898], [909, 910], [983, 985], [991, 993], [1309, 1310], [1314, 1315], [1324, 1324], [1330, 1331], [1344, 1346], [1365, 1366], [1372, 1373], [1377, 1380], [1404, 1405], [1415, 1416], [1462, 1462], [1464, 1465], [1508, 1509], [1513, 1515]] },
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs", "ranges": [[736, 737], [759, 762]] }
  ]
}

Project coverage: 59.3% (314853/531207 lines)

@tobias-tengler
tobias-tengler force-pushed the tte/entity-reuse-row-copy branch from 0372c9e to 5f99529 Compare October 21, 2026 15:57
@tobias-tengler
tobias-tengler changed the base branch from main to tte/apollo-entity-reuse October 21, 2026 15:58
# Conflicts:
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.RepresentationValue.cs
…e/entity-reuse-row-copy

# Conflicts:
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.Pooling.cs
…e/entity-reuse-row-copy

# Conflicts:
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.RepresentationValue.cs
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs
…e/entity-reuse-row-copy

# Conflicts:
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.RepresentationValue.cs
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs
Base automatically changed from tte/apollo-entity-reuse to main October 22, 2026 12:44
@michaelstaib
michaelstaib marked this pull request as ready for review October 22, 2026 12:45
…copy

# Conflicts:
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.Pooling.cs
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.RepresentationValue.cs
#	src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs
@michaelstaib
michaelstaib merged commit 9c5cfa3 into main Oct 22, 2026
157 checks passed
@michaelstaib
michaelstaib deleted the tte/entity-reuse-row-copy branch October 22, 2026 13:12
This was referenced Oct 25, 2026
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.

2 participants