실시간 분석데이터 웨어하우징관측성AI/MLCloudOss
사전 요구사항
- A running ClickHouse Cloud service. If you don’t have one yet, complete the Create your first Cloud service quickstart first.
만들어 볼 내용
ORDER BY와 PARTITION BY를 선택하고, S3에서 직접 데이터를 로드한 다음, system.parts를 쿼리해 ClickHouse가 디스크에 데이터를 물리적으로 어떻게 구성하는지 확인합니다.
이 과정을 마치면 MergeTree 엔진이 거의 모든 ClickHouse 테이블의 기반인 이유와, 정렬 및 파티셔닝 방식의 선택이 쿼리 성능에 어떤 영향을 주는지 이해하게 됩니다.
MergeTree의 작동 방식 이해하기
SQL을 작성하기 전에 MergeTree가 기존 데이터베이스 테이블과 어떻게 다른지 알아두면 도움이 됩니다.MergeTree 테이블에 데이터를 삽입할 때 ClickHouse는 행을 하나씩 쓰지 않습니다. 대신 정렬되고 압축된 작은 행 묶음인 **데이터 파트(data part)**를 디스크에 직접 씁니다. 그런 다음 ClickHouse는 시간이 지나면서 백그라운드에서 이러한 파트들을 머지합니다. 이름도 여기서 유래했습니다: merge + tree.모든 데이터 파트는 테이블의ORDER BY 표현식에 따라 정렬됩니다. 이 정렬 순서는 기본 키(primary key) 인덱스가 되며, 그 결과 ClickHouse는 쿼리 실행 중 읽을 필요가 없는 큰 데이터 블록을 건너뛸 수 있습니다(이를 데이터 프루닝(data pruning)이라고 합니다). 가장 자주 사용하는 쿼리에서 ORDER BY 컬럼의 선택성이 높을수록 ClickHouse가 읽는 데이터의 양은 줄어듭니다.다음 세 가지 절이 MergeTree에서 데이터를 구성하는 방식을 제어합니다:| 절 | 역할 |
|---|---|
ORDER BY | 각 파트 내부의 데이터를 물리적으로 정렬합니다. 기본 키를 결정합니다. 필수입니다. |
PARTITION BY | 데이터를 별도의 파티션으로 나눕니다. 일반적으로 날짜 범위를 기준으로 합니다. 서로 다른 파티션의 파트는 함께 머지되지 않으므로 빠른 파티션 프루닝이 가능합니다. |
PRIMARY KEY | 명시적으로 더 짧은 접두사를 설정하지 않으면 기본값은 ORDER BY입니다. 희소 인덱스는 이를 기반으로 생성됩니다. |
소스 데이터 미리 보기
테이블을 생성하기 전에s3 테이블 함수를 사용해 소스 파일을 미리 확인합니다. 이렇게 하면 데이터를 먼저 ClickHouse에 쓰지 않고도 S3에 직접 쿼리할 수 있습니다.SQL 콘솔에서 다음을 실행하세요:Nullable(String)으로 추론된다는 점에 유의하십시오. ClickHouse는 원본 CSV를 읽고 있으므로 실제 데이터 타입을 알 수 없습니다. 이는 다음 단계에서 테이블 스키마를 설계하면서 바로잡게 됩니다.몇 개의 행을 미리 살펴보겠습니다:id, 판매 price, date, 부동산 type, 주소 필드, 지리 식별자 등이 들어 있습니다. 끝부분의 두 개 컬럼(column15, column16)이 비어 있는 것도 확인할 수 있는데, 이는 무시해도 됩니다.id, price, date, postcode, type, town, county 등의 컬럼이 포함된 행이 표시되는지 확인하여 이를 검증하십시오.MergeTree 테이블 설계 및 생성
이제 적절한 스키마로 영구 테이블을 생성합니다. 아래 컬럼 타입은 의도적으로 선택한 것입니다.LowCardinality(String)은 고유값 수가 적은 컬럼(우편번호, 도시명, 카운티명)에 사용됩니다. 내부적으로 딕셔너리 인코딩을 사용하므로, 이러한 컬럼을 그룹화하거나 필터링할 때 스토리지 사용량을 크게 줄이고 성능도 향상됩니다.Enum8은type및duration컬럼을 디스크에 작은 정수로 인코딩하면서도, 쿼리에서는 사람이 읽기 쉬운 문자열 레이블을 유지합니다. 원본 CSV는 한 글자 코드를 사용하므로,insert시 이 값을 매핑합니다.PARTITION BY toYYYYMM(date)는 달력 기준으로 매월 하나의 파티션을 생성하므로,WHERE절이date를 기준으로 필터링할 때 ClickHouse가 해당 월 전체를 건너뛸 수 있습니다.ORDER BY (postcode, addr1, addr2)는 속성 주소 기준의 빠른 조회를 지원하도록 데이터를 정렬합니다. 이는 이 데이터셋에서 가장 자연스러운 접근 패턴입니다.
ENGINE = MergeTree를 지정했지만, ClickHouse Cloud는 테이블을 SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')로 생성했다는 점에 유의하십시오. 이는 예상된 동작입니다. Cloud는 MergeTree를 자동으로 SharedMergeTree로 변환하여 복제 및 공유 스토리지 지원을 추가합니다. 동작과 쿼리 인터페이스는 동일하게 유지됩니다.S3에서 데이터 로드하기
s3() 테이블 함수를 직접 조회하여 전체 데이터를 삽입합니다. ClickHouse는 S3의 압축 파일을 스트리밍하여 정렬된 파트로 테이블에 기록합니다.T, 자유보유권은 F, 신축은 Y/N), transform을 사용해 사람이 읽기 쉬운 레이블로 매핑하고 toUInt32/if를 사용해 숫자 컬럼을 형변환합니다. id, column15, column16 컬럼은 필요하지 않으므로 제외합니다.이 작업은 service 크기에 따라 1~2분 정도 걸릴 수 있습니다. 완료되면 행 수를 확인하십시오:system.parts를 사용해 파트 살펴보기
이 지점에서 MergeTree의 내부 구조가 드러납니다.system.parts 테이블은 서비스 내 모든 MergeTree 테이블에서 디스크에 저장된 각 데이터 파트를 추적합니다.partition-PARTITION BY표현식에서 파생된YYYYMM값입니다. 각 월의 데이터는 서로 분리되어 저장됩니다.name- 파트 이름에는 파티션, 블록 번호 범위, 머지 수준이 인코딩됩니다(예:199501_1_4_2는 파티션199501, 블록 1–4, 2번 머지되었음을 의미합니다).marks- 인덱스 그래뉼 수입니다. 각 그래뉼은 기본적으로 8,192개의 행을 포함하며, 프라이머리 키 인덱스는 그래뉼마다 항목 1개를 저장합니다. 이 희소 인덱스가 메모리에 유지되어 빠른 데이터 스키핑이 가능합니다.bytes_on_disk- ClickHouse는 기본적으로 각 파트를 컬럼별로 LZ4를 사용해 압축합니다. 압축률을 체감하려면 원본 크기와 비교해 보십시오.
active = true 필터를 사용하면 정리 대기 중인 이전 파트가 아니라 현재의 병합된 파트만 볼 수 있습니다.데이터를 쿼리해 기본 키(primary key) 동작 살펴보기
이제 실제 분석용 쿼리를 실행합니다. 먼저, 지금까지 기록된 판매 내역 중 가장 고가의 판매를 찾아봅니다:price가 ORDER BY 키에 포함되지 않으므로 ClickHouse는 프라이머리 인덱스를 사용해 데이터를 건너뛸 수 없고, 전체 테이블 스캔을 수행해야 합니다.다음으로, 카운티별 평균 판매 가격을 구합니다:county는 ORDER BY나 PARTITION BY에 포함되어 있지 않으므로 ClickHouse는 테이블 전체를 스캔합니다.이제 집계와 ORDER BY를 결합한 쿼리를 실행합니다. 데이터가 (postcode, addr1, addr2) 순으로 정렬되어 있으므로 postcode 접두사로 필터링하면 ClickHouse가 테이블 대부분을 건너뛸 수 있습니다. 여기서는 SW1A postcode 지역 부동산의 연도별 평균 매매가를 구합니다:postcode로 필터링한 집계는 테이블의 행 중 일부만 읽어야 하며, 이를 통해 프라이머리 키 인덱스가 실제로 작동하는 것을 확인할 수 있습니다. 이를 더 광범위하게 스캔하는 앞선 쿼리와 비교해 보세요. 이 차이를 보면 적절한 ORDER BY를 선택하는 것이 왜 중요한지 알 수 있습니다.