이 가이드에서 설명하는 내용은 GCS, Azure Blob storage처럼 전용 테이블 함수를 제공하는 다른 객체 스토리지 구현에도 적용할 수 있습니다.
삽입 메커니즘(단일 노드)
삽입 블록 크기
INSERT INTO SELECT를 수행할 때 ClickHouse는 일정량의 데이터를 받아, 받은 데이터로부터 ① 파티셔닝 키별로 메모리 내 삽입 블록을 (최소) 하나 생성합니다. 그런 다음 블록의 데이터를 정렬하고, 테이블 엔진별 최적화를 적용합니다. 이후 데이터는 압축되어 ② 새로운 데이터 파트 형태로 데이터베이스 스토리지에 기록됩니다.
삽입 블록 크기는 ClickHouse 서버의 디스크 파일 I/O 사용량과 메모리 사용량 모두에 영향을 줍니다. 삽입 블록이 클수록 더 많은 메모리를 사용하지만, 더 크고 더 적은 수의 초기 파트를 생성합니다. 대량의 데이터를 로드할 때 ClickHouse가 생성해야 하는 파트 수가 적을수록, 디스크 파일 I/O와 자동 백그라운드 머지 필요량이 줄어듭니다.
통합 테이블 엔진 또는 테이블 함수와 함께 INSERT INTO SELECT 쿼리를 사용하면 ClickHouse 서버가 데이터를 가져옵니다:
데이터가 완전히 로드될 때까지 서버는 다음 루프를 실행합니다:
min_insert_block_size_rows(기본값:1048545행)min_insert_block_size_bytes(기본값:256 MiB)
min_insert_block_size_bytes 값은 비압축 메모리 내 블록 크기(압축된 디스크상의 파트 크기가 아님)를 의미한다는 점에 유의하십시오. 또한 ClickHouse는 데이터를 행별 블록 단위로 스트리밍하고 처리하므로, 생성된 블록과 파트에 설정된 행 수나 바이트 수가 정확히 맞아떨어지는 경우는 드뭅니다. 따라서 이러한 설정은 최소 임계값을 지정합니다.
머지에 유의하세요
MergeTree 테이블이라는 이름이 붙었습니다).
삽입 병렬성
s3와 같은 테이블 함수는 글롭 패턴을 사용해 로드할 파일 이름 집합을 지정할 수 있습니다. 글롭 패턴이 여러 기존 파일과 일치하면 ClickHouse는 파일 간 및 파일 내부의 읽기를 병렬화하고, 병렬로 실행되는 삽입 스레드(서버별)를 활용해 데이터를 테이블에 병렬로 삽입할 수 있습니다:
모든 파일의 데이터가 모두 처리될 때까지 각 삽입 스레드는 다음 루프를 실행합니다:
max_insert_threads 설정으로 구성할 수 있습니다. 기본값은 오픈소스 ClickHouse에서 1, ClickHouse Cloud에서는 4입니다.
파일 수가 많을 때는 여러 삽입 스레드를 통한 병렬 처리가 효과적입니다. 이 방식은 사용 가능한 CPU 코어와 네트워크 대역폭(병렬 파일 다운로드 시)까지 모두 충분히 활용할 수 있습니다. 반면 소수의 큰 파일만 테이블에 로드하는 시나리오에서는 ClickHouse가 자동으로 높은 수준의 데이터 처리 병렬성을 확보하고, 큰 파일의 서로 다른 범위를 병렬로 읽기(다운로드)할 수 있도록 각 삽입 스레드마다 추가 읽기 스레드를 생성해 네트워크 대역폭 사용도 최적화합니다.
s3 함수와 테이블의 경우, 개별 파일의 병렬 다운로드는 max_download_threads 및 max_download_buffer_size 값에 따라 결정됩니다. 파일 크기가 2 * max_download_buffer_size보다 큰 경우에만 병렬 다운로드가 수행됩니다. 기본적으로 max_download_buffer_size의 기본값은 10MiB입니다. 경우에 따라 각 파일이 단일 스레드로 다운로드되도록 하기 위해 이 버퍼 크기를 50 MB(max_download_buffer_size=52428800)로 안전하게 늘릴 수 있습니다. 이렇게 하면 각 스레드가 S3 호출에 소비하는 시간을 줄일 수 있고, 결과적으로 S3 대기 시간도 낮출 수 있습니다. 또한 병렬 읽기에 너무 작은 파일의 경우 처리량을 높이기 위해 ClickHouse는 이러한 파일을 비동기적으로 미리 읽어 데이터를 자동으로 프리페치합니다.
성능 측정
하드웨어 크기의 영향
- 지원되는 파트의 초기 크기
- 가능한 삽입 병렬성 수준
- 백그라운드 파트 병합의 처리량
리전 일치
포맷
s3 함수와 S3 엔진을 사용해 지원되는 포맷으로 S3 버킷에 저장된 파일을 읽을 수 있습니다. 원시 파일을 읽는 경우, 이러한 포맷 중 일부는 뚜렷한 장점이 있습니다.
- Native, Parquet, CSVWithNames, TabSeparatedWithNames처럼 컬럼 이름이 인코딩된 포맷은
s3함수에서 컬럼 이름을 지정할 필요가 없으므로 쿼리를 더 간결하게 작성할 수 있습니다. 컬럼 이름을 통해 이 정보를 유추할 수 있기 때문입니다. - 포맷마다 읽기 및 쓰기 처리량 측면의 성능이 다릅니다. Native와 Parquet는 이미 컬럼 지향적이며 더 압축된 구조이므로 읽기 성능에 가장 적합한 포맷입니다. 또한 Native 형식은 ClickHouse가 메모리에 데이터를 저장하는 방식과도 잘 맞기 때문에, 데이터가 ClickHouse로 스트리밍될 때 처리 오버헤드를 줄일 수 있습니다.
- 블록 크기는 큰 파일 읽기의 지연 시간에 자주 영향을 줍니다. 이는 데이터의 일부만 샘플링하는 경우, 예를 들어 상위 N개의 행만 반환할 때 특히 두드러집니다. CSV 및 TSV 같은 포맷은 행 집합을 반환하려면 파일을 파싱해야 합니다. 반면 Native와 Parquet 같은 포맷은 이로 인해 더 빠른 샘플링이 가능합니다.
- 각 압축 포맷에는 장단점이 있으며, 일반적으로 속도와 압축 수준 사이에서 절충이 필요하고 압축 또는 압축 해제 성능 중 한쪽에 더 유리하게 작용합니다. CSV나 TSV 같은 원시 파일을 압축하는 경우, lz4는 압축률을 희생하는 대신 가장 빠른 압축 해제 성능을 제공합니다. Gzip은 일반적으로 읽기 속도가 약간 느려지는 대신 더 높은 압축률을 제공합니다. Xz는 한 걸음 더 나아가 보통 가장 높은 압축률을 제공하지만, 압축 및 압축 해제 성능은 가장 느립니다. 내보내기 시에는 Gz와 lz4가 비슷한 압축 속도를 제공합니다. 이를 연결 속도와 함께 고려하십시오. 압축 또는 압축 해제가 더 빨라서 얻는 이점은 S3 버킷에 대한 연결이 느리면 쉽게 상쇄될 수 있습니다.
- Native나 Parquet 같은 포맷은 일반적으로 압축 오버헤드를 감수할 만큼의 이점을 제공하지 않습니다. 이러한 포맷은 본질적으로 압축된 구조이므로 데이터 크기 절감 효과가 크지 않은 경우가 많습니다. 압축과 압축 해제에 소요되는 시간은 네트워크 전송 시간을 상쇄할 만큼의 이점을 주는 경우가 드뭅니다. 특히 S3는 전 세계에서 사용할 수 있고 네트워크 대역폭도 더 높기 때문입니다.
예시 데이터셋
SELECT 쿼리의 성능을 측정할 때는, 쿼리에 null 포맷을 사용하거나 결과를 Null 엔진으로 보내십시오. 이렇게 하면 클라이언트가 데이터로 인해 과부하되는 상황과 네트워크 포화를 방지할 수 있습니다.
쿼리 결과를 읽을 때는 동일한 쿼리를 반복 실행하는 경우보다 첫 번째 쿼리가 더 느리게 보이는 일이 많습니다. 이는 S3 자체의 Caching뿐 아니라 ClickHouse Schema Inference Cache의 영향이기도 합니다. 이 캐시는 파일에 대해 추론된 스키마를 저장하므로, 이후에 접근할 때는 추론 단계를 건너뛸 수 있어 쿼리 시간을 줄일 수 있습니다.
읽기 작업에 스레드 사용
- 일반적으로
max_threads의 기본값, 즉 코어 수면 충분합니다. 쿼리에서 사용하는 메모리 양이 많아 이를 줄여야 하거나 결과에 대한LIMIT가 작다면 이 값을 더 낮게 설정할 수 있습니다. 메모리가 충분하다면 S3에서 더 높은 읽기 처리량을 얻기 위해 이 값을 늘려 실험해볼 수 있습니다. 보통 이는 코어 수가 적은 머신, 즉 < 10인 경우에만 유리합니다. 추가 병렬화의 이점은 대개 다른 리소스가 병목이 되면서 점차 줄어듭니다. 예를 들어 네트워크와 CPU 경합이 있습니다. - 22.3.1 이전 버전의 ClickHouse는
s3함수 또는S3테이블 엔진을 사용할 때 여러 파일에 걸친 읽기만 병렬화했습니다. 따라서 최적의 읽기 성능을 얻으려면 사용자가 파일을 S3에서 청크로 분할하고, 글롭 패턴을 사용해 읽도록 구성해야 했습니다. 이후 버전에서는 파일 내부 다운로드도 병렬화합니다. - 스레드 수가 적은 환경에서는
remote_filesystem_read_method를 “read”로 설정해 S3에서 파일을 동기식으로 읽도록 하면 도움이 될 수 있습니다. - s3 함수와 테이블의 경우, 개별 파일의 병렬 다운로드는
max_download_threads및max_download_buffer_size값에 따라 결정됩니다.max_download_threads는 사용하는 스레드 수를 제어하지만, 파일 크기가 2 *max_download_buffer_size보다 클 때만 파일이 병렬로 다운로드됩니다. 기본적으로max_download_buffer_size의 기본값은 10MiB입니다. 경우에 따라 더 작은 파일이 단일 스레드로만 다운로드되도록 하기 위해 이 버퍼 크기를 50 MB(max_download_buffer_size=52428800)까지 안전하게 늘릴 수 있습니다. 이렇게 하면 각 스레드가 S3 호출에 소비하는 시간을 줄일 수 있고, 결과적으로 S3 대기 시간도 낮출 수 있습니다. 이에 대한 예시는 이 블로그 게시물을 참조하십시오.
system.query_log를 사용해 확인하십시오.
앞서의 쿼리 예시에서 max_threads를 16으로 두 배 늘리면(기본 max_thread는 노드의 코어 수임) 메모리 사용량이 증가하는 대신 읽기 쿼리 성능이 2배 향상됩니다. 하지만 그림에서 보듯 max_threads를 더 늘려도 효과는 점차 줄어듭니다.
삽입을 위한 스레드 및 블록 크기 튜닝
- 삽입 블록 크기를 크게 설정할수록 ClickHouse가 생성해야 하는 파트 수가 줄어들며, 필요한 디스크 파일 I/O와 백그라운드 머지도 줄어듭니다.
- 병렬 삽입 스레드 수를 많이 설정할수록 데이터가 더 빠르게 처리됩니다.
min_insert_block_size_rows를 0으로 설정해(행 기반 임계값을 비활성화) max_insert_threads는 선택한 값으로, min_insert_block_size_bytes는 위 공식으로 계산한 값으로 설정할 수 있습니다.
이전에 살펴본 Stack Overflow 예시에 이 공식을 적용하면 다음과 같습니다.
max_insert_threads=4(노드당 8개 코어)peak_memory_usage_in_bytes- 32 GiB (노드 리소스의 100%) 또는34359738368바이트.min_insert_block_size_bytes=34359738368/(3*4) = 2863311530
33% 이상 개선되었습니다. 단일 노드 성능을 더 향상시킬 수 있는지는 직접 확인해 보시기 바랍니다.
리소스와 노드를 통한 스케일링
수직 스케일링
개별 노드도 네트워크와 S3 GET 요청으로 인해 병목이 생길 수 있어, 수직 스케일링을 하더라도 성능이 선형적으로 향상되지 않을 수 있습니다.
수평 스케일링
s3Cluster 함수를 사용해야 합니다. 이렇게 하면 읽기가 여러 노드에 분산됩니다.
처음에 삽입 쿼리를 수신한 서버는 먼저 glob 패턴을 확인한 다음, 일치하는 각 파일의 처리를 자신과 다른 서버에 동적으로 분배합니다.
앞서 사용한 읽기 쿼리를 다시 사용하되, 작업 부하를 3개의 노드에 분산하도록 s3Cluster를 사용하도록 쿼리를 조정합니다. ClickHouse Cloud에서는 default 클러스터를 참조하면 이 작업이 자동으로 수행됩니다.
클러스터 활용에서 언급했듯이 이 작업은 파일 수준에서 분산됩니다. 이 기능의 이점을 얻으려면 충분한 수의 파일, 즉 최소한 노드 수보다 많은 파일이 필요합니다.
s3Cluster를 사용해 분산되지만, 삽입은 initiator 노드에서 수행됩니다. 즉, 읽기는 각 노드에서 수행되지만, 결과 행은 분산을 위해 initiator로 전달됩니다. 고처리량 시나리오에서는 이것이 병목이 될 수 있습니다. 이를 해결하려면 s3cluster 함수에 대해 매개변수 parallel_distributed_insert_select를 설정하십시오.
이를 parallel_distributed_insert_select=2로 설정하면 각 노드에서 Distributed 엔진의 기반 테이블에 대해 각 세그먼트별로 SELECT와 INSERT가 실행됩니다.
추가 조정
중복 제거 비활성화
INSERT INTO SELECT를 수행할 때는 불필요할 수 있습니다. 삽입 시 이 기능을 비활성화하면 아래와 같이 성능을 향상시킬 수 있습니다:
삽입 시 최적화
optimize_on_insert 설정은 삽입 과정에서 데이터 파트를 머지할지 여부를 제어합니다. 이 설정이 활성화되어 있으면(기본값: optimize_on_insert = 1) 작은 파트가 삽입되는 동안 더 큰 파트로 머지되어, 읽어야 할 파트 수가 줄어들고 그에 따라 쿼리 성능이 향상됩니다. 하지만 이 머지 작업은 삽입 과정에 오버헤드를 추가하므로, 처리량이 높은 삽입 작업에서는 속도가 저하될 수 있습니다.
이 설정을 비활성화하면(optimize_on_insert = 0) 삽입 중 머지를 수행하지 않으므로, 특히 작은 삽입이 자주 발생하는 경우 데이터를 더 빠르게 기록할 수 있습니다. 머지 과정은 백그라운드로 미뤄지므로 삽입 성능은 개선되지만, 일시적으로 작은 파트 수가 증가해 백그라운드 머지가 완료될 때까지 쿼리 성능이 저하될 수 있습니다. 이 설정은 삽입 성능이 우선이고, 이후 백그라운드 머지 프로세스가 최적화를 효율적으로 처리할 수 있는 경우에 적합합니다. 아래와 같이, 이 설정을 비활성화하면 삽입 처리량이 향상될 수 있습니다:
기타 참고 사항
- 메모리가 부족한 환경에서는 S3에 삽입할 때
max_insert_delayed_streams_for_parallel_write값을 낮추는 것을 고려하세요.