Skip to content

[Fusion] Build subgraph HTTP requests before streaming their results - #10633

Closed
tobias-tengler wants to merge 1 commit into
mainfrom
tte/fusion-build-subgraph-request-eagerly
Closed

tobias-tengler wants to merge 1 commit into
mainfrom
tte/fusion-build-subgraph-request-eagerly

Conversation

@tobias-tengler

Copy link
Copy Markdown
Member

Every subgraph request sent by HttpSourceSchemaClient.ExecuteAsync allocates two async iterators: an outer one that builds and sends the request and stores the whole client request and its cancellation token in its state, and a nested one that reads the response.

This PR builds the GraphQLHttpRequest synchronously in ExecuteAsync, the pattern the native batching path already uses, and hands it to a single iterator that sends it, tracks the transport and reads the results. A failure while building the request now throws from ExecuteAsync itself instead of from the first MoveNextAsync; every execution node already calls it inside its try, and the request batching fallback now catches it so it is still recorded as a per-request batch error.

Measured with a gateway benchmark over real HttpSourceSchemaClients whose HttpMessageHandler returns canned responses: ListWithLookup ({ products { id name price } }, a root fetch plus one batched lookup, 2 sends) and SingleFetch ({ products { id name } }, 1 send). BenchmarkDotNet's allocated column was unreliable for this async benchmark (missing cells, about 1 KB between two runs of the same build), so allocations come from a probe that reads GC.GetTotalAllocatedBytes(precise: true) over 20,000 sequential requests per round, 5 rounds per run, two runs per build. Each allocation cell lists the per-round value of the two runs. Both builds include #10632, so the baseline row is main plus that change.

Method Version B/request Mean (median, σ)
ListWithLookup (2 sends) main + #10632 44,096 / 44,064 48.3 µs (1.1)
ListWithLookup (2 sends) PR + #10632 43,280 / 43,312 47.4 µs (1.6)
SingleFetch (1 send) main + #10632 32,016 / 31,984 19.0 µs (0.5)
SingleFetch (1 send) PR + #10632 31,592 / 31,624 19.4 µs (0.6)

The PR allocates 392 B less per subgraph send (784 B with 2 sends, 392 B with 1). Time is unchanged within noise.

HttpSourceSchemaClient.ExecuteAsync now creates the HTTP request
synchronously and hands it to a single async iterator that sends it and
reads the results. This drops the outer iterator that stored the whole
client request and its token twice, and the nested read iterator.
@github-actions

Copy link
Copy Markdown
Contributor

Patch coverage

74.2% of changed lines covered (23/31)

File Covered Changed Patch %
…/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs 23 31 74.2% 🔴
Uncovered changed lines (JSON)
{
  "sha": "d4b5c0ed2dc7143b62f3a42ce78ab9a72732985b",
  "files": [
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs", "ranges": [[127, 130], [283, 286]] }
  ]
}

Project coverage: 59.0% (311549/527844 lines)

@tobias-tengler
tobias-tengler deleted the tte/fusion-build-subgraph-request-eagerly branch October 21, 2026 15:55
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.

1 participant