ClickHouse에서 greatCircleDistance: 전체 테이블 스캔 방지
Source: Dev.to

문제점
위치 데이터를 다룰 때 흔히 묻는 질문은 다음과 같습니다:
데이터베이스에 저장된 두 좌표 사이의 거리를 어떻게 계산할까?
ClickHouse를 사용한다면, 데이터베이스 외부에서 처리할 필요가 없습니다—내장 함수가 제공됩니다.
올바른 도구: greatCircleDistance
greatCircleDistance(lat1, lon1, lat2, lon2)
지구상의 두 지점 사이의 최단 거리를 미터 단위로 반환합니다.
예시
SELECT greatCircleDistance(13.0827, 80.2707, 12.9716, 77.5946) AS distance_meters;
위 쿼리는 첸나이와 방갈로르 사이의 거리를 반환합니다.
간단해 보이지만… 함정이 있습니다
다음과 같은 순진한 쿼리:
SELECT city
FROM locations
WHERE greatCircleDistance(lat, lon, 13.0827, 80.2707) < 5000;
은 괜찮아 보이지만, 대용량 테이블에서는 전체 테이블 스캔을 유발할 수 있습니다.
왜 이런 일이 발생할까
ClickHouse 인덱스는 **희소(sparse)**하며 **범위 프루닝(range pruning)**을 위해 설계되었습니다. 다음과 같은 조건에서는 잘 동작합니다:
WHERE lat BETWEEN x AND y
하지만 다음과 같은 경우에는 동작하지 않습니다:
WHERE greatCircleDistance(lat, lon, x, y) < 5000
함수가 컬럼 값에 직접 적용되기 때문에 ClickHouse는 인덱스를 활용해 데이터를 효율적으로 건너뛸 수 없습니다.
더 나은 접근법 (실제로 해야 할 일)
먼저 바운딩 박스 필터로 데이터셋을 축소한 뒤, 정확한 거리 계산을 적용합니다.
바운딩 박스 필터
SELECT city
FROM locations
WHERE lat BETWEEN (13.0827 - 0.05) AND (13.0827 + 0.05)
AND lon BETWEEN (80.2707 - 0.05) AND (80.2707 + 0.05)
AND greatCircleDistance(lat, lon, 13.0827, 80.2707) < 5000;
바운딩 박스는 정확한 greatCircleDistance 검사를 수행하기 전에 검색 범위를 좁히는 근사값입니다.
왜 이렇게 하면 되는가
lat BETWEEN …→ 인덱스를 사용lon BETWEEN …→ 행을 추가로 감소greatCircleDistance→ 필터링된 부분집합에만 적용
따라서 전체 테이블을 스캔하지 않게 됩니다.
실제 활용 사례
- 배달 반경 필터링
- 인근 사용자 찾기
- 지리 기반 분석
- 라이드셰어링 시스템
한 가지 중요한 주의점
- 좌표는 도(degrees) 단위여야 합니다(라디안이 아님).
- 순서는 항상
(lat, lon)입니다. 순서를 바꾸면 잘못된 결과가 나옵니다.
마무리 생각
greatCircleDistance는 강력하지만 무분별하게 사용하면 성능에 악영향을 줄 수 있습니다. ClickHouse에서는 쿼리 설계가 함수 호출보다 더 중요할 때가 많습니다. 인덱스 친화적인 사전 필터 이후에 greatCircleDistance를 언제, 어떻게 적용할지 아는 것이 효율적이고 확장 가능한 지리 쿼리를 보장합니다.