소개
- 전체 재정렬
- 원본 테이블의 일부를 다른 순서로 재정렬한 형태
- 사전 계산된 집계(materialized view와 유사함)이지만, 집계에 맞게 정렬된 형태
PROJECTION은 어떻게 작동합니까?
- 프라이머리 인덱스를 적절히 활용
- 집계를 미리 계산
_part_offset를 활용한 더 스마트한 스토리지
_part_offset을 지원하며,
이를 통해 프로젝션을 정의하는 새로운 방식을 제공합니다.
이제 프로젝션을 정의하는 방법은 두 가지입니다.
- 전체 컬럼 저장(기존 방식): 프로젝션에 전체 데이터가 포함되므로 직접 읽을 수 있으며, 필터가 프로젝션의 정렬 키 순서와 일치할 때 더 빠른 성능을 제공합니다.
-
정렬 키 +
_part_offset만 저장: 프로젝션이 인덱스처럼 동작합니다. ClickHouse는 프로젝션의 프라이머리 인덱스를 사용해 일치하는 행을 찾지만, 실제 데이터는 기본 테이블에서 읽습니다. 이 방식은 스토리지 오버헤드를 줄이는 대신 쿼리 시점에 I/O가 약간 더 늘어납니다.
_part_offset을 통해 간접적으로 참조할 수 있습니다.
프로젝션은 언제 사용해야 하나요?
- 프로젝션은 원본 테이블과 (숨겨진) 대상 테이블에 서로 다른 TTL을 적용할 수 없지만, 구체화된 뷰는 서로 다른 TTL을 적용할 수 있습니다.
- 프로젝션이 있는 테이블에서는 경량 업데이트와 삭제가 지원되지 않습니다.
- 구체화된 뷰는 체인으로 연결할 수 있습니다. 하나의 구체화된 뷰의 대상 테이블이 다른 구체화된 뷰의 원본 테이블이 될 수 있으며, 이런 방식으로 계속 이어갈 수 있습니다. 프로젝션에서는 이것이 불가능합니다.
- 프로젝션 정의는 조인을 지원하지 않지만, 구체화된 뷰는 지원합니다. 그러나 프로젝션이 있는 테이블에 대한 쿼리에서는 조인을 자유롭게 사용할 수 있습니다.
- 프로젝션 정의는 필터(
WHERE절)를 지원하지 않지만, 구체화된 뷰는 지원합니다. 그러나 프로젝션이 있는 테이블에 대한 쿼리에서는 자유롭게 필터를 적용할 수 있습니다.
- 데이터의 전체 재정렬이 필요한 경우입니다. 프로젝션의 표현식은 이론적으로
GROUP BY,를 사용할 수 있지만, 집계를 유지하는 용도에는 구체화된 뷰가 더 효과적입니다. 또한 쿼리 최적화기는 단순한 재정렬을 사용하는 프로젝션, 즉SELECT * ORDER BY x를 활용할 가능성이 더 높습니다. 저장 공간 사용량을 줄이기 위해 이 표현식에서 컬럼 일부만 선택할 수도 있습니다. - 저장 공간 사용량이 늘어날 가능성과 데이터를 두 번 기록하는 오버헤드를 감수할 수 있는 경우입니다. 삽입 속도에 미치는 영향을 테스트하고 저장 공간 오버헤드도 평가하세요.
예시
프라이머리 키에 없는 컬럼으로 필터링하기
pickup_datetime 기준으로 정렬되어
있습니다.
승객이 기사에게 $200를 초과하는 팁을 준 모든 이동의 trip ID를 찾는 간단한 쿼리를 작성해 보겠습니다:
ORDER BY에 없는 tip_amount를 기준으로 필터링하므로 ClickHouse는
테이블 전체를 스캔해야 했습니다. 이 쿼리를 더 빠르게 만들어 보겠습니다.
원본 테이블과 결과를 유지하기 위해 새 테이블을 만들고 INSERT INTO SELECT를 사용해 데이터를 복사하겠습니다:
ALTER TABLE 문과 함께 ADD PROJECTION
문을 사용합니다:
MATERIALIZE PROJECTION
문을 사용해야 합니다:
system.query_log 테이블을 조회하여 확인할 수 있습니다:
PROJECTION을 사용해 영국 부동산 실거래가 쿼리 속도 높이기
town과 price가
ORDER BY 구문에 포함되지 않았기 때문에 3,003만 개 전체 행을 대상으로 한
전체 테이블 스캔이 발생했다는 점에 유의하십시오:
INSERT INTO SELECT로 데이터를 복사합니다:
prj_oby_town_price를 만들고 데이터를 채워, 특정 town에서 가장 높은 거래 가격의 county를 나열하는 쿼리를
최적화합니다:
mutations_sync 설정은
동기식 실행을 강제하는 데 사용됩니다.
PROJECTION prj_gby_county를 생성하고 데이터를 채웁니다. 이 PROJECTION은
기존 영국의 130개 카운티 전체에 대해 avg(price) 집계 값을 점진적으로 미리 계산하는
추가적인 (숨겨진) 테이블입니다:
위의
prj_gby_county PROJECTION과 같이 PROJECTION에서 GROUP BY 절을 사용하면,
(숨겨진) 테이블의 기반 스토리지 엔진이 AggregatingMergeTree가 되고,
모든 집계 함수는 AggregateFunction으로 변환됩니다. 이렇게 하면
점진적인 데이터 집계가 올바르게 수행됩니다.uk_price_paid_with_projections
과 두 개의 PROJECTION을 시각화한 것입니다:
이제 가장 높은 거래 가격 3건에 해당하는 런던의 카운티를 나열하는
쿼리를 다시 실행하면, 쿼리 성능이 향상된 것을 확인할 수 있습니다:
마찬가지로, 평균 거래 가격이 가장 높은 영국 카운티 3개를 나열하는
쿼리도 있습니다:
두 쿼리 모두 원본 테이블을 대상으로 하며, 두 프로젝션을 생성하기 전에는
두 쿼리 모두 전체 테이블 스캔이 발생했다는 점에 유의하십시오
(총 3,003만 개의 행이 디스크에서 스트리밍되었습니다).
또한, 가장 높은 가격 3건에 해당하는 London의 카운티를 나열하는
쿼리에서는 217만 개의 행이 스트리밍된다는 점에도 유의하십시오. 이 쿼리에
최적화된 두 번째 테이블을 직접 사용했을 때는 디스크에서
8만 1,920개의 행만 스트리밍되었습니다.
이 차이가 발생하는 이유는 위에서 언급한 optimize_read_in_order
최적화가 현재 프로젝션에서는 지원되지 않기 때문입니다.
system.query_log 테이블을 확인하면 ClickHouse가
위의 두 쿼리에 대해 두 프로젝션을 자동으로 사용했음을 알 수 있습니다(아래의
projections 컬럼 참조):
추가 예시
CREATE AS와 INSERT INTO SELECT를 사용해 테이블의 복사본을 생성합니다.
Projection 만들기
toYear(date), district, town을 기준으로 집계 Projection을 생성하겠습니다:
optimize_use_projections을 비활성화하십시오.
쿼리 1. 연도별 평균 가격
쿼리 2. 런던의 연도별 평균 가격
쿼리 3. 가장 비싼 지역
(date >= '2020-01-01')은 프로젝션 차원인 toYear(date) >= 2020에 맞게 수정해야 합니다:
이번에도 결과는 동일하지만, 2번째 쿼리의 성능이 향상된 점을 확인할 수 있습니다.
하나의 쿼리에서 프로젝션 결합하기
_part_offset 지원을 바탕으로, ClickHouse는 이제 여러 필터가 있는 단일 쿼리를 가속하기 위해 여러 프로젝션을 사용할 수 있습니다.
중요한 점은 ClickHouse가 여전히 하나의 프로젝션(또는 기본 테이블)에서만 데이터를 읽지만,
읽기 전에 다른 프로젝션의 프라이머리 인덱스를 사용해 불필요한 파트를 프루닝할 수 있다는 점입니다.
이는 특히 여러 컬럼을 기준으로 필터링하는 쿼리에서 유용하며, 각 컬럼이
서로 다른 프로젝션에 대응될 수 있을 때 더욱 효과적입니다.
현재 이 메커니즘은 전체 파트만 프루닝합니다. granule 수준의 프루닝은 아직 지원되지 않습니다.이를 보여주기 위해
_part_offset 컬럼을 사용하는 프로젝션이 포함된 테이블을 정의하고,
위 다이어그램에 맞는 예시 행 5개를 삽입합니다.
참고: 이 테이블은 예시를 위해 한 행짜리 그래뉼, 비활성화된 파트 병합 등 사용자 지정 설정을 사용합니다.
이러한 설정은 프로덕션 환경에서는 권장되지 않습니다.
- 5개의 개별 파트(삽입된 각 행마다 1개)
- 각 행마다 하나의 프라이머리 인덱스 엔트리(기본 테이블과 각 프로젝션에 각각)
- 각 파트에는 정확히 하나의 행만 포함됨
region과 user_id를 모두 기준으로 필터링하는 쿼리를 실행합니다.
기본 테이블의 프라이머리 인덱스는 event_date와 id를 기준으로 구축되므로 여기서는
도움이 되지 않습니다. 따라서 ClickHouse는 다음을 사용합니다:
region_proj를 사용해 region별로 파트를 가지치기user_id_proj를 사용해user_id별로 파트를 추가로 가지치기
EXPLAIN projections = 1로 확인할 수 있으며, 이를 통해
ClickHouse가 프로젝션을 선택하고 적용하는 방식을 보여줍니다.
EXPLAIN 출력(위에 표시됨)은 위에서 아래 순서로 논리적 쿼리 계획을 보여줍니다:
결과적으로 기본 테이블에서는 5개 파트 중 1개만 읽습니다.
여러 프로젝션의 인덱스 분석을 결합하면 ClickHouse는 스캔하는 데이터 양을 크게 줄여
스토리지 오버헤드는 낮게 유지하면서 성능을 향상합니다.