InfoGrab Docs

집계 프레임워크(Aggregations Framework)

요약

집계 프레임워크는 서로 다른 데이터베이스 백엔드에서 분석 쿼리를 구성하기 위한 통합 인터페이스를 제공합니다. ActiveRecord 엔진(Gitlab::Database::Aggregation::ActiveRecord::Engine)은 ActiveRecord의 쿼리 인터페이스를 사용하여 PostgreSQL 쿼리를 생성합니다.

집계 프레임워크는 서로 다른 데이터베이스 백엔드에서 분석 쿼리를 구성하기 위한 통합 인터페이스를 제공합니다. PostgreSQL(ActiveRecord를 통한)과 ClickHouse를 모두 지원하며, 개발자가 메트릭, 차원, 필터가 있는 재사용 가능한 집계 엔진을 정의할 수 있도록 합니다.

ActiveRecord 엔진 정의#

ActiveRecord 엔진(Gitlab::Database::Aggregation::ActiveRecord::Engine)은 ActiveRecord의 쿼리 인터페이스를 사용하여 PostgreSQL 쿼리를 생성합니다.

ActiveRecord 엔진 예시#

class IssueAggregationEngine < Gitlab::Database::Aggregation::ActiveRecord::Engine
  filters do
    exact_match :project_id, :integer, description: 'Filter by project ID'
    exact_match :state, :string, description: 'Filter by issue state'
  end

  dimensions do
    column :author_id, :integer, description: 'Group by author'
    date_bucket :created_at, :datetime,
      parameters: { granularity: { in: %i[daily weekly monthly yearly], type: :string } },
      description: 'Group by creation date'
  end

  metrics do
    count description: 'Total number of issues'
    mean :weight, :float, description: 'Average issue weight'
  end
end

ActiveRecord 엔진은 단일 수준 SQL 쿼리를 생성합니다:

SELECT
  "issues"."author_id" AS aeq_author_id,
  date_trunc('month', "issues"."created_at") AS aeq_created_at,
  COUNT(*) AS aeq_total_count,
  AVG("issues"."weight") AS aeq_mean_weight
FROM "issues"
WHERE "issues"."project_id" IN (1, 2, 3)
  AND "issues"."state" IN ('opened')
GROUP BY aeq_author_id, aeq_created_at
ORDER BY aeq_author_id, aeq_created_at

주요 특징:

  • 모든 컬럼은 aeq_ (Aggregation Engine Query)로 접두사가 붙습니다. 이 접두사는 AggregationResult 객체에 의해 제거됩니다.
  • 필터는 WHERE 또는 HAVING 절로 적용됩니다
  • 차원은 GROUP BY 컬럼이 됩니다
  • 메트릭은 집계 함수(COUNT, AVG)를 사용합니다

사용 가능한 구성 요소#

count 메트릭#

COUNT(*)를 사용하여 행을 카운트합니다.

옵션 타입 필수 설명
name Symbol 아니오 카운트 메트릭 이름. 기본값: 'total'. 식별자는 :{name}_count
type Symbol 아니오 데이터 타입. 기본값: :integer
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

mean 메트릭#

AVG()를 사용하여 평균값을 계산합니다.

옵션 타입 필수 설명
name Symbol 평균을 구할 컬럼 이름. 식별자는 :mean_{name}
type Symbol 아니오 데이터 타입. 기본값: :float
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
scope_proc Proc 아니오 ActiveRecord 스코프 수정 (예: JOIN용)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

column 차원#

컬럼 값으로 결과를 그룹화합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자
type Symbol 데이터 타입 (:string, :integer, :datetime 등)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
scope_proc Proc 아니오 ActiveRecord 스코프 수정 (예: JOIN용)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

date_bucket 차원#

PostgreSQL의 date_trunc() 함수를 사용하여 시간 간격으로 결과를 그룹화합니다. 매개변수를 지원합니다.

옵션 타입 필수 설명
name Symbol 날짜/datetime 컬럼 이름
type Symbol 데이터 타입 (:date 또는 :datetime)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
scope_proc Proc 아니오 ActiveRecord 스코프 수정
parameters Hash 아니오 매개변수 구성 (아래 참조)
description String 아니오 사람이 읽을 수 있는 설명

지원되는 매개변수:

매개변수 타입 기본값 설명
granularity String daily, weekly, monthly, yearly monthly 그룹화를 위한 시간 간격

exact_match 필터#

WHERE column IN (...)을 사용하여 정확한 값 일치로 행을 필터링합니다.

옵션 타입 필수 설명
name Symbol 필터링할 컬럼 이름
type Symbol 필터 값의 데이터 타입
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
max_size Integer 아니오 필터에 허용되는 최대 값 수
description String 아니오 사람이 읽을 수 있는 설명

ClickHouse 엔진 정의#

ClickHouse 엔진(Gitlab::Database::Aggregation::ClickHouse::Engine)은 ClickHouse의 컬럼형 데이터베이스에 최적화된 쿼리를 생성합니다.

ClickHouse 엔진 예시#

class SessionAnalyticsEngine < Gitlab::Database::Aggregation::ClickHouse::Engine
  self.table_name = 'sessions'
  filters do
    exact_match :flow_type, :string, description: 'Filter by flow type'
    range :created_at, :datetime, description: 'Filter by creation date'
  end

  dimensions do
    column :flow_type, :string, description: 'Group by flow type'
    date_bucket :created_at, :datetime,
      parameters: { granularity: { in: %i[daily weekly monthly], type: :string } },
      description: 'Group by date'
  end

  metrics do
    count description: 'Total sessions'
    count :completed, :integer,
      expression: -> { Arel.sql('1') },
      if: -> { Arel.sql('finished_at IS NOT NULL') },
      description: 'Completed sessions'
    mean :duration, :float,
      expression: -> { Arel.sql('finished_at - created_at') },
      if: -> { Arel.sql('finished_at IS NOT NULL') },
      description: 'Average session duration'
    rate :completion,
      numerator_if: -> { Arel.sql('finished_at IS NOT NULL') },
      description: 'Session completion rate'
    quantile :duration, :float,
      expression: -> { Arel.sql('finished_at - created_at') },
      parameters: { quantile: { type: :float, description: 'Quantile value (0.0-1.0)' } },
      description: 'Duration percentile'
  end
end

ClickHouse 엔진은 최적의 성능을 위해 2단계 중첩 쿼리를 생성합니다. 전체 구조는 다음과 같이 표현할 수 있습니다:

-- 쿼리 구조를 강조하는 메타코드 쿼리
SELECT dimensions, metrics
FROM (
  SELECT
    primary_key_columns,
    dimensions_expressions,
    metrics_expressions,
  FROM source_table
  WHERE filters
  GROUP BY ALL
) ch_aggregation_inner_query
GROUP BY ALL
ORDER BY orders

내부 쿼리는 소스 테이블의 각 기본 키에 대한 데이터를 사전 계산합니다. 외부 쿼리는 내부 쿼리를 기반으로 메트릭과 차원을 계산합니다.

전체 쿼리 예시:

SELECT
  `ch_aggregation_inner_query`.`aeq_flow_type` AS aeq_flow_type,
  toStartOfInterval(
    `ch_aggregation_inner_query`.`aeq_created_at`,
    INTERVAL 1 month
  ) AS aeq_created_at,
  COUNT(*) AS aeq_total_count,
  countIf(`ch_aggregation_inner_query`.`aeq_completed_secondary` = 1) AS aeq_completed_count,
  avgIf(
    `ch_aggregation_inner_query`.`aeq_mean_duration`,
    `ch_aggregation_inner_query`.`aeq_mean_duration_secondary` = 1
  ) AS aeq_mean_duration,
  countIf(`ch_aggregation_inner_query`.`aeq_completion_rate` = 1) / COUNT(*) AS aeq_completion_rate,
  quantile(0.5)(`ch_aggregation_inner_query`.`aeq_duration_quantile`) AS aeq_duration_quantile
FROM (
  SELECT
    `sessions`.`flow_type` AS aeq_flow_type,
    `sessions`.`created_at` AS aeq_created_at,
    finished_at IS NOT NULL AS aeq_completed_secondary,
    finished_at - created_at AS aeq_mean_duration,
    finished_at IS NOT NULL AS aeq_mean_duration_secondary,
    finished_at IS NOT NULL AS aeq_completion_rate,
    finished_at - created_at AS aeq_duration_quantile,
    `sessions`.`user_id`,
    `sessions`.`session_id`
  FROM `sessions`
  WHERE `sessions`.`created_at` BETWEEN '2024-01-01' AND '2024-12-31'
  GROUP BY ALL
) ch_aggregation_inner_query
GROUP BY ALL
ORDER BY aeq_flow_type, aeq_created_at

주요 특징:

  • 2단계 쿼리 구조 (내부 쿼리 + 외부 집계)
  • 내부 쿼리는 행 수준 계산 및 기본 키 그룹화를 처리합니다. 외부 쿼리는 최종 집계를 수행합니다. 이 접근 방식을 통해 *Merge 컬럼과 *If 집계를 쉽게 사용할 수 있습니다.
  • 조건부 메트릭은 *If 함수를 사용합니다
  • 모든 컬럼은 aeq_ (Aggregation Engine Query)로 접두사가 붙습니다. 이 접두사는 AggregationResult 객체에 의해 제거됩니다.
  • 컬럼 필터는 내부 쿼리에서 WHERE 또는 HAVING 절로 적용됩니다
  • 메트릭 필터는 외부 쿼리에서 HAVING 절로 적용됩니다
  • 차원은 외부 쿼리에서 GROUP BY 컬럼이 됩니다
  • 메트릭은 외부 쿼리에서 집계 함수를 사용합니다

사용 가능한 구성 요소#

count 메트릭#

countIf()를 사용하여 고유 카운트와 조건부 카운트를 지원합니다.

옵션 타입 필수 설명
name Symbol 아니오 카운트 메트릭 이름. 기본값: 'total'. 식별자는 :{name}_count
type Symbol 아니오 데이터 타입. 기본값: :integer
expression Proc 아니오 특정 값을 카운트하기 위한 커스텀 표현식
if Proc 아니오 조건부 카운트를 위한 조건 표현식 (countIf)
distinct Boolean 아니오 고유 카운트 활성화. 기본값: false
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

mean 메트릭#

avgIf()를 사용하여 조건부 평균을 지원하며 평균값을 계산합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자. 식별자는 :mean_{name}
type Symbol 아니오 데이터 타입. 기본값: :float
expression Proc 아니오 평균을 구할 값에 대한 커스텀 표현식
if Proc 아니오 조건부 평균을 위한 조건 표현식 (avgIf)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

rate 메트릭#

분자 조건에 일치하는 행과 분모 조건에 일치하는 행(또는 전체 행) 사이의 비율을 계산합니다.

옵션 타입 필수 설명
name Symbol 식별자 이름. 식별자는 :{name}_rate
type Symbol 아니오 데이터 타입. 기본값: :float
numerator_if Proc 분자 조건 (카운트할 행)
denominator_if Proc 아니오 분모 조건. 제공되지 않으면 전체 카운트 사용
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

quantile 메트릭#

ClickHouse의 quantile() 함수를 사용하여 백분위수를 계산합니다. 매개변수를 지원합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자. 식별자는 :{name}_quantile
type Symbol 아니오 데이터 타입. 기본값: :float
expression Proc 아니오 값에 대한 커스텀 표현식
parameters Hash 아니오 매개변수 구성 (아래 참조)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

지원되는 매개변수:

매개변수 타입 기본값 설명
quantile Float 0.0 - 1.0 0.5 분위수 값 (0.5 = 중앙값, 0.9 = p90, 0.99 = p99)

column 차원#

컬럼 값으로 결과를 그룹화합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자
type Symbol 데이터 타입 (:string, :integer, :datetime 등)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명
association Boolean 아니오 true이면 _id 접미사 없이도 객체로 차원에 접근 가능합니다. 기본값: false.

date_bucket 차원#

ClickHouse의 toStartOfInterval() 함수를 사용하여 시간 간격으로 결과를 그룹화합니다. 매개변수를 지원합니다.

옵션 타입 필수 설명
name Symbol 날짜/datetime 컬럼 이름
type Symbol 데이터 타입 (:date 또는 :datetime)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
parameters Hash 아니오 매개변수 구성 (아래 참조)
description String 아니오 사람이 읽을 수 있는 설명

지원되는 매개변수:

매개변수 타입 기본값 설명
granularity String daily, weekly, monthly, yearly monthly 그룹화를 위한 시간 간격

exact_match 필터#

정확한 값 일치로 행을 필터링합니다. 일반 컬럼 또는 머지 컬럼(사전 집계된 데이터)에서 필터링을 지원합니다.

옵션 타입 필수 설명
name Symbol 필터링할 컬럼 이름
type Symbol 필터 값의 데이터 타입
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
merge_column Boolean 아니오 true이면 WHERE 대신 HAVING을 사용하여 필터 적용
max_size Integer 아니오 필터에 허용되는 최대 값 수
description String 아니오 사람이 읽을 수 있는 설명

range 필터#

BETWEEN을 사용하여 값 범위로 행을 필터링합니다. 일반 컬럼 또는 머지 컬럼에서 필터링을 지원합니다.

옵션 타입 필수 설명
name Symbol 필터링할 컬럼 이름
type Symbol 필터 값의 데이터 타입 (:datetime, :integer 등)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
merge_column Boolean 아니오 true이면 WHERE 대신 HAVING을 사용하여 필터 적용
description String 아니오 사람이 읽을 수 있는 설명

metric_exact_match 필터#

집계된 메트릭 값에 대한 정확한 일치로 그룹을 필터링합니다. 집계 후 HAVING 절로 적용됩니다.

옵션 타입 필수 설명
name Symbol 필터링할 메트릭의 식별자. 동일한 엔진에 정의된 메트릭과 일치해야 합니다.
type Symbol 필터 값의 데이터 타입
max_size Integer 아니오 필터에 허용되는 최대 값 수
description String 아니오 사람이 읽을 수 있는 설명

참조된 메트릭은 동일한 Request에서도 요청되어야 합니다. 매개변수화된 메트릭의 경우, 필터 parameters는 요청된 메트릭 인스턴스의 매개변수와 일치해야 합니다.

예시:

filters do
  metric_exact_match :total_count, :integer
