1081 words
5 minutes
[clickHouse]Final keyword의 의미
01. Overview
ClickHouse에서 FINAL 키워드는 MergeTree 계열 테이블에서 아직 물리적으로 Merge되지 않은 중복/삭제/버전 데이터를, 쿼리 시점에 강제로 정리해서 읽는 키워드
SELECT * FROM events FINAL WHERE user_id = 123;-> background merge 결과를 기다리지 않고, 쿼리 시점에 논리적 최종 상태를 강제 계산
ClickHouse MergeTree 계열은 즉시 정합성이 아닌 eventual consistency + background merge 모델
- ClickHouse의 MergeTree 엔진은 데이터를 즉시 병합하지 않음
- INSERT된 데이터는 별도의 Part로 저장되고, 나중 백그라운드에서 비동기적으로 Merge됨
- 그래서 중간 상태가 존재함
[Part 1] user_id=1, status='active', version=1[Part 2] user_id=1, status='inactive', version=2 ← 같은 키, 다른 Part[Part 3] user_id=1, status='active', version=3Merge 전 SELECT를 하면 3개 행이 모두 반환됨
02. ReplacingMergeTree 동작 원리
ReplacingMergeTree는 동일한 ORDER BY 키를 가진 행 중 최신 버전만 유지하는 엔진이다.
CREATE TABLE user_status ㅓ( user_id UInt64, status String, version UInt64) ENGINE = ReplacingMergeTree(version)ORDER BY user_id;- 문제는 유지의 시점
- 기본적으로 중복 제거는 INSERT 시점이 아니라 Merge 시점에 일어남
- Merge 타이밍은 비결정적이므로, 쿼리 시점에 중복이 보일 수 있음
03. FINAL 사용 시점
적합한 상황
- 실시간 최신 상태 조회: 사용자 프로필, 주문 상태 등 “현재 값”이 필요할 때
- 소규모 데이터 조회 : WHERE 조건으로 범위가 충분히 좁혀진 쿼리
- 정확성 > 성능
Trade-off
| 장점 | 단점 |
|---|---|
| 쿼리 레벨에서 데이터 정합성 보장 | 쿼리 성능 저하 (특히 대용량) |
| 애플리케이션 로직 단순화 | 병렬 처리 제한 가능성 |
| Merge 완료 여부와 무관한 일관된 결과 | 메모리 사용량 증가 |
04. FINAL 동작 방식과 성능 특성
내부 동작
FINAL이 붙으면 ClickHouse는:
- 관련된 모든 Part를 읽음
- ORDER BY 키 기준으로 행들을 정렬
- 중복 키에 대해 병합 로직 실행 (버전 비교 등)
- 최종 결과만 반환
┌─────────────────────────────────────────┐│ FINAL 처리 흐름 │├─────────────────────────────────────────┤│ Part1 ──┐ ││ Part2 ──┼──→ [Merge 연산] ──→ 결과 ││ Part3 ──┘ (쿼리 시점) │└─────────────────────────────────────────┘성능 최적화
-- 느림: 전체 테이블 스캔 + FINALSELECT * FROM events FINAL;
-- 빠름: Primary Key 필터링 + FINALSELECT * FROM events FINAL WHERE user_id = 123;파티션 단위 최적화
SET do_not_merge_across_partitions_select_final = 1;05. FINAL 대안들
1. argMax를 활용한 수동 중복 제거
SELECT user_id, argMax(status, version) AS latest_statusFROM user_statusGROUP BY user_id;대용량 분석 쿼리에서 FINAL보다 효율적일 수 있다.
2. OPTIMIZE FINAL 강제 실행
OPTIMIZE TABLE user_status FINAL;백그라운드 Merge를 즉시 강제 실행한다. I/O 부하가 크므로 주의.
3. Materialized View로 사전 집계
INSERT 시점에 집계를 수행해 조회 성능을 확보
| 방법 | 장점 | 단점 |
|---|---|---|
| FINAL | 간단, 정확 | 쿼리 성능 저하 |
| argMax | 대용량에서 빠름 | 쿼리 복잡도 증가 |
| OPTIMIZE FINAL | 이후 조회 빠름 | I/O 부하 |
06. 사용 패턴
Case 1: 단일 사용자 조회 - FINAL 적합
SELECT * FROM user_status FINAL WHERE user_id = 12345;Case 2: 대시보드 집계 - argMax 권장
SELECT status, count() AS cntFROM ( SELECT user_id, argMax(status, version) AS status FROM user_status GROUP BY user_id)GROUP BY status;Concept
- FINAL(clickHouse) : MergeTree 계열 테이블에서 아직 물리적으로 merge되지 않은 중복/삭제/버전을, 쿼리 시점에 강제로 정리해서 읽게하는 키워드
- Part : INSERT된 데이터가 저장되는 불변의 데이터 조각, Merge 전까지 독립 존재
- Merge(clickHouse) : 여러 Part를 하나로 병합하며 중복 제거/삭제 반영하는 백그라운드 작업
- ReplacingMergeTree : 동일 ORDER BY 키의 행 중 최신 버전만 유지하는 clickhouse 엔진
- argMax : 특정 컬럼이 최대값일 때의 다른 컬럼 값을 반환하는 집계 함수
Key Takeaways
- FINAL은 물리적 Merge 없이도 쿼리 시점에 중복/삭제/버전을 정리해서 깨끗한 결과를 반환한다
- 소규모 조회에는 FINAL이 적합하지만, 대용량 분석에서는 argMax 등 대안을 고려해야 한다
- FINAL의 성능은 WHERE 조건으로 범위를 좁힐수록 개선된다.(index 태워야함)
- 정확성 vs 성능 Trade-off에 따라 FINAL, argMax, OPTIMIZE FINAL 중 선택
[clickHouse]Final keyword의 의미
https://yjinheon.netlify.app/posts/02de/db/clickhouse/clickhouse_final/