집계된 가치 스트림 분석
- GitLab 14.7에 도입되었습니다.
면책 조항: 이 페이지에는 예정된 제품, 기능 및 기능과 관련된 정보가 포함되어 있습니다. 제시된 정보는 정보 제공을 목적으로 한 것임을 유념해 주시기 바랍니다. 구매 또는 계획 목적으로 이 정보를 신뢰하지 마십시오. 이 페이지에 언급된 모든 항목은 변경되거나 지연될 수 있습니다. 제품, 기능 또는 기능의 개발, 릴리스 및 타이밍은 GitLab Inc의 단독 재량에 따라 남아 있습니다.
이 페이지는 가치 스트림 분석(VSA)을 위한 집계된 백엔드의 개요를 제공합니다.
현재 상태
집계된 백엔드는 그룹 수준에서 GitLab 15.0에서 기본적으로 사용됩니다.
동기
집계된 백엔드는 VSA 기능의 성능 제한을 해결하고 장기적인 성장을 위해 설정하는 것을 목표로 합니다.
주요 데이터베이스는 분석 워크로드에 준비되어 있지 않습니다. 장기 실행 쿼리를 실행하면 응용 프로그램의 신뢰성에 영향을 미칠 수 있습니다. 대규모 그룹의 경우 현재 구현(이전 백엔드)은 느리며 경우에 따라 구성된 문장 시간 초과(15초)로 인해 로드되지 않을 수도 있습니다.
이전 백엔드의 데이터베이스 쿼리는 핵심 도메인 모델을 직접 사용합니다. IssuableFinders
클래스를 통해(예: MergeRequestsFinder 및 IssuesFinder) 요청된 일자 범위 필터의 변경으로 성능적으로 더 이상 실용적이지 않았습니다.
집계된 VSA 백엔드의 이점:
- 더 간단한 데이터베이스 쿼리(더 적은 JOIN).
- 빠른 집계, 단일 테이블만 액세스됩니다.
- 첫 번째 페이지로드 시간을 개선하기 위한 추가 집계를 도입할 수 있는 가능성.
- 대규모 그룹(다수의 하위 그룹, 프로젝트, 이슈 및 병합 요청)에 대한 더 나은 성능.
- 데이터베이스 분해 준비가 되어 있습니다. VSA 관련 데이터베이스 테이블은 최소한의 개발 노력으로 별도의 데이터베이스에 저장될 수 있습니다.
- 데이터 내보내기에 유용한 키세트 페이지네이션 준비가 되어 있습니다.
- 더 복잡한 이벤트 정의를 구현할 수 있는 가능성.
- 예: 시작 이벤트는 시스템이 사용할 가장 일찍 발생하는 타임스탬프 열이 될 수 있습니다.
- 예:
MIN(issues.created_at, issues.updated_at)
구성 예시
이 예시에서 두 독립적인 가치 스트림이 Test Group
(최상위 네임스페이스) 내에서 서로 다른 개발 워크플로우를 사용하는 두 팀을 위해 설정되어 있습니다.
첫 번째 가치 스트림은 단계를 정의하기 위해 표준 타임스탬프 기반 이벤트를 사용합니다. 두 번째 가치 스트림은 라벨 이벤트를 사용합니다.
예제에서 각 가치 스트림과 단계 항목은 데이터베이스에 저장됩니다. Deployment
단계가 두 가치 스트림에 대해 동일하게 사용되는 것에 주목하세요. 이것은 기저 stage_event_hash_id
가 두 단계에 대해 동일하다는 것을 의미합니다. stage_event_hash_id
는 백엔드가 수집하는 데이터 양을 줄이고 데이터베이스 분할에 중요한 역할을 합니다.
우리는 가치 스트림과 단계가 드물게 변경될 것으로 기대합니다. 단계(시작 및 종료 이벤트)가 변경되면 집계된 데이터가 오래되어집니다. 이는 매일 주기적으로 발생하는 집계로 고쳐집니다.
기능 가용성
집계된 VSA 기능은 그룹 및 프로젝트 수준에서 사용할 수 있지만, 집계된 백엔드는 데이터 저장 및 데이터 계산 비용으로 인해 프리미엄 및 얼티메이트 고객만 사용할 수 있습니다. 정규화되고 집계된 데이터를 저장하려면 상당한 디스크 공간이 필요합니다.
집계된 가치 스트림 분석 아키텍처
집계된 VSA 백엔드의 주요 아이디어는 분리입니다. VSA 데이터베이스 테이블과 쿼리는 핵심 도메인 모델(이슈, 병합 요청)을 직접 사용하지 않습니다. 이를 통해 VSA를 응용 프로그램의 다른 부분과 독립적으로 확장하고 최적화할 수 있습니다.
아키텍처는 두 가지 주요 메커니즘으로 구성됩니다:
- 주기적 데이터 수집 및 로드(백그라운드에서 발생).
- 수집된 데이터에 대한 쿼리(사용자에 의해 호출).
데이터 로드
VSA의 집계된 특성은 주기적 데이터 로딩에서 나타납니다. 시스템은 핵심 도메인 모델을 쿼리하여 단계 및 타임스탬프 데이터를 수집합니다. 이러한 데이터는 주기적으로 VSA 데이터베이스 테이블에 삽입됩니다.
프리미엄 또는 얼티메이트 라이선스를 가진 각 최상위 네임스페이스에 대한 고수준 개요:
- 그룹 내 모든 단계를 로드합니다.
- 이슈 및 병합 요청 레코드를 반복합니다.
- 단계 구성(시작 및 종료 이벤트 식별자)에 따라 타임스탬프 데이터를 수집합니다.
- VSA 데이터베이스 테이블에 데이터를
INSERT
또는UPDATE
합니다.
데이터 로딩은 Analytics::CycleAnalytics::DataLoaderService
클래스 내에서 구현됩니다. 일부 그룹에는 많은 데이터가 포함되어 있으므로, 일반적인 데이터베이스에 과부하를 방지하기 위해 서비스는 일괄 처리 작업을 수행하고 엄격한 응용 프로그램 제한을 강제합니다:
- 일괄 처리 내에서 레코드를 로드합니다.
- 일괄 처리 내에서 레코드를 삽입합니다.
- 한도에 도달하면 작업을 중지하고 나중에 처리를 계속할 수 있도록 백그라운드 작업을 예약합니다.
- 특정 지점부터 데이터 처리를 계속합니다.
GitLab 14.7을 기준으로 데이터 로딩은 수동으로 수행됩니다. 기능이 준비되면 시스템이 cron 작업을 통해 주기적으로 서비스를 호출합니다(이 부분은 아직 구현되지 않았습니다).
반복 레코드
일괄 처리된 반복은 효율적인 IN 연산자로 구현됩니다. 백그라운드 작업은 updated_at
및 id
열을 기준으로 그룹 계층 구조의 모든 이슈 및 병합 요청 레코드를 스캔합니다. 이미 집계된 그룹의 경우, DataLoaderService
는 특정 지점부터 집계를 계속하여 시간을 절약합니다.
타임스탬프 데이터 수집은 매 반복마다 발생합니다. DataLoaderService
는 그룹 계층 내에서 구성된 단계 이벤트를 결정하고 필요한 타임스탬프를 선택하는 쿼리를 작성합니다. 단계 레코드는 구성된 이벤트를 알고 있고, 이벤트는 타임스탬프 열을 선택하는 방법을 알고 있습니다.
수집된 단계 이벤트 예시: 병합 요청이 병합됨, 병합 요청이 생성됨, 병합 요청이 닫힘
타임스탬프를 로드하기 위한 생성된 SQL 쿼리:
SELECT
-- 열 목록은 구성된 단계에 따라 다릅니다
"merge_request_metrics"."merged_at",
"merge_requests"."created_at",
"merge_request_metrics"."latest_closed_at"
FROM "merge_requests"
LEFT OUTER JOIN "merge_request_metrics" ON "merge_request_metrics"."merge_request_id" = "merge_requests"."id"
WHERE "merge_requests"."id" IN (1, 2, 3, 4) -- 아이디는 일괄 처리된 쿼리에서 나온 것입니다
merged_at
열은 별도의 테이블(merge_request_metrics
)에 있습니다. Gitlab::Analytics::CycleAnalytics::StagEvents::MergeRequestMerged
클래스는 자체를 (LEFT JOIN을 사용) 타임스탬프 데이터를 로드하기 위한 스코프에 추가합니다. 이 동작은 각 StageEvent
클래스에 include_in
메서드로 구현됩니다.
데이터 수집 쿼리는 이벤트 수준에서 작동합니다. 이벤트는 단계에서 이벤트 타임스탬프를 추출하고 동일한 데이터를 여러 번 수집하지 않도록 보장합니다. 위에서 언급한 이벤트는 다음과 같은 단계 구성에서 나올 수 있습니다:
- 병합 요청 생성됨 - 병합 요청 병합됨
- 병합 요청 생성됨 - 병합 요청 닫힘
다른 조합도 가능하지만 의미가 없는 조합은 방지합니다. 예를 들어:
- 병합 요청 병합됨 - 병합 요청 생성됨
생성 시간은 항상 처음에 발생하므로 이 단계는 항상 음의 기간을 보고합니다.
데이터 범위
데이터 수집은 최상위 그룹부터 시작하여 그룹 계층의 모든 이슈 및 병합 요청 레코드를 스캔하고 처리합니다. 따라서 하위 그룹에 값 스트림이 하나뿐인 그룹이라도 이 그룹의 계층 구조에서 모든 이슈 및 병합 요청 데이터를 수집합니다. 이는 데이터 수집 메커니즘을 간단하게 하는 것을 목표로 합니다. 또한, 데이터 연구에 따르면 대부분의 그룹 계층에서 단계가 최상위 수준에 구성되어 있다는 것을 보여줍니다.
데이터 수집 과정에서 수집된 타임스탬프 데이터는 행으로 변환됩니다. 각 구성된 단계에 대해 시작 이벤트 타임스탬프가 있는 경우, 시스템은 하나의 이벤트 레코드를 삽입 또는 업데이트합니다. 이를 통해 각 이슈 및 병합 요청의 총합을 세고 해당 합곱을 단계 수로 곱하여 그룹 당 삽입된 행의 상한선을 결정할 수 있습니다.
데이터 일관성에 대한 고려사항
데이터 수집의 비동기적인 성격으로 인해 데이터 일관성 문제가 발생할 수 있습니다. 이는 쿼리 성능을 크게 향상시키는 대신에 발생하는 트레이드오프입니다. 분석 작업에 있어 데이터가 약간 늦게 도착하는 것은 허용될 수 있는 것으로 생각됩니다.
전개 전에 VSA 페이지에 일부 지표를 구현하여 가장 최근의 백엔드 활동을 보여주는 것을 계획하고 있습니다. 예를 들어, 마지막 데이터 수집 타임스탬프와 마지막 일관성 확인 타임스탬프를 보여주는 지표 등이 있을 것입니다.
데이터베이스 구조
VSA는 다음 도메인 모델에 대한 데이터를 수집합니다: Issue
및 MergeRequest
. 집계된 데이터를 분리하기 위해 두 개의 추가 데이터베이스 테이블을 사용합니다:
analytics_cycle_analytics_issue_stage_events
analytics_cycle_analytics_merge_request_stage_events
두 테이블은 stage_event_hash_id
에 의해 해싱된 파티션을 사용합니다. 각 테이블은 32개의 파티션을 사용합니다. 이것은 임의의 숫자이며 변경될 수 있습니다. 중요한 것은 파티션을 100GB 미만의 크기로 유지하는 것입니다 (이것은 기능에 많은 여유를 제공합니다).
열 | 설명 |
---|---|
stage_event_hash_id
| 파티션 키 |
merge_request_id 또는 issue_id
| 도메인 레코드 (Issuable)에 대한 참조 |
group_id
| 그룹에 대한 참조 (비정규화) |
project_id
| 프로젝트에 대한 참조 |
milestone_id
| 도메인 레코드 테이블에서 복제된 데이터 |
author_id
| 도메인 레코드 테이블에서 복제된 데이터 |
state_id
| 도메인 레코드 테이블에서 복제된 데이터 |
start_event_timestamp
| 단계 구성에서 파생된 타임스탬프 |
end_event_timestamp
| 단계 구성에서 파생된 타임스탬프 |
데이터 분리 요구 사항에 따라, 테이블에는 외래 키가 없습니다. 일관성은 백그라운드 작업에 의해 보장됩니다 (이벤트는 최종적으로 일관성이 맞추어집니다).
데이터 쿼리
기본 쿼리는 항상 다음 필터를 포함합니다:
-
stage_event_hash_id
- 파티션 키 -
project_id
또는group_id
- 프로젝트 또는 그룹 레벨 쿼리에 따라 다름 -
end_event_timestamp
- 날짜 범위 필터 (최근 30일)
예: GitLab 프로젝트의 리뷰 단계 기간을 선택하는 것
SELECT end_event_timestamp - start_event_timestamp
FROM analytics_cycle_analytics_merge_request_stage_events
WHERE
stage_event_hash_id = 16 AND -- 특정 파티션을 선택
project_id = 278964 AND
end_event_timestamp > '2022-01-01' AND end_event_timestamp < '2022-01-30'
쿼리 생성
쿼리 백엔드는 이전 백엔드 구현에서 사용하는 동일한 인터페이스 뒤에 숨겨져 있습니다. 이를 통해 우리는 쉽게 이전과 새로운 쿼리 백엔드 간을 전환할 수 있습니다.
-
DataCollector
: VSA 데이터를 쿼리하기 위한 진입점-
BaseQueryBuilder
: 기본ActiveRecord
범위를 제공합니다 (여기에서 필터가 적용됩니다). -
average
: 평균 집계. -
median
: 중간값 집계. -
count
: 행 계산. -
records
: 이슈 또는 병합 요청 레코드 목록.
-
필터
VSA는 기본 쿼리에 다양한 필터를 지원합니다. 대부분의 필터는 추가 JOIN이 필요하지 않습니다.
필터 이름 | 설명 |
---|---|
milestone_title
| 백엔드에서 milestone_id 필터로 변환됨
|
author_username
| 백엔드에서 author_id 필터로 변환됨
|
project_ids
| 그룹 수준에서만 사용됨 |
예외: 이러한 필터는 다른 테이블에 적용되어 JOIN
이 필요합니다.
필터 이름 | 설명 |
---|---|
label_name
|
label_links 테이블을 사용한 배열 필터
|
assignee_username
|
*_assignees 테이블을 사용한 배열 필터
|
데이터베이스를 완전히 분해하려면, 필요한 ID 값이 VSA 데이터베이스 테이블에 복제되어야 합니다. 이 변경 사항은 배열 열을 사용하여 구현할 수 있습니다.
엔드포인트
이 기능은 데이터를 프런트엔드로 전달하기 위해 비공개 JSON API를 사용합니다. 첫 페이지 로드 시에는 다음 요청이 호출됩니다:
- 대부분 비어 있는 초기 HTML 페이지 로드입니다. 일부 구성 데이터는
data
속성을 통해 노출됩니다. -
value_streams
- 주어진 그룹에 대한 사용 가능한 가치 스트림 로드. -
stages
- 현재 선택된 가치 스트림에 대한 단계 로드. -
median
- 각 단계별로 중간 기간을 요청합니다. -
count
- 각 단계별로 항목 수를 요청합니다 (이는 제한된 횟수이며, 최대 1000행). -
average_duration_chart
- 기간 차트에 대한 데이터입니다. -
summary
,time_summary
- Top-level 집계, 대부분의 메트릭은 다른 API/finders를 사용하고 집계된 백엔드를 호출하지 않습니다.
특정 단계를 선택하면 records
엔드포인트가 호출되어 선택한 단계에 대한 관련된 레코드(페이지별)가 특정 순서로 반환됩니다.
데이터베이스 분해
쿼리 로직을 주 애플리케이션 코드에서 분리함으로써, 이 기능은 데이터베이스 분해를 준비하게 되었습니다. VSA가 별도의 데이터베이스 인스턴스를 필요로 한다고 결정하면, 집계된 테이블을 이동하는 것은 노력이 적게 들어갑니다.
이 기능의 성능을 더욱 개선하기 위해 다른 데이터베이스 기술을 사용할 수도 있습니다. 예를 들어 Timescale DB와 같은 것입니다.