end
Gitlab::Database::Aggregation::Request.new(
  filters: [{ identifier: :total_count, values: [1, 2] }],
  dimensions: [{ identifier: :user_id }],
  metrics: [{ identifier: :total_count }]
)

metric_range 필터#

BETWEEN을 사용하여 집계된 메트릭의 값 범위로 그룹을 필터링합니다. 집계 후 HAVING 절로 적용됩니다.

Transient 컬럼#

Transient 컬럼은 한 번 정의하여 dimensions, metrics, filters 블록 전체에서 참조할 수 있는 이름 있는 SQL 표현식 별칭입니다. 최종 쿼리 결과에는 투영되지 않습니다. 복잡한 SQL 표현식의 중복을 제거하기 위해 transient 컬럼을 사용합니다.

Transient 컬럼 정의#

이름과 Arel 표현식을 반환하는 블록을 사용하여 클래스 수준에서 transient를 호출합니다. 참조하기 전에 transient 컬럼을 정의합니다.

transient(:duration) do
  sql("dateDiff('seconds', anyIfMerge(created_event_at), anyIfMerge(finished_event_at))")
end

transient(:is_finished) { sql('anyIfMerge(finished_event_at) IS NOT NULL') }

Transient 컬럼 참조#

dimensions, metrics, filters 블록 내에서 transient(:name)을 호출하여 저장된 표현식을 삽입합니다. 람다 표현식이 허용되는 모든 곳에 반환값을 전달할 수 있습니다: 위치 인수 또는 키워드 인수 값으로.

metrics do
  mean :duration, :float, transient(:duration),
    description: 'Average session duration in seconds'

  count :finished, if: transient(:is_finished),
    description: 'Number of finished sessions'
end

프레임워크 사용#

집계 요청 생성#

request = Gitlab::Database::Aggregation::Request.new(
  filters: [
    { identifier: :project_id, values: [1, 2, 3] },
    { identifier: :state, values: ['opened'] }
  ],
  dimensions: [
    { identifier: :author_id },
    { identifier: :created_at, parameters: { granularity: 'monthly' } },
    { identifier: :created_at, parameters: { granularity: 'weekly' } },
  ],
  metrics: [
    { identifier: :total_count },
    { identifier: :mean_weight }
  ],
  order: [
    { identifier: :total_count, direction: :desc } # 순서 식별자는 차원 또는 메트릭을 참조해야 합니다.
  ]
)

엔진으로 요청 실행#

engine = IssueAggregationEngine.new(context: { scope: Issue.all })
response = engine.execute(request)

if response.success?
  puts "Success: #{response.payload[:data].to_a.inspect}"
else
  puts "Errors: #{response.errors}"
end
  • 엔진은 기본 스코프를 제공해야 합니다. 사용 사례에 따라 현재 프로젝트, 네임스페이스, 사용자 등에 이미 사전 필터링된 스코프를 제공할 수 있습니다.
  • 모든 요청 필터는 제공된 기본 스코프에 적용됩니다.

아키텍처 개요#

프레임워크는 여러 핵심 구성 요소로 이루어집니다:

  • Engine: 특정 데이터 소스에 대해 사용 가능한 메트릭, 차원, 필터를 정의하는 핵심 클래스
  • Request: 선택된 메트릭, 차원, 필터, 정렬이 포함된 쿼리 요청을 나타냅니다
  • QueryPlan: 요청을 검증하고 실행 가능한 쿼리 부분으로 변환합니다
  • AggregationResult: 쿼리 실행 및 결과 서식을 처리합니다
┌────────────────────────────────────────────────────┐
│                        Request                     │
│  (metrics, dimensions, filters, order)             │
└────────────────────────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────┐
│                        QueryPlan                   │
│  (validates request, builds plan parts)            │
└────────────────────────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────┐
│                         Engine                     │
│  (executes query plan, returns AggregationResult)  │
└────────────────────────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────┐
│                   AggregationResult                │
│ implements Enumerable to access formatted results  │
└────────────────────────────────────────────────────┘

유효성 검사#

프레임워크는 실행 전에 요청을 검증합니다:

  • 최소한 하나의 메트릭이 필요합니다
  • 참조된 모든 식별자는 엔진 정의에 존재해야 합니다
  • 매개변수는 선언된 검증을 충족해야 합니다. 예: granularity: { in: %i[daily weekly monthly], type: :string }는 granularity 값이 제공된 3개의 문자열 중 하나여야 합니다.

GraphQL 통합#

집계 프레임워크는 Mounter 모듈을 통해 원활한 GraphQL 통합을 제공합니다. 자세한 문서는 GraphQL 통합을 참조하세요.

관련 문서#

집계 프레임워크(Aggregations Framework)

원문 보기
요약

