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

Feature: Execution observer #871

Open
Diggsey opened this issue Feb 8, 2021 · 0 comments
Open

Feature: Execution observer #871

Diggsey opened this issue Feb 8, 2021 · 0 comments
Labels
enhancement Improvement of existing features or bugfix

Comments

@Diggsey
Copy link

Diggsey commented Feb 8, 2021

Is your feature request related to a problem? Please describe.

To avoid the N+1 problem, solutions like juniper_eager_loading and dataloader exist. Arguably, dataloader is easier to use, but it comes with a problem:

How does one determine when to actually issue the queries?

The solution currently in place is to just "yield" a fixed number of times, and hope that all the queries have been executed by that time. This is dangerous, because it will typically work for toy projects, but as queries get more complex, you could exceed the number of yields and suddenly lose all benefit from the dataloader.

Furthermore, yielding many times has a performance cost. Ideally the dataloader could be explicitly flushed when we're done issuing queries.

Describe the solution you'd like

There are a few possible approaches, but they all follow the same basic pattern:

  1. Juniper provides hooks to know when certain stages of query execution are complete.
  2. User code can listen for these hooks, and explicitly issue "flush" commands to their database layer in response.
  3. The "flush" command causes a whole batch of results to be fetched in one go.

There are a few different options for (1) so I'd suggest adding several different hooks via an "observerability" layer, and then allowing people to do with it as they will. Some examples:

  1. An "idle" hook. This would trigger whenever no more "resolve" requests can be issued until at least one of the ones currently in progress completes. This could occur multiple times per execution, since you might need to wait for previous values to be resolved before starting to resolve nested values.

  2. A "layer complete" hook. This would trigger whenever all "resolve" operations have been kicked off for a given depth. For example, after calling "resolve" on the root, it would trigger "layer 0 complete". Once resolution of all fields on the root has been requested, it would trigger "layer 1 complete", and so on, for however deeply nested the query goes. This would be combined with a mechanism within "resolve" to access the current layer: when the hook is received, all database requests for that specific layer can be flushed.

The advantage of (1) is that it's simple and easy to understand. The downside is that it can result in non-deterministic and timing-sensitive queries. In contrast, (2) is a bit more complicated, but is fully deterministic: for a given GraphQL query, you can be sure that the exact same database queries will be issued.

Describe alternatives you've considered

It's difficult to detect "Idleness" without assistance from juniper, as other asynchronous tasks could interfere. The only other option is to stick with "juniper_eager_loading".

@Diggsey Diggsey added the enhancement Improvement of existing features or bugfix label Feb 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improvement of existing features or bugfix
Projects
None yet
Development

No branches or pull requests

1 participant