Graphql N+1 Triage: FetchProjectContainerRepositories

Summary

FetchProjectContainerRepositories ranked among the top N+1 candidates on gitlab.com, based on 24 hours of production log data. It's estimated to cost 20.5 avoidable queries per minute. See https://gitlab.com/groups/gitlab-org/-/epics/23614 for how this list was built and the method to follow.

Numbers

Metric Value
Requests in 24h 2,961
N+1 risk 0.95
p95 queries on main replica 422
p95 DB duration (s) 0.28
Estimated savings per minute 20.5

Where the query comes from

No .graphql file in gitlab-org/gitlab defines this operation, so it comes from an external client (IDE extension, CLI, or a third-party API user). Reconstruct the selection from json.graphql.used_fields in Kibana.

Primary type read, inferred from the operation name: ContainerRepository. Owning group: group::container registry.

Reconstructed selection

Rebuilt from json.graphql.used_fields of one request sampled on 2026-09-24 (3,169 requests that day). The log lists fields per type, not per position, so a field used on a type shows up under every place that type appears, and arguments and fragments are lost.

query FetchProjectContainerRepositories {
  project {
    containerRepositories {
      nodes {
        id
        name
        path
        location
        createdAt
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

What to do

  1. Find the query source: the .graphql file in the repo, or json.graphql.used_fields in Kibana for external clients.
  2. Write a throwaway request spec, run it against a small and a larger page, and record queries with ActiveRecord::QueryRecorder.new(skip_cached: false).
  3. Fix per-row growth with lookahead preloads, BatchLoader::GraphQL, or a policy preloader in before_connection_authorization.
  4. Add a permanent N+1 spec that asserts query counts don't grow with page size.
  5. After deploy, confirm the savings estimate dropped in the Lens table.

Acceptance criteria

  • Per-row queries identified and listed in this issue.
  • Fix merged.
  • Permanent N+1 spec added.
  • Savings in the Lens table dropped after deploy, or this issue is closed with an explanation of why the spread is fixed cost.
Edited by Eduardo Bonet