Skip to content

[GraphQL] Lookahead optimizations for merge requests

Alex Kalderimis requested to merge ajk-gql-lookahead into master

What does this MR do?

This MR is a working proof of concept for lookahead optimizations in GraphQL resolvers, addressing N + 1 performance issues. Tests indicate significant ~performance gains, with reductions in query counts in the tested query below from 47 to 20, a nearly 60% reduction in query count. Much higher reductions would be anticipated in typical queries in real life scenarios:

query($fullpath: String!) {
  project(fullPath: $fullpath) {
    mergeRequests(first: 10) {
      nodes {
        assignees { nodes { username } }
        headPipeline { status }
      }
    }
  }
}

This eliminates N+1 problems in this case.

This is not an automatic transformation - if applied thoroughly it would require looking over all our resolvers and types and finding opportunities for such improvements. We could automate the search provide candidates if needed.

How does this work

This MR adds a new GraphQL resolver concern: LooksAhead, which helps with applying lookahead preloading optimisations.

This requires that the resolver include LooksAhead, and then make use of with_lookahead and apply_lookahead, providing unconditional_includes and preloads:

Example:

class MyResolver < BaseResolver
  include LooksAhead

  # Rather than defining `resolve(**args)`, we implement: `resolve_with_lookahead(**args)`
  def resolve_with_lookahead(**args)
    apply_lookahead(MyThingFinder.new(current_user).execute)
  end

  # We list things that should always be preloaded:
  # For example, if child_attribute is always needed (during authorization
  # perhaps), then we can include it here.
  def unconditional_includes
    [:child_attribute]
  end

  # We list things that should be included if a certain field is selected:
  def preloads
    {
        field_one: [:other_attribute],
        field_two: [{ nested: [:included_attribute] }]
    }
  end
end

The final thing that is needed is that every field that uses this resolver needs to advertise the need for lookahead:

  field :my_things, MyThingType.connection_type, null: true,
        extras: [:lookahead], # Necessary
        description: 'Find my things'

apply_lookahead uses the lookahead functionality in the framework to ActiveRecord::Relation#preload the required associations as needed.

Screenshots

Does this MR meet the acceptance criteria?

Conformity

Availability and Testing

This requires tests to verify performance impact, and that correctness is preserved.

Edited by 🤖 GitLab Bot 🤖

Merge request reports