집계 프레임워크는 서로 다른 데이터베이스 백엔드에서 분석 쿼리를 구성하기 위한 통합 인터페이스를 제공합니다. ActiveRecord 엔진(Gitlab::Database::Aggregation::ActiveRecord::Engine)은 ActiveRecord의 쿼리 인터페이스를 사용하여 PostgreSQL 쿼리를 생성합니다.

집계 프레임워크는 서로 다른 데이터베이스 백엔드에서 분석 쿼리를 구성하기 위한 통합 인터페이스를 제공합니다. PostgreSQL(ActiveRecord를 통한)과 ClickHouse를 모두 지원하며, 개발자가 메트릭, 차원, 필터가 있는 재사용 가능한 집계 엔진을 정의할 수 있도록 합니다.

ActiveRecord 엔진 정의#

ActiveRecord 엔진(Gitlab::Database::Aggregation::ActiveRecord::Engine)은 ActiveRecord의 쿼리 인터페이스를 사용하여 PostgreSQL 쿼리를 생성합니다.

ActiveRecord 엔진 예시#

class IssueAggregationEngine < Gitlab::Database::Aggregation::ActiveRecord::Engine
  filters do
    exact_match :project_id, :integer, description: 'Filter by project ID'
    exact_match :state, :string, description: 'Filter by issue state'
  end

  dimensions do
    column :author_id, :integer, description: 'Group by author'
    date_bucket :created_at, :datetime,
      parameters: { granularity: { in: %i[daily weekly monthly yearly], type: :string } },
      description: 'Group by creation date'
  end

  metrics do
    count description: 'Total number of issues'
    mean :weight, :float, description: 'Average issue weight'
  end
end

ActiveRecord 엔진은 단일 수준 SQL 쿼리를 생성합니다:

SELECT
  "issues"."author_id" AS aeq_author_id,
  date_trunc('month', "issues"."created_at") AS aeq_created_at,
  COUNT(*) AS aeq_total_count,
  AVG("issues"."weight") AS aeq_mean_weight
FROM "issues"
WHERE "issues"."project_id" IN (1, 2, 3)
  AND "issues"."state" IN ('opened')
GROUP BY aeq_author_id, aeq_created_at
ORDER BY aeq_author_id, aeq_created_at

주요 특징:

  • 모든 컬럼은 aeq_ (Aggregation Engine Query)로 접두사가 붙습니다. 이 접두사는 AggregationResult 객체에 의해 제거됩니다.
  • 필터는 WHERE 또는 HAVING 절로 적용됩니다
  • 차원은 GROUP BY 컬럼이 됩니다
  • 메트릭은 집계 함수(COUNT, AVG)를 사용합니다

사용 가능한 구성 요소#

count 메트릭#

COUNT(*)를 사용하여 행을 카운트합니다.

옵션 타입 필수 설명
name Symbol 아니오 카운트 메트릭 이름. 기본값: 'total'. 식별자는 :{name}_count
type Symbol 아니오 데이터 타입. 기본값: :integer
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

mean 메트릭#

AVG()를 사용하여 평균값을 계산합니다.

옵션 타입 필수 설명
name Symbol 평균을 구할 컬럼 이름. 식별자는 :mean_{name}
type Symbol 아니오 데이터 타입. 기본값: :float
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
scope_proc Proc 아니오 ActiveRecord 스코프 수정 (예: JOIN용)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

column 차원#

컬럼 값으로 결과를 그룹화합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자
type Symbol 데이터 타입 (:string, :integer, :datetime 등)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
scope_proc Proc 아니오 ActiveRecord 스코프 수정 (예: JOIN용)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

date_bucket 차원#

PostgreSQL의 date_trunc() 함수를 사용하여 시간 간격으로 결과를 그룹화합니다. 매개변수를 지원합니다.

옵션 타입 필수 설명
name Symbol 날짜/datetime 컬럼 이름
type Symbol 데이터 타입 (:date 또는 :datetime)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
scope_proc Proc 아니오 ActiveRecord 스코프 수정
parameters Hash 아니오 매개변수 구성 (아래 참조)
description String 아니오 사람이 읽을 수 있는 설명

지원되는 매개변수:

매개변수 타입 기본값 설명
granularity String daily, weekly, monthly, yearly monthly 그룹화를 위한 시간 간격

exact_match 필터#

WHERE column IN (...)을 사용하여 정확한 값 일치로 행을 필터링합니다.

옵션 타입 필수 설명
name Symbol 필터링할 컬럼 이름
type Symbol 필터 값의 데이터 타입
expression Proc 아니오 컬럼 대신 사용하는 커스텀 Arel 표현식
max_size Integer 아니오 필터에 허용되는 최대 값 수
description String 아니오 사람이 읽을 수 있는 설명

ClickHouse 엔진 정의#

ClickHouse 엔진(Gitlab::Database::Aggregation::ClickHouse::Engine)은 ClickHouse의 컬럼형 데이터베이스에 최적화된 쿼리를 생성합니다.

ClickHouse 엔진 예시#

class SessionAnalyticsEngine < Gitlab::Database::Aggregation::ClickHouse::Engine
  self.table_name = 'sessions'
  filters do
    exact_match :flow_type, :string, description: 'Filter by flow type'
    range :created_at, :datetime, description: 'Filter by creation date'
  end

  dimensions do
    column :flow_type, :string, description: 'Group by flow type'
    date_bucket :created_at, :datetime,
      parameters: { granularity: { in: %i[daily weekly monthly], type: :string } },
      description: 'Group by date'
  end

  metrics do
    count description: 'Total sessions'
    count :completed, :integer,
      expression: -> { Arel.sql('1') },
      if: -> { Arel.sql('finished_at IS NOT NULL') },
      description: 'Completed sessions'
    mean :duration, :float,
      expression: -> { Arel.sql('finished_at - created_at') },
      if: -> { Arel.sql('finished_at IS NOT NULL') },
      description: 'Average session duration'
    rate :completion,
      numerator_if: -> { Arel.sql('finished_at IS NOT NULL') },
      description: 'Session completion rate'
    quantile :duration, :float,
      expression: -> { Arel.sql('finished_at - created_at') },
      parameters: { quantile: { type: :float, description: 'Quantile value (0.0-1.0)' } },
      description: 'Duration percentile'
  end
end

ClickHouse 엔진은 최적의 성능을 위해 2단계 중첩 쿼리를 생성합니다. 전체 구조는 다음과 같이 표현할 수 있습니다:

-- 쿼리 구조를 강조하는 메타코드 쿼리
SELECT dimensions, metrics
FROM (
  SELECT
    primary_key_columns,
    dimensions_expressions,
    metrics_expressions,
  FROM source_table
  WHERE filters
  GROUP BY ALL
) ch_aggregation_inner_query
GROUP BY ALL
ORDER BY orders

내부 쿼리는 소스 테이블의 각 기본 키에 대한 데이터를 사전 계산합니다. 외부 쿼리는 내부 쿼리를 기반으로 메트릭과 차원을 계산합니다.

전체 쿼리 예시:

SELECT
  `ch_aggregation_inner_query`.`aeq_flow_type` AS aeq_flow_type,
  toStartOfInterval(
    `ch_aggregation_inner_query`.`aeq_created_at`,
    INTERVAL 1 month
  ) AS aeq_created_at,
  COUNT(*) AS aeq_total_count,
  countIf(`ch_aggregation_inner_query`.`aeq_completed_secondary` = 1) AS aeq_completed_count,
  avgIf(
    `ch_aggregation_inner_query`.`aeq_mean_duration`,
    `ch_aggregation_inner_query`.`aeq_mean_duration_secondary` = 1
  ) AS aeq_mean_duration,
  countIf(`ch_aggregation_inner_query`.`aeq_completion_rate` = 1) / COUNT(*) AS aeq_completion_rate,
  quantile(0.5)(`ch_aggregation_inner_query`.`aeq_duration_quantile`) AS aeq_duration_quantile
FROM (
  SELECT
    `sessions`.`flow_type` AS aeq_flow_type,
    `sessions`.`created_at` AS aeq_created_at,
    finished_at IS NOT NULL AS aeq_completed_secondary,
    finished_at - created_at AS aeq_mean_duration,
    finished_at IS NOT NULL AS aeq_mean_duration_secondary,
    finished_at IS NOT NULL AS aeq_completion_rate,
    finished_at - created_at AS aeq_duration_quantile,
    `sessions`.`user_id`,
    `sessions`.`session_id`
  FROM `sessions`
  WHERE `sessions`.`created_at` BETWEEN '2024-01-01' AND '2024-12-31'
  GROUP BY ALL
) ch_aggregation_inner_query
GROUP BY ALL
ORDER BY aeq_flow_type, aeq_created_at

주요 특징:

  • 2단계 쿼리 구조 (내부 쿼리 + 외부 집계)
  • 내부 쿼리는 행 수준 계산 및 기본 키 그룹화를 처리합니다. 외부 쿼리는 최종 집계를 수행합니다. 이 접근 방식을 통해 *Merge 컬럼과 *If 집계를 쉽게 사용할 수 있습니다.
  • 조건부 메트릭은 *If 함수를 사용합니다
  • 모든 컬럼은 aeq_ (Aggregation Engine Query)로 접두사가 붙습니다. 이 접두사는 AggregationResult 객체에 의해 제거됩니다.
  • 컬럼 필터는 내부 쿼리에서 WHERE 또는 HAVING 절로 적용됩니다
  • 메트릭 필터는 외부 쿼리에서 HAVING 절로 적용됩니다
  • 차원은 외부 쿼리에서 GROUP BY 컬럼이 됩니다
  • 메트릭은 외부 쿼리에서 집계 함수를 사용합니다

