PREWHERE 최적화 없이 쿼리가 처리되는 방식
① 이 쿼리에는
town 컬럼에 대한 필터가 포함되어 있으며, 이 컬럼은 테이블의 프라이머리 키에 포함되므로 프라이머리 인덱스의 일부이기도 합니다.
② 쿼리 속도를 높이기 위해 ClickHouse는 테이블의 프라이머리 인덱스를 메모리에 로드합니다.
③ 인덱스 엔트리를 스캔해 town 컬럼의 그래뉼 중 프레디케이트와 일치하는 행을 포함할 가능성이 있는 그래뉼을 식별합니다.
④ 이렇게 관련 있을 가능성이 있는 그래뉼을 메모리에 로드하고, 쿼리에 필요한 다른 컬럼에서도 같은 위치에 정렬된 그래뉼을 함께 로드합니다.
⑤ 그런 다음 쿼리 실행 중에 나머지 필터를 적용합니다.
보시는 것처럼 PREWHERE가 없으면 실제로 일치하는 행이 몇 개 되지 않더라도 필터링 전에 관련 있을 가능성이 있는 모든 컬럼을 먼저 로드합니다.
PREWHERE가 쿼리 효율을 개선하는 방식
① 쿼리에는
town 컬럼에 대한 필터가 포함되어 있으며, 이 컬럼은 테이블의 프라이머리 키에 속하므로 프라이머리 인덱스에도 포함됩니다.
② PREWHERE 절 없이 실행할 때와 마찬가지로, 쿼리 속도를 높이기 위해 ClickHouse는 프라이머리 인덱스를 메모리에 로드하고,
③ 이어서 인덱스 엔트리를 스캔해 town 컬럼의 어떤 그래뉼에 프레디케이트와 일치하는 행이 있을 수 있는지 식별합니다.
이제 PREWHERE 절 덕분에 다음 단계는 달라집니다. 관련된 모든 컬럼을 한 번에 읽는 대신, ClickHouse는 컬럼별로 데이터를 필터링하면서 실제로 필요한 데이터만 로드합니다. 이렇게 하면 특히 컬럼 수가 많은 테이블에서 I/O를 크게 줄일 수 있습니다.
각 단계에서는 이전 필터를 통과한, 즉 일치하는 행이 하나 이상 들어 있는 그래뉼만 로드합니다. 따라서 각 필터에서 로드하고 평가해야 하는 그래뉼 수는 단계가 진행될수록 단조 감소합니다:
1단계: town으로 필터링ClickHouse는 ①
town 컬럼에서 선택된 그래뉼을 읽고, 그중 실제로 London과 일치하는 행이 들어 있는 그래뉼을 확인하는 것으로 PREWHERE 처리를 시작합니다.
이 예시에서는 선택된 모든 그래뉼이 일치하므로, ② 다음 필터 컬럼인 date에서 위치상 대응되는 그래뉼이 처리 대상으로 선택됩니다:
2단계: date로 필터링
다음으로 ClickHouse는 ① 선택된
date 컬럼 그래뉼을 읽어 date > '2024-12-31' 필터를 평가합니다.
이 경우 3개의 그래뉼 중 2개에 일치하는 행이 포함되어 있으므로, ② 다음 필터 컬럼인 price에서 위치상 대응되는 그래뉼만 후속 처리 대상으로 선택됩니다:
3단계: price로 필터링
마지막으로 ClickHouse는 ①
price 컬럼에서 선택된 2개의 그래뉼을 읽어 마지막 필터 price > 10_000을 평가합니다.
2개의 그래뉼 중 1개에만 일치하는 행이 포함되어 있으므로, ② SELECT 컬럼인 street에서 위치상 대응되는 그래뉼만 추가 처리를 위해 로드하면 됩니다:
최종 단계에서는 일치하는 행이 포함된 최소한의 컬럼 그래뉼만 로드됩니다. 그 결과 메모리 사용량이 줄고, 디스크 I/O가 감소하며, 쿼리 실행 속도도 빨라집니다.
PREWHERE는 읽는 데이터만 줄이며, 처리하는 행 수는 줄이지 않습니다PREWHERE를 적용한 쿼리와 적용하지 않은 쿼리에서 ClickHouse가 처리하는 행 수는 동일합니다. 하지만 PREWHERE 최적화를 적용하면 처리되는 모든 행에 대해 모든 컬럼 값을 로드할 필요는 없습니다.
PREWHERE 최적화는 자동으로 적용됩니다
optimize_move_to_prewhere 설정이 활성화되면(기본값은 true) ClickHouse는 WHERE의 필터 조건을 PREWHERE로 자동 이동하며, 읽기량을 가장 크게 줄일 수 있는 조건을 우선적으로 선택합니다.
핵심은 크기가 작은 컬럼일수록 더 빠르게 스캔할 수 있고, 크기가 큰 컬럼을 처리할 때쯤이면 대부분의 그래뉼이 이미 걸러져 있다는 점입니다. 모든 컬럼은 동일한 수의 행을 가지므로, 컬럼의 크기는 주로 데이터 타입에 따라 결정됩니다. 예를 들어 UInt8 컬럼은 일반적으로 String 컬럼보다 훨씬 작습니다.
ClickHouse는 버전 23.2부터 기본적으로 이 전략을 따르며, 다단계 처리를 위해 PREWHERE 필터 컬럼을 압축되지 않은 크기의 오름차순으로 정렬합니다.
버전 23.11부터는 선택적 컬럼 통계(column statistics)를 사용해 단순히 컬럼 크기뿐 아니라 실제 데이터 선택도를 기준으로 필터 처리 순서를 정함으로써 이를 더욱 개선할 수 있습니다.
PREWHERE 영향 측정 방법
optimize_move_to_prewhere 설정을 활성화했을 때와 비활성화했을 때의 쿼리 성능을 비교하면 됩니다.
먼저 optimize_move_to_prewhere 설정을 비활성화한 상태에서 쿼리를 실행합니다:
optimize_move_to_prewhere 설정을 활성화한 상태로 쿼리를 실행합니다. (이 설정은 기본적으로 활성화되어 있으므로 선택적으로 사용할 수 있습니다):
핵심 사항
- PREWHERE는 나중에 필터링으로 제외될 컬럼 데이터를 미리 읽지 않아 I/O와 메모리를 절약합니다.
optimize_move_to_prewhere가 활성화되어 있으면(기본값) 자동으로 적용됩니다.- 필터링 순서는 중요합니다. 크기가 작고 선택도가 높은 컬럼을 먼저 배치해야 합니다.
EXPLAIN과 로그를 사용해 PREWHERE가 적용되었는지 확인하고, 그 효과를 파악하십시오.- PREWHERE는 wide 테이블과 선택적 필터가 있는 대규모 스캔에서 가장 큰 효과를 발휘합니다.