Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] incremental delivery without branching #1023

Closed
wants to merge 63 commits into from

Conversation

yaacovCR
Copy link
Contributor

@yaacovCR yaacovCR commented Mar 24, 2023

These spec edits should correspond to the working implementation at graphql/graphql-js#3862 demonstrating incremental delivery without branching.

[The diff to main might be helpful, but this is built on top of the amazing https://github.com//pull/742 and so the diff from that branch could be more useful.]

Other efforts are in the works to avoid branching, specifically #1018 and #1020. As far as I can tell, the main distinction of the approach taken by this PR is that the implementation and spec changes show how one can start executing deferred fragments semi-immediately (i.e. after deferring in an implementation-specific way), rather than waiting for the entire initial result to be emitted. This is not required -- one could still be compliant with the spec by deferring all the way until the initial result completes! In fact, how one defers is not per se observable and so the spec cannot mandate much about it with great normative force. But -- and I think this is important -- this PR and the implementation PR provide an algorithm/implementation/spec changes that give servers the flexibility to do what they think is right in that regard, and that might be desirable.

As of this moment, I am fairly confident in the implementation PR over at graphql-js, and the spec PR should generally correspond, demonstrating:

  • the Field Group, Defer Usage, and Stream Usage record types that contain the complex information derived from the operation during field collection
  • the new Publisher construct, which keeps track of when to release payloads
  • the change from a single parent Async Payload record to potentially multiple parents
  • the deferMap, which maps Defer Usage records to individual Deferred Fragment Async Payload records
  • the tracking mechanism by which Deferred Fragment records are notified as complete, namely the AddPendingDeferredField and ReportDeferredValue algorithms

Deduplication and Payload Format

The implementation and these spec edits do not currently included deduplication, and the payload format is the same as the deferred fragment, but that can be easily changed as per below.

TLDR: it can be easily changed.

Adding deduplication

The algorithm includes events and handlers corresponding to:

  1. the unique completion of each fields
  2. the successful completion of each overall deferred fragment at a given path.

On events of type 1, fields are forwarded to each deferred fragment, and on events of type 2, the fragment including potentially duplicate values is published. We can easily change the algorithm to do something different in response to each event, without the use of WeakMap or any sort of long-lived cache.

For example:

  • On events of type 1, we could immediately publish the field values
  • On events of type 2, we could publish a completion notice for the fragment

Or, potentially:

  • On events of type 1, we could add the field to a list of values ready to be sent -- once the first payload completes.
  • On events of type 2, we could send a completion notice for the fragment and release any pending field values associated with the fragment.

Or, we could do something more complicated:

We could modify the field group collection algorithm to defined "building blocks" or subset of the deferred fragments that will combine in a predictable way no matter the order of deferred fragment completion.

  • On events of type 1A, field completion, we could notify the "building block" that a field is ready.
  • On events of type 1B, "building block" completion, we could notify the deferred fragment that a building block is ready
  • On events of type 2, all "building blocks" for a field are ready, we could send a completion notice for the fragment and release any pending "building blocks" associated with the fragment.

** huge thanks to @Urigo and @dotansimha of the guild for sponsoring my open-source work on this. **

robrichard and others added 30 commits January 15, 2023 12:15
Update Section 3 -- Type System.md

Update Section 3 -- Type System.md

Update Section 3 -- Type System.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Update Section 6 -- Execution.md

Amend changes

change initial_count to initialCount

add payload fields to Response section

add stream validation for overlapping fields

spelling updates

add note about re-execution

add note about final payloads

label is optional

fix build

Update ExecuteQuery with hasNext logic

fix spelling

fix spaces

Update execution to add defer/stream to mutations and subscriptions

clarify stream records

Apply suggestions from code review

Co-authored-by: Benjie Gillam <benjie@jemjie.com>

missing bracket

Update spec/Section 7 -- Response.md

Co-authored-by: Benjie Gillam <benjie@jemjie.com>

clarify line about stream record iterator

update visitedFragments with defer

Updates to consolidate subsequent payload logic for queries, mutations, and subscriptions

Apply suggestions from code review

Co-authored-by: Benjie Gillam <benjie@jemjie.com>

address review feedback

Add handling of termination signal

more formatting

fix spelling

Add assertion for record type

add "Stream Directives Are Used On List Fields" validation rule

Add defaultValue to @stream initialCount

Update spec/Section 5 -- Validation.md

Co-authored-by: Benjie Gillam <benjie@jemjie.com>

# Conflicts:
#	spec/Section 3 -- Type System.md
#	spec/Section 5 -- Validation.md
#	spec/Section 6 -- Execution.md
#	spec/Section 7 -- Response.md
# Conflicts:
#	spec/Section 6 -- Execution.md
# Conflicts:
#	spec/Section 3 -- Type System.md
# Conflicts:
#	spec/Section 3 -- Type System.md
# Conflicts:
#	spec/Section 3 -- Type System.md
# Conflicts:
#	spec/Section 3 -- Type System.md
# Conflicts:
#	spec/Section 7 -- Response.md
# Conflicts:
#	spec/Section 7 -- Response.md
# Conflicts:
#	spec/Section 3 -- Type System.md
#	spec/Section 6 -- Execution.md
# Conflicts:
#	spec/Section 7 -- Response.md
# Conflicts:
#	spec/Section 6 -- Execution.md
# Conflicts:
#	spec/Section 6 -- Execution.md
# Conflicts:
#	spec/Section 6 -- Execution.md
…ators

# Conflicts:
#	spec/Section 6 -- Execution.md
robrichard and others added 12 commits January 15, 2023 12:15
* add comma

* remove unused parameter
* streamline stream execution

Currently, these spec changes introduce a new internal function named `ResolveFieldGenerator` that is suggested parallels `ResolveFieldValue`. This function is used  during field execution such that if the stream directive is specified, it is called instead of `ResolveFieldValue`.

The reference implementation, however, does not require any such function, simply utilizing the result of `ResolveFieldValue`.

With incremental delivery, collections completed by `CompleteValue` should be explicitly iterated using a well-defined iterator, such that the iterator can be passed to `ExecuteStreamField`. But this does not require a new internal function to be specified/exposed.

Moreover, introducing this function causes a mixing of concerns between the `ExecuteField` and `CompleteValue` algorithms; Currently, if stream is specified for a field, `ExecuteField` extracts the iterator and passes it to `CompleteValue`, while if stream is not specified, the `ExecuteField` passes the collection, i.e. the iterable, not the iterator. In the stream case, this shunts some of the logic checking the validity of resolution results into field execution. In fact, it exposes a specification "bug" => in the stream case, no checking is actually done that `ResolveFieldGenerator` returns an iterator!

This change removes `ResolveFieldGenerator` and with it some complexity, and brings it in line with the reference implementation.

The reference implementation contains some simplification of the algorithm for the synchronous iterator case (we don't have to preserve the iterator on the StreamRecord, because there will be no early close required and we don't have to set isCompletedIterator, beacuse we don't have to create a dummy payload for termination of the asynchronous stream), We could consider also removing these bits as well, as they are an implementation detail in terms of how our dispatcher is managing its iterators, but that should be left for another change.

* run prettier
* fix whitespace

* complete renaming of initialItems
* Add error handling for stream iterators

* also add iterator error handling within CompleteValue

* incorporate feedback
Co-authored-by: Simon Gellis <82392336+simongellis-attentive@users.noreply.github.com>
@netlify
Copy link

netlify bot commented Mar 24, 2023

Deploy Preview for graphql-spec-draft ready!

Name Link
🔨 Latest commit 6fc43df
🔍 Latest deploy log https://app.netlify.com/sites/graphql-spec-draft/deploys/646a166f236bd70008c3c4e1
😎 Deploy Preview https://deploy-preview-1023--graphql-spec-draft.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@yaacovCR yaacovCR force-pushed the without-branching branch 2 times, most recently from 4b26cbb to 9199250 Compare March 27, 2023 13:39
@yaacovCR yaacovCR marked this pull request as ready for review March 27, 2023 13:40
@yaacovCR
Copy link
Contributor Author

The big TODO is:

https://github.com/yaacovCR/graphql-spec/blob/6fc43df3c0014d6bd0ebe3267a00f96e500f3677/spec/Section%206%20--%20Execution.md?plain=1#L1252

...where i sort of wave my hand and say we should just build the payload from the reported results, but that's kind of because how we report the results (as part of the payload, i.e. with duplication, or separately, i.e. with deduplication) is in flux.

@yaacovCR yaacovCR force-pushed the without-branching branch 2 times, most recently from cd34dd0 to 6fc43df Compare May 11, 2023 09:16
@yaacovCR yaacovCR changed the title [Pre-RFC] incremental delivery without branching [RFC] incremental delivery without branching May 21, 2023
@yaacovCR
Copy link
Contributor Author

closing in favor of #1026

@yaacovCR yaacovCR closed this May 21, 2023
@benjie benjie added the 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) label Nov 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants