Attribute job analytics ClickHouse queries

What does this MR do and why?

Instruments the CI job analytics ClickHouse query so FinOps can attribute ClickHouse cost per root namespace. Attribution rides on the query log_comment, which carries root_namespace_id from the application context.

The wrinkle: the job analytics resolver does not execute the query. It returns a ClickHouseAggregatedRelation and defers execution to the GraphQL pagination connection. By execution time the resolver's local scope and any with_context block are gone, so the application context attributes have to be carried on the relation itself and applied at execution time.

Changes

Three edits:

  1. lib/gitlab/graphql/pagination/click_house_aggregated_relation.rb: the SimpleDelegator subclass now takes a generic context_attributes: keyword in its constructor (default {}) and exposes it with attr_reader :context_attributes. The relation does not know about namespaces; it only carries Gitlab::ApplicationContext attributes for the deferred query. A real reader takes precedence over delegation, so pagination's delegated calls are unaffected. The default keeps the other caller, UserMetricsResolver (ClickHouseAggregatedRelation.new(result.payload)), unchanged.
  2. app/graphql/resolvers/ci/job_analytics_resolver.rb: resolve builds the relation with context_attributes: { namespace: project.project_namespace }. project_namespace is a namespace record that is always present and has traversal_ids, so root_namespace_id resolves reliably.
  3. lib/gitlab/graphql/pagination/click_house_aggregated_connection.rb: in execute_query (which this class already overrides), read items.context_attributes; if present, wrap the super call in Gitlab::ApplicationContext.with_context(context_attributes), otherwise call super unwrapped. Reading from items (the original relation) rather than the query argument is deliberate: pagination calls .having/.where/.limit which return a plain QueryBuilder without the reader, so only items still carries it. Both the first and last pagination paths go through execute_query, so every pagination query is attributed. with_context raises ArgumentError on unknown attribute keys, so a typo fails loudly instead of silently dropping attribution.

Behavior and cost note: This change affects attribution only. Results are unchanged, and it adds no database queries. The resolver reads project.project_namespace, which Ci::JobAnalytics::QueryBuilder#execute already loads earlier in the same resolve call to scope the ClickHouse query by traversal_path. I measured this locally with ActiveRecord::QueryRecorder on the project(fullPath:) { jobAnalytics } request, using session and token auth with job_analytics_siphon on and off, and each request made exactly one project_namespace query, issued by existing code.

No feature flag (read-only, low risk). No Changelog trailer (internal observability).

References

Screenshots or screen recordings

N/A - backend only, no UI changes.

How to set up and validate locally

  1. Run the job analytics request spec (request specs need VITE_ENABLED=true):

    VITE_ENABLED=true bundle exec rspec spec/requests/api/graphql/project/job_analytics_spec.rb

    Expected: 103 examples, 0 failures. Includes new examples asserting every executed ClickHouse query's log_comment in the paginated request (both forward and backward pagination) carries the project root_namespace_id.

  2. Run both pagination connection specs:

    bundle exec rspec spec/lib/gitlab/graphql/pagination/click_house_aggregated_connection_spec.rb spec/lib/gitlab/graphql/pagination/click_house_connection_spec.rb

    Expected: 51 examples, 0 failures. The aggregated connection spec has #execute_query unit tests: one asserts a subgroup attribution namespace resolves to the root group id, one asserts that with no attribution namespace the query runs without a root_namespace_id key. The plain connection spec proves the non-aggregated path is unaffected.

  3. Run the resolver spec:

    bundle exec rspec spec/graphql/resolvers/ci/job_analytics_resolver_spec.rb

    Expected: 5 examples, 0 failures.

  4. Run the AI user metrics request spec, which exercises the other ClickHouseAggregatedRelation caller with the default context_attributes (request specs need VITE_ENABLED=true):

    VITE_ENABLED=true bundle exec rspec ee/spec/requests/api/graphql/analytics/ai_analytics/ai_user_metrics_spec.rb

    Expected: 47 examples, 0 failures.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Narendran

Merge request reports

Loading
Loading