ClickHouse 리뷰어 가이드라인
이 페이지는 ClickHouse 리뷰어를 위한 입문 자료와 가이드를 제공합니다. ClickHouse 리뷰어는 ClickHouse OLAP 데이터베이스에 경험이 있는 도메인 전문가입니다. 리뷰어의 책임은 ClickHouse 관련 변경 사항을 검증하고 ClickHouse가 구성될 수 있는 모든 GitLab 환경에서 올바르게 작동하는지 확인하는 것입니다.
이 페이지는 ClickHouse 리뷰어를 위한 입문 자료와 가이드를 제공합니다.
ClickHouse 리뷰어 업무 범위#
ClickHouse 리뷰어는 ClickHouse OLAP 데이터베이스에 경험이 있는 도메인 전문가입니다. ClickHouse와 상호작용하는 애플리케이션 코드가 수정될 때마다 ClickHouse 데이터베이스 리뷰가 필요합니다. 예시는 다음과 같습니다:
- 새 ClickHouse 마이그레이션 추가.
- ClickHouse 쿼리를 실행하는 서비스 클래스 변경.
- 새 ClickHouse 쿼리 도입.
리뷰어의 책임은 ClickHouse 관련 변경 사항을 검증하고 ClickHouse가 구성될 수 있는 모든 GitLab 환경에서 올바르게 작동하는지 확인하는 것입니다.
ClickHouse 리뷰어를 위한 리소스#
- GitLab 내 ClickHouse: GitLab에서의 ClickHouse 사용 개요.
- GitLab 데이터베이스 리뷰어 가이드라인: 데이터베이스 마이그레이션에 관해 특히 ClickHouse에도 적용되는 일반 원칙. ClickHouse는 관계형 데이터베이스와 동일한 배포 전 및 배포 후 마이그레이션 전략을 따릅니다.
일반 가이드라인#
데이터베이스 스키마 일관성 보장#
현재 ClickHouse 데이터베이스 스키마는 단일 main.sql 파일에 저장됩니다. 이 파일은 ActiveRecord 마이그레이션과 유사하게 마이그레이션 실행 시 자동으로 업데이트됩니다.
때로는 main.sql 파일이 머지 리퀘스트에서 업데이트되거나 커밋되지 않아 마이그레이션으로 구축된 스키마와 커밋된 스키마 파일 간에 불일치가 발생할 수 있습니다.
이 문제를 감지하기 위해 CI 잡(clickhouse:check-schema)이 테스트 스테이지 중에 실행됩니다. 이 잡은 새로 구축된 스키마를 main.sql과 비교하고 차이가 있으면 실패합니다.
- 이 잡은 실패해도 괜찮도록 허용되지 않습니다. 실패하면 MR 파이프라인이 실패합니다.
- 리뷰어로서 잡 로그를 항상 확인하세요. 실패하는 경우 차이를 신중하게 검사하세요. 공백이 아닌 관련 차이는 MR 작성자와 논의해야 합니다.
- 거짓 양성의 경우(예: ClickHouse 버전 불일치로 인한 차이), MR에
pipeline:skip-check-clickhouse-schema라벨을 추가하여 이 검사를 건너뜁니다.
정당한 스키마 차이를 해결하기 위해 작성자는 모든 마이그레이션이 실행되도록 하고 스키마를 덤프할 수 있습니다:
bundle exec rake gitlab:clickhouse:migrate; bundle exec rake gitlab:clickhouse:schema:dump
데이터베이스 쿼리 리뷰#
GitLab의 ClickHouse 쿼리는 두 가지 방법으로 작성할 수 있습니다:
- Raw SQL 쿼리
- QueryBuilder – ActiveRecord와 유사한 추상화(문서)
Raw SQL 쿼리를 리뷰할 때 변수 보간에 주의를 기울이세요:
-
권장: 민감한 데이터가 로그에 기록되지 않도록 변수는 ClickHouse의 플레이스홀더 구문을 사용해야 합니다:
sql = 'SELECT * FROM events WHERE id > {min_id:UInt64}' -
고정 문자열 보간(예: 문자열이 Ruby 상수에 할당되는 경우)은 SQL 인젝션 또는 잘못된 형식의 쿼리를 방지하기 위해 항상 적절한 인용을 사용해야 합니다:
SQL = "SELECT * FROM events WHERE type = #{ClickHouse::Client::Quoting.quote('Issue')}"
데이터베이스 쿼리 성능 리뷰#
ClickHouse는 대용량 데이터셋을 효율적으로 처리할 수 있지만 복잡한 집계에서도 쿼리 실행을 10초 이내로 유지하는 것을 목표로 합니다. 성능 기대치는 기능 사용량과 데이터셋 크기에 따라 다릅니다.
쿼리를 리뷰할 때:
- 코드에서 명확하게 보이지 않는 경우 작성자에게 Raw SQL 제공을 요청합니다.
- 테이블 구조를 검토합니다(
SHOW CREATE TABLE table_name FORMAT raw) 파티셔닝과 기본 키를 이해하기 위해. - 쿼리 필터가 테이블의 기본 키 또는 파티셔닝 열과 일치하는지 확인합니다.
쿼리 예시:
SELECT count(DISTINCT contributions.author_id) AS contributor_count
FROM (
SELECT argMax(author_id, contributions.updated_at) AS author_id
FROM contributions
WHERE
startsWith(contributions.path, {namespace_path:String})
AND contributions.created_at BETWEEN {from:Date} AND {to:Date}
GROUP BY id
) contributions
이 쿼리는 필터 열(path, created_at)이 기본 키에 포함된 경우 잘 동작합니다:
CREATE TABLE contributions (
id UInt64,
path String,
author_id UInt64,
target_type LowCardinality(String),
action UInt8,
created_at Date,
updated_at DateTime64(6, 'UTC')
) ENGINE = ReplacingMergeTree
PARTITION BY toYear(created_at)
ORDER BY (path, created_at, author_id, id);
성능 검증 단계:
-
대표적인 파라미터로 테스트합니다(예:
namespace_path='9970/', 1개월 날짜 범위). -
쿼리를 실행하고 경과 시간과 읽은 행 수를 기록합니다:
Elapsed: 0.062s Read: 1,111,111 rows (15.55 MB) -
스캔한 행을 전체 테이블 행과 비교합니다(
SELECT COUNT(*) FROM contributions). 제대로 제한된 쿼리는 전체 행의 일부만 읽어야 합니다.
쿼리 플랜 검사:
EXPLAIN indexes=1을 사용하여 필터가 기본 키 인덱스를 사용하는지 확인합니다:
EXPLAIN indexes=1
SELECT count(DISTINCT author_id) FROM contributions ...
FORMAT raw
플랜에서의 발췌:
PrimaryKey
Keys:
path
created_at
Condition: and((created_at in (-Inf, 20361]), and((created_at in [20332, +Inf)), (path in ['9970/', '99700'))))
Parts: 11/11
Granules: 185/72937
Search Algorithm: generic exclusion search
출력에서 PrimaryKey 섹션을 찾고 Granules 비율을 확인하세요.
예를 들어: 185/72937 그래뉼은 테이블의 작은 부분만 스캔되었음을 의미합니다 - 성능에 이상적입니다.
성능에 대한 논의를 제기할 때:
- 쿼리가 1000만 행 이상을 스캔하는 경우.
- 쿼리가 5~10초 실행 시간을 지속적으로 초과하는 경우.
- 쿼리가 자주 실행될 경우.
성능 검증이 대규모 네임스페이스(예: gitlab-org 또는 gitlab-org/gitlab)의 실제(또는 합성) 데이터를 사용하도록 하세요.
새 구체화된 뷰 리뷰#
구체화된 뷰가 POPULATE 키워드로 생성되었는지 또는 대용량 데이터셋에 대한 백필 마이그레이션이 있는지 확인하세요.
테이블 엔진별 동작#
MergeTree 계열에서 기본 키(즉, ORDER BY)는 정렬/인덱스를 정의하며 고유성 제약이 아닙니다. 동일한 기본 키 값을 가진 행이 공존할 수 있습니다. 수집 파이프라인이 중복 또는 업데이트를 생성할 수 있는 경우 읽기 시간에 처리하거나(또는 버전을 축소하는 엔진을 선택해야 합니다).
MergeTree 엔진#
- 자동 중복 제거 없음.
- 데이터가 완전히 추가 전용이고 중복이 발생할 수 없는 경우(예: 불변 이벤트 로그) 사용합니다.
CREATE TABLE events
(
event_id UInt64,
timestamp DateTime,
user_id UInt64,
payload String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(timestamp)
ORDER BY (event_id, timestamp);
ReplacingMergeTree#
ReplacingMergeTree는 백그라운드 병합 중에 중복 기본 키를 축소할 수 있습니다(비결정적 타이밍). 병합이 발생할 때까지 읽기에서 여전히 여러 버전이 보일 수 있으므로 쿼리 시간 중복 제거가 권장됩니다.
모범 사례:
- 버전 열(단조 증가, 일반적으로
DateTime64) 제공. - 소프트 삭제를 위한 선택적 삭제 플래그(
Bool유형).
버전 파라미터를 생략하면 병합 후 중복 제거된 행은 임의적입니다.
CREATE TABLE items
(
id UInt64,
name String,
status LowCardinality(String),
updated_at DateTime64(6), -- acts as the version
deleted Bool DEFAULT 0 -- deleted flag for marking a record deleted
)
ENGINE = ReplacingMergeTree(updated_at, deleted)
ORDER BY id
행을 중복 제거하려면 버전 열로 argMax를 사용하고 기본 키로 GROUP BY를 수행하세요:
SELECT *
FROM (
SELECT
id,
argMax(name, updated_at) AS name,
argMax(status, updated_at) AS status,
argMax(deleted, updated_at) AS deleted
FROM items
GROUP BY id
) AS items
WHERE deleted = false
ClickHouse 콘솔이나 테스트 케이스에서는 FINAL 수정자를 사용할 수 있습니다.
SELECT * FROM items FINAL;
프로덕션 쿼리에서 FINAL을 피하세요. FINAL은 즉석에서 축소/병합을 강제하며 I/O 측면에서 매우 비쌀 수 있습니다. 위에서 언급한 쿼리 시간 중복 제거 패턴을 선호하세요.