사용 가능한 구성 요소#

count 메트릭#

countIf()를 사용하여 고유 카운트와 조건부 카운트를 지원합니다.

옵션 타입 필수 설명
name Symbol 아니오 카운트 메트릭 이름. 기본값: 'total'. 식별자는 :{name}_count
type Symbol 아니오 데이터 타입. 기본값: :integer
expression Proc 아니오 특정 값을 카운트하기 위한 커스텀 표현식
if Proc 아니오 조건부 카운트를 위한 조건 표현식 (countIf)
distinct Boolean 아니오 고유 카운트 활성화. 기본값: false
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

mean 메트릭#

avgIf()를 사용하여 조건부 평균을 지원하며 평균값을 계산합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자. 식별자는 :mean_{name}
type Symbol 아니오 데이터 타입. 기본값: :float
expression Proc 아니오 평균을 구할 값에 대한 커스텀 표현식
if Proc 아니오 조건부 평균을 위한 조건 표현식 (avgIf)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

rate 메트릭#

분자 조건에 일치하는 행과 분모 조건에 일치하는 행(또는 전체 행) 사이의 비율을 계산합니다.

옵션 타입 필수 설명
name Symbol 식별자 이름. 식별자는 :{name}_rate
type Symbol 아니오 데이터 타입. 기본값: :float
numerator_if Proc 분자 조건 (카운트할 행)
denominator_if Proc 아니오 분모 조건. 제공되지 않으면 전체 카운트 사용
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

quantile 메트릭#

ClickHouse의 quantile() 함수를 사용하여 백분위수를 계산합니다. 매개변수를 지원합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자. 식별자는 :{name}_quantile
type Symbol 아니오 데이터 타입. 기본값: :float
expression Proc 아니오 값에 대한 커스텀 표현식
parameters Hash 아니오 매개변수 구성 (아래 참조)
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명

지원되는 매개변수:

매개변수 타입 기본값 설명
quantile Float 0.0 - 1.0 0.5 분위수 값 (0.5 = 중앙값, 0.9 = p90, 0.99 = p99)

column 차원#

컬럼 값으로 결과를 그룹화합니다.

옵션 타입 필수 설명
name Symbol 컬럼 이름 또는 식별자
type Symbol 데이터 타입 (:string, :integer, :datetime 등)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
formatter Proc 아니오 결과에 적용되는 서식 함수
description String 아니오 사람이 읽을 수 있는 설명
association Boolean 아니오 true이면 _id 접미사 없이도 객체로 차원에 접근 가능합니다. 기본값: false.

date_bucket 차원#

ClickHouse의 toStartOfInterval() 함수를 사용하여 시간 간격으로 결과를 그룹화합니다. 매개변수를 지원합니다.

옵션 타입 필수 설명
name Symbol 날짜/datetime 컬럼 이름
type Symbol 데이터 타입 (:date 또는 :datetime)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
parameters Hash 아니오 매개변수 구성 (아래 참조)
description String 아니오 사람이 읽을 수 있는 설명

지원되는 매개변수:

매개변수 타입 기본값 설명
granularity String daily, weekly, monthly, yearly monthly 그룹화를 위한 시간 간격

exact_match 필터#

정확한 값 일치로 행을 필터링합니다. 일반 컬럼 또는 머지 컬럼(사전 집계된 데이터)에서 필터링을 지원합니다.

옵션 타입 필수 설명
name Symbol 필터링할 컬럼 이름
type Symbol 필터 값의 데이터 타입
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
merge_column Boolean 아니오 true이면 WHERE 대신 HAVING을 사용하여 필터 적용
max_size Integer 아니오 필터에 허용되는 최대 값 수
description String 아니오 사람이 읽을 수 있는 설명

range 필터#

BETWEEN을 사용하여 값 범위로 행을 필터링합니다. 일반 컬럼 또는 머지 컬럼에서 필터링을 지원합니다.

옵션 타입 필수 설명
name Symbol 필터링할 컬럼 이름
type Symbol 필터 값의 데이터 타입 (:datetime, :integer 등)
expression Proc 아니오 컬럼 대신 사용하는 커스텀 표현식
merge_column Boolean 아니오 true이면 WHERE 대신 HAVING을 사용하여 필터 적용
description String 아니오 사람이 읽을 수 있는 설명

metric_exact_match 필터#

집계된 메트릭 값에 대한 정확한 일치로 그룹을 필터링합니다. 집계 후 HAVING 절로 적용됩니다.

옵션 타입 필수 설명
name Symbol 필터링할 메트릭의 식별자. 동일한 엔진에 정의된 메트릭과 일치해야 합니다.
type Symbol 필터 값의 데이터 타입
max_size Integer 아니오 필터에 허용되는 최대 값 수
description String 아니오 사람이 읽을 수 있는 설명

