> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-home-button.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ClickHouse는 처리 레인과 max_threads 설정을 사용해 쿼리 실행을 병렬화합니다.

# ClickHouse에서 쿼리를 병렬로 실행하는 방식

export const Image = ({img, alt, size}) => {
  return <Frame>
      <img src={img} alt={alt} />
    </Frame>;
};

ClickHouse는 [속도를 위해 설계되었습니다](/ko/get-started/about/why-clickhouse-is-so-fast). 쿼리를 매우 높은 수준의 병렬 방식으로 실행하며, 사용 가능한 모든 CPU 코어를 활용하고, 데이터를 처리 레인에 분산하고, 하드웨어 성능을 한계에 가깝게 끌어올리는 경우가 많습니다.

이 가이드에서는 ClickHouse에서 쿼리 병렬성이 어떻게 작동하는지, 그리고 대규모 워크로드에서 성능을 개선하기 위해 이를 어떻게 조정하거나 모니터링할 수 있는지 설명합니다.

핵심 개념을 설명하기 위해 [uk\_price\_paid\_simple](/ko/concepts/core-concepts/parts) 데이터셋에 대한 집계 쿼리를 사용합니다.

<div id="step-by-step-how-clickHouse-parallelizes-an-aggregation-query">
  ## 단계별: ClickHouse가 집계 쿼리를 병렬화하는 방법
</div>

ClickHouse는 ① 테이블의 프라이머리 키(primary key)에 대한 필터가 있는 집계 쿼리를 실행할 때, ② 어떤 그래뉼을 처리해야 하고 어떤 그래뉼을 안전하게 건너뛸 수 있는지 식별하기 위해 ③ 프라이머리 인덱스(primary index)를 메모리에 로드합니다:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/query-parallelism_01.gif?s=0a51815a8e532210ab3b5b54dbb5d1df" size="md" alt="인덱스 분석" width="1079" height="1004" data-path="images/guides/best-practices/query-parallelism_01.gif" />

<div id="distributing-work-across-processing-lanes">
  ### 처리 레인 간 작업 분산
</div>

선택한 데이터는 이후 `n`개의 병렬 [처리 레인](/ko/concepts/core-concepts/academic-overview#4-2-multi-core-parallelization)에 [동적으로](#load-balancing-across-processing-lanes) 분산되며, 각 레인은 데이터를 [블록](/ko/resources/develop-contribute/introduction/architecture#block) 단위로 스트리밍하고 처리해 최종 결과를 생성합니다:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/query-parallelism_02.gif?s=b7c9808d0ba1bc8d4b44ce9bb7a2e509" size="md" alt="4개의 병렬 처리 레인" width="3600" height="2025" data-path="images/guides/best-practices/query-parallelism_02.gif" />

<br />

<br />

`n`개의 병렬 처리 레인 수는 [`max_threads`](/ko/reference/settings/session-settings#max_threads) 설정으로 제어되며, 기본적으로 서버에서 ClickHouse가 사용할 수 있는 단일 CPU의 코어 수(스레드 수)와 일치합니다. 위 예시에서는 `4`개의 코어를 가정합니다.

`8`개의 코어가 있는 머신에서는 더 많은 레인이 데이터를 병렬로 처리하므로 쿼리 처리량이 대략 2배로 증가합니다(메모리 사용량도 그에 따라 함께 증가합니다):

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/query-parallelism_03.gif?s=2c5d6723a17bac0c09c1f2cc59f1b191" size="md" alt="8개의 병렬 처리 레인" width="3600" height="2025" data-path="images/guides/best-practices/query-parallelism_03.gif" />

<br />

<br />

효율적으로 레인을 분산하는 것은 CPU 활용도를 극대화하고 전체 쿼리 시간을 줄이는 데 중요합니다.

<div id="processing-queries-on-sharded-tables">
  ### 세그먼트된 테이블에서 쿼리 처리하기
</div>

테이블 데이터가 여러 서버에 [세그먼트](/ko/guides/oss/deployment-and-scaling/shards)로 분산되어 있으면 각 서버는 자신의 세그먼트를 병렬로 처리합니다. 각 서버 내부에서는 앞서 설명한 대로 로컬 데이터가 병렬 처리 레인을 통해 처리됩니다:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/query-parallelism_04.gif?s=d10cec0888985a43ff9e926363426c06" size="md" alt="분산 레인" width="1788" height="2160" data-path="images/guides/best-practices/query-parallelism_04.gif" />

<br />

<br />

처음 쿼리를 받은 서버는 세그먼트에서 나온 모든 하위 결과를 수집해 최종 전역 결과로 결합합니다.

쿼리 부하를 세그먼트 전체에 분산하면 병렬성을 수평으로 확장할 수 있으며, 특히 처리량이 높은 환경에서 효과적입니다.

<Info>
  **ClickHouse Cloud는 세그먼트 대신 병렬 레플리카를 사용합니다**

  ClickHouse Cloud에서는 동일한 병렬성을 [병렬 레플리카](/ko/products/cloud/features/infrastructure/parallel-replicas)를 통해 구현하며, 이는 shared-nothing 클러스터의 세그먼트와 유사하게 동작합니다. 각 ClickHouse Cloud 레플리카(무상태 컴퓨트 노드)는 데이터의 일부를 병렬로 처리하고, 독립적인 세그먼트와 마찬가지로 최종 결과 생성에 기여합니다.
</Info>

<div id="monitoring-query-parallelism">
  ## 쿼리 병렬성 모니터링
</div>

다음 도구를 사용해 쿼리가 사용 가능한 CPU 리소스를 충분히 활용하는지 확인하고, 그렇지 않을 때 원인을 진단할 수 있습니다.

이 예시는 59개의 CPU 코어가 있는 테스트 서버에서 실행하며, 이를 통해 ClickHouse의 쿼리 병렬성을 충분히 보여줄 수 있습니다.

예시 쿼리가 어떻게 실행되는지 확인하기 위해, 집계 쿼리 수행 중 trace 수준의 모든 로그 엔트리를 반환하도록 ClickHouse 서버에 지시할 수 있습니다. 이번 시연에서는 쿼리의 프레디케이트를 제거했습니다. 그렇지 않으면 3개의 그래뉼만 처리되어, ClickHouse가 몇 개 이상의 병렬 처리 레인을 활용하기에는 데이터가 충분하지 않기 때문입니다:

```sql theme={null}
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
SETTINGS send_logs_level='trace';
```

```txt theme={null}
① <Debug> ...: 3609 marks to read from 3 ranges
② <Trace> ...: Spreading mark ranges among streams
② <Debug> ...: Reading approx. 29564928 rows with 59 streams
```

다음과 같은 점을 확인할 수 있습니다.

* ① ClickHouse는 3개의 데이터 범위에 걸쳐 3,609개의 그래뉼(트레이스 로그에서는 마크로 표시됨)을 읽어야 합니다.
* ② 59개의 CPU 코어가 있으므로 이 작업은 59개의 병렬 처리 스트림에 분산되며, 각 레인에 하나씩 할당됩니다.

또는 [EXPLAIN](/ko/reference/statements/explain#explain-pipeline) 절을 사용해 집계 쿼리의 [물리적 연산자 계획](/ko/concepts/core-concepts/academic-overview#4-2-multi-core-parallelization), 즉 "query pipeline"이라고도 하는 구조를 살펴볼 수 있습니다.

```sql theme={null}
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple;
```

```txt theme={null}
    ┌─explain───────────────────────────────────────────────────────────────────────────┐
 1. │ (Expression)                                                                      │
 2. │ ExpressionTransform × 59                                                          │
 3. │   (Aggregating)                                                                   │
 4. │   Resize 59 → 59                                                                  │
 5. │     AggregatingTransform × 59                                                     │
 6. │       StrictResize 59 → 59                                                        │
 7. │         (Expression)                                                              │
 8. │         ExpressionTransform × 59                                                  │
 9. │           (ReadFromMergeTree)                                                     │
10. │           MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 59 0 → 1 │
    └───────────────────────────────────────────────────────────────────────────────────┘
```

참고: 위의 연산자 계획은 아래에서 위로 읽으십시오. 각 줄은 물리적 실행 계획의 한 단계를 나타내며, 맨 아래의 스토리지에서 데이터를 읽는 단계부터 시작해 맨 위의 최종 처리 단계로 끝납니다. `× 59`로 표시된 연산자는 59개의 병렬 처리 레인에서 서로 겹치지 않는 데이터 영역에 대해 동시에 실행됩니다. 이는 `max_threads` 값을 반영하며, 쿼리의 각 단계가 CPU 코어 전반에서 어떻게 병렬화되는지 보여줍니다.

ClickHouse의 [내장 web UI](/ko/concepts/features/interfaces/http) (`/play` endpoint에서 사용 가능)는 위의 물리적 계획을 그래픽으로 시각화해 표시할 수 있습니다. 이 예시에서는 시각화를 간결하게 유지하기 위해 `max_threads`를 `4`로 설정하여 병렬 처리 레인 4개만 표시합니다:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/query-parallelism_05.png?fit=max&auto=format&n=kYq9dTBOYcMERk1I&q=85&s=5aeed44f4cc2a131c3bdf0076d102ef2" alt="쿼리 파이프라인" width="4694" height="1174" data-path="images/guides/best-practices/query-parallelism_05.png" />

참고: 이 시각화는 왼쪽에서 오른쪽으로 읽으십시오. 각 행은 데이터 블록을 순차적으로 스트리밍하면서 필터링, 집계, 최종 처리 단계와 같은 변환을 적용하는 병렬 처리 레인을 나타냅니다. 이 예시에서는 `max_threads = 4` 설정에 해당하는 4개의 병렬 레인을 확인할 수 있습니다.

<div id="load-balancing-across-processing-lanes">
  ### 처리 레인 간 부하 분산
</div>

위의 물리 계획에서 `Resize` 연산자는 데이터 블록 스트림을 [재파티셔닝하고 재분배](/ko/concepts/core-concepts/academic-overview#4-2-multi-core-parallelization)하여 처리 레인이 고르게 활용되도록 합니다. 이러한 재균형은 데이터 범위별로 쿼리 프레디케이트와 일치하는 행 수가 다를 때 특히 중요합니다. 그렇지 않으면 일부 레인에는 부하가 집중되고 다른 레인은 유휴 상태로 남을 수 있습니다. 작업을 재분배하면 더 빠른 레인이 더 느린 레인의 작업을 사실상 나눠 처리하게 되어 전체 쿼리 런타임이 최적화됩니다.

<div id="why-max-threads-isnt-always-respected">
  ## max\_threads가 항상 준수되지는 않는 이유
</div>

위에서 언급했듯이, `n`개의 병렬 처리 레인 수는 `max_threads` 설정으로 제어되며, 기본적으로 서버에서 ClickHouse가 사용할 수 있는 CPU 코어 수와 같습니다:

```sql theme={null}
SELECT getSetting('max_threads');
```

```txt theme={null}
   ┌─getSetting('max_threads')─┐
1. │                        59 │
   └───────────────────────────┘
```

하지만 처리할 데이터로 선택된 양에 따라 `max_threads` 값이 적용되지 않을 수 있습니다:

```sql theme={null}
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
```

```txt theme={null}
...   
(ReadFromMergeTree)
MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 30
```

위의 연산자 계획 추출 내용에서 볼 수 있듯이, `max_threads`를 `59`로 설정했더라도 ClickHouse는 데이터를 스캔할 때 동시 스트림을 **30**개만 사용합니다.

이제 쿼리를 실행해 보겠습니다:

```sql theme={null}
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
```

```txt theme={null}
   ┌─max(price)─┐
1. │  594300000 │ -- 594.30 million
   └────────────┘
   
1 row in set. Elapsed: 0.013 sec. Processed 2.31 million rows, 13.66 MB (173.12 million rows/s., 1.02 GB/s.)
Peak memory usage: 27.24 MiB.   
```

위 출력에서 볼 수 있듯이, 이 쿼리는 231만 개의 행을 처리하고 13.66MB의 데이터를 읽었습니다. 이는 인덱스 분석 단계에서 ClickHouse가 처리 대상으로 **282개의 그래뉼**을 선택했기 때문이며, 각 그래뉼에는 8,192개의 행이 포함되어 있어 총 약 231만 개의 행이 됩니다:

```sql theme={null}
EXPLAIN indexes = 1
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
```

```txt theme={null}
    ┌─explain───────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))             │
 2. │   Aggregating                                         │
 3. │     Expression (Before GROUP BY)                      │
 4. │       Expression                                      │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple)   │
 6. │         Indexes:                                      │
 7. │           PrimaryKey                                  │
 8. │             Keys:                                     │
 9. │               town                                    │
10. │             Condition: (town in ['LONDON', 'LONDON']) │
11. │             Parts: 3/3                                │
12. │             Granules: 282/3609                        │
    └───────────────────────────────────────────────────────┘  
```

설정된 `max_threads` 값과 관계없이, ClickHouse는 이를 뒷받침할 만큼 데이터가 충분할 때에만 추가 처리 레인을 할당합니다. `max_threads`의 "max"는 실제 사용되는 스레드 수를 보장한다는 뜻이 아니라 상한을 의미합니다.

여기서 "충분한 데이터"는 주로 두 가지 설정으로 결정되며, 각 처리 레인이 처리해야 하는 최소 행 수(기본값 163,840)와 최소 바이트 수(기본값 2,097,152)를 정의합니다.

shared-nothing 클러스터의 경우:

* [merge\_tree\_min\_rows\_for\_concurrent\_read](/ko/reference/settings/session-settings#merge_tree_min_rows_for_concurrent_read)
* [merge\_tree\_min\_bytes\_for\_concurrent\_read](/ko/reference/settings/session-settings#merge_tree_min_bytes_for_concurrent_read)

공유 스토리지를 사용하는 클러스터의 경우(예: ClickHouse Cloud):

* [merge\_tree\_min\_rows\_for\_concurrent\_read\_for\_remote\_filesystem](/ko/reference/settings/session-settings#merge_tree_min_rows_for_concurrent_read_for_remote_filesystem)
* [merge\_tree\_min\_bytes\_for\_concurrent\_read\_for\_remote\_filesystem](/ko/reference/settings/session-settings#merge_tree_min_bytes_for_concurrent_read_for_remote_filesystem)

또한 읽기 작업 크기에는 절대적인 하한이 있으며, 다음 설정으로 제어됩니다.

* [Merge\_tree\_min\_read\_task\_size](/ko/reference/settings/session-settings#merge_tree_min_read_task_size) + [merge\_tree\_min\_bytes\_per\_task\_for\_remote\_reading](/ko/reference/settings/session-settings#merge_tree_min_bytes_per_task_for_remote_reading)

<Warning>
  **이 설정은 수정하지 마십시오**

  운영 환경에서는 이 설정을 수정하지 않는 것이 좋습니다. 여기서는 `max_threads`가 실제 병렬성 수준을 항상 결정하지는 않는 이유를 설명하기 위해서만 보여줍니다.
</Warning>

시연을 위해, 최대 동시성이 강제로 적용되도록 이 설정을 재정의한 상태에서 물리 계획을 살펴보겠습니다.

```sql theme={null}
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON'
SETTINGS
  max_threads = 59,
  merge_tree_min_read_task_size = 0,
  merge_tree_min_rows_for_concurrent_read_for_remote_filesystem = 0, 
  merge_tree_min_bytes_for_concurrent_read_for_remote_filesystem = 0;
```

```txt theme={null}
...   
(ReadFromMergeTree)
MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 59
```

이제 ClickHouse는 설정된 `max_threads`를 완전히 준수하면서 59개의 동시 스트림을 사용해 데이터를 스캔합니다.

이는 작은 데이터셋에 대한 쿼리에서는 ClickHouse가 의도적으로 동시성을 제한한다는 점을 보여줍니다. 설정 재정의는 비효율적인 실행이나 리소스 경합을 초래할 수 있으므로 테스트 용도로만 사용하고, 프로덕션 환경에서는 사용하지 마십시오.

<div id="key-takeaways">
  ## 핵심 요약
</div>

* ClickHouse는 `max_threads`에 연결된 처리 레인을 사용해 쿼리를 병렬로 실행합니다.
* 실제 레인 수는 처리할 데이터로 선택된 데이터의 크기에 따라 달라집니다.
* 레인 사용 방식을 분석하려면 `EXPLAIN PIPELINE`과 트레이스 로그를 사용하세요.

<div id="where-to-find-more-information">
  ## 더 많은 정보를 확인할 수 있는 곳
</div>

ClickHouse가 쿼리를 병렬로 실행하는 방식과 대규모 환경에서 높은 성능을 달성하는 방법을 더 자세히 알아보려면, 다음 자료를 참고하십시오:

* [쿼리 처리 레이어 – VLDB 2024 논문(웹 버전)](/ko/concepts/core-concepts/academic-overview#4-query-processing-layer) - 스케줄링, 파이프라이닝, 연산자 설계를 포함해 ClickHouse의 내부 실행 모델을 자세히 설명합니다.

* [부분 집계 상태 설명](https://clickhouse.com/blog/clickhouse_vs_elasticsearch_mechanics_of_count_aggregations#-multi-core-parallelization) - 부분 집계 상태가 처리 레인 전반에서 효율적인 병렬 실행을 어떻게 가능하게 하는지 기술적으로 자세히 설명합니다.

* ClickHouse 쿼리 처리의 전체 단계를 자세히 설명하는 비디오 튜토리얼:

<Frame>
  <iframe src="https://www.youtube.com/embed/hP6G2Nlz_cA?si=Imd_i427J_kZOXHe" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />
</Frame>