참조된 메트릭은 동일한 Request에서도 요청되어야 합니다. 매개변수화된 메트릭의 경우, 필터 parameters는 요청된 메트릭 인스턴스의 매개변수와 일치해야 합니다.

예시:

filters do
  metric_exact_match :total_count, :integer
end
Gitlab::Database::Aggregation::Request.new(
  filters: [{ identifier: :total_count, values: [1, 2] }],
  dimensions: [{ identifier: :user_id }],
  metrics: [{ identifier: :total_count }]
)

metric_range 필터#

BETWEEN을 사용하여 집계된 메트릭의 값 범위로 그룹을 필터링합니다. 집계 후 HAVING 절로 적용됩니다.

Transient 컬럼#

Transient 컬럼은 한 번 정의하여 dimensions, metrics, filters 블록 전체에서 참조할 수 있는 이름 있는 SQL 표현식 별칭입니다. 최종 쿼리 결과에는 투영되지 않습니다. 복잡한 SQL 표현식의 중복을 제거하기 위해 transient 컬럼을 사용합니다.

Transient 컬럼 정의#

이름과 Arel 표현식을 반환하는 블록을 사용하여 클래스 수준에서 transient를 호출합니다. 참조하기 전에 transient 컬럼을 정의합니다.

transient(:duration) do
  sql("dateDiff('seconds', anyIfMerge(created_event_at), anyIfMerge(finished_event_at))")
end

transient(:is_finished) { sql('anyIfMerge(finished_event_at) IS NOT NULL') }

Transient 컬럼 참조#

dimensions, metrics, filters 블록 내에서 transient(:name)을 호출하여 저장된 표현식을 삽입합니다. 람다 표현식이 허용되는 모든 곳에 반환값을 전달할 수 있습니다: 위치 인수 또는 키워드 인수 값으로.

metrics do
  mean :duration, :float, transient(:duration),
    description: 'Average session duration in seconds'

  count :finished, if: transient(:is_finished),
    description: 'Number of finished sessions'
end

프레임워크 사용#

집계 요청 생성#

request = Gitlab::Database::Aggregation::Request.new(
  filters: [
    { identifier: :project_id, values: [1, 2, 3] },
    { identifier: :state, values: ['opened'] }
  ],
  dimensions: [
    { identifier: :author_id },
    { identifier: :created_at, parameters: { granularity: 'monthly' } },
    { identifier: :created_at, parameters: { granularity: 'weekly' } },
  ],
  metrics: [
    { identifier: :total_count },
    { identifier: :mean_weight }
  ],
  order: [
    { identifier: :total_count, direction: :desc } # 순서 식별자는 차원 또는 메트릭을 참조해야 합니다.
  ]
)

엔진으로 요청 실행#

engine = IssueAggregationEngine.new(context: { scope: Issue.all })
response = engine.execute(request)

if response.success?
  puts "Success: #{response.payload[:data].to_a.inspect}"
else
  puts "Errors: #{response.errors}"
end
  • 엔진은 기본 스코프를 제공해야 합니다. 사용 사례에 따라 현재 프로젝트, 네임스페이스, 사용자 등에 이미 사전 필터링된 스코프를 제공할 수 있습니다.
  • 모든 요청 필터는 제공된 기본 스코프에 적용됩니다.

아키텍처 개요#

프레임워크는 여러 핵심 구성 요소로 이루어집니다:

  • Engine: 특정 데이터 소스에 대해 사용 가능한 메트릭, 차원, 필터를 정의하는 핵심 클래스
  • Request: 선택된 메트릭, 차원, 필터, 정렬이 포함된 쿼리 요청을 나타냅니다
  • QueryPlan: 요청을 검증하고 실행 가능한 쿼리 부분으로 변환합니다
  • AggregationResult: 쿼리 실행 및 결과 서식을 처리합니다
┌────────────────────────────────────────────────────┐
│                        Request                     │
│  (metrics, dimensions, filters, order)             │
└────────────────────────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────┐
│                        QueryPlan                   │
│  (validates request, builds plan parts)            │
└────────────────────────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────┐
│                         Engine                     │
│  (executes query plan, returns AggregationResult)  │
└────────────────────────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────┐
│                   AggregationResult                │
│ implements Enumerable to access formatted results  │
└────────────────────────────────────────────────────┘

유효성 검사#

프레임워크는 실행 전에 요청을 검증합니다:

  • 최소한 하나의 메트릭이 필요합니다
  • 참조된 모든 식별자는 엔진 정의에 존재해야 합니다
  • 매개변수는 선언된 검증을 충족해야 합니다. 예: granularity: { in: %i[daily weekly monthly], type: :string }는 granularity 값이 제공된 3개의 문자열 중 하나여야 합니다.

GraphQL 통합#

집계 프레임워크는 Mounter 모듈을 통해 원활한 GraphQL 통합을 제공합니다. 자세한 문서는 GraphQL 통합을 참조하세요.

관련 문서#