본문으로 바로가기
본문으로 바로가기

프라이머리 인덱스

고급 인덱싱에 대한 자세한 내용을 찾고 계십니까?

이 페이지에서는 ClickHouse의 희소 기본 인덱스가 어떻게 구성되고 동작하는지, 그리고 쿼리 실행을 어떻게 가속하는지 소개합니다.

더 고급 인덱싱 전략과 심화된 기술적 내용을 확인하려면 기본 인덱스 심층 가이드를 참고하십시오.

ClickHouse에서 희소 기본 인덱스는 어떻게 동작합니까?


ClickHouse의 희소 기본 인덱스는 그래뉼—여러 행으로 이루어진 블록—이 테이블의 ^^primary key^^ 컬럼에 대한 쿼리의 조건과 일치하는 데이터를 포함할 수 있는지 효율적으로 식별하는 데 도움이 됩니다. 다음 절에서는 이 인덱스가 해당 컬럼들의 값에서 어떻게 구성되는지 설명합니다.

희소 기본 인덱스 생성

희소 기본 인덱스가 어떻게 구성되는지 설명하기 위해, 몇 가지 애니메이션과 함께 uk_price_paid_simple 테이블을 사용합니다.

다시 상기하자면, ① ^^기본 키^^가 (town, street)인 예제 테이블에서 ② 삽입된 데이터는 ③ 디스크에 저장되며, ^^기본 키^^ 컬럼 값으로 정렬되고 압축되며, 각 컬럼마다 별도의 파일에 저장됩니다:



처리를 위해 각 컬럼의 데이터는 ④ 논리적으로 그래뉼로 분할되며, 각 그래뉼은 8,192개의 행을 포함합니다. 이는 ClickHouse의 데이터 처리 메커니즘이 동작하는 최소 단위입니다.

이 ^^그래뉼^^ 구조는 기본 인덱스를 희소하게 만드는 요소이기도 합니다. 모든 행을 인덱싱하는 대신, ClickHouse는 각 ^^그래뉼^^에서 단 하나의 행—구체적으로는 첫 번째 행—의 ^^기본 키^^ 값만 ⑤ 저장합니다. 그 결과 ^^그래뉼^^당 인덱스 엔트리가 하나씩 존재하게 됩니다:



희소성 덕분에 기본 인덱스는 전체가 메모리에 상주할 만큼 충분히 작게 유지되며, ^^기본 키^^ 컬럼에 대한 조건이 있는 쿼리에서 빠른 필터링을 가능하게 합니다. 다음 섹션에서는 이 인덱스가 이러한 쿼리를 어떻게 가속하는지 살펴봅니다.

기본 인덱스 사용 방법

희소 기본 인덱스가 쿼리 가속에 어떻게 사용되는지 다른 애니메이션을 통해 간략히 설명합니다:



① 예제 쿼리에는 두 ^^primary key^^ 컬럼 모두에 대한 조건이 포함됩니다: town = 'LONDON' AND street = 'OXFORD STREET'.

② 쿼리를 가속하기 위해 ClickHouse는 테이블의 기본 인덱스를 메모리에 로드합니다.

③ 그런 다음 인덱스 엔트리를 스캔하여 조건과 일치하는 행을 포함할 수 있는 그래뉼이 어떤 것들인지, 다시 말해 건너뛸 수 없는 그래뉼이 무엇인지 식별합니다.

④ 이렇게 잠재적으로 관련 있는 그래뉼을 메모리에 로드하고, 쿼리에 필요한 다른 컬럼의 해당 그래뉼과 함께 메모리에서 처리합니다.

기본 인덱스 모니터링

테이블의 각 데이터 파트는 자체 기본 인덱스를 가지고 있습니다. mergeTreeIndex 테이블 함수로 이러한 인덱스의 내용을 확인할 수 있습니다.

다음 쿼리는 예제 테이블의 각 데이터 파트별로 기본 인덱스에 있는 항목 개수를 나열합니다.

SELECT
    part_name,
    max(mark_number) AS entries
FROM mergeTreeIndex('uk', 'uk_price_paid_simple')
GROUP BY part_name;
   ┌─part_name─┬─entries─┐
1. │ all_2_2_0 │     914 │
2. │ all_1_1_0 │    1343 │
3. │ all_0_0_0 │    1349 │
   └───────────┴─────────┘

이 쿼리는 현재 존재하는 데이터 ^^파트^^ 중 하나의 프라이머리 인덱스에서 첫 10개 항목을 보여줍니다. 이러한 ^^파트^^는 백그라운드에서 지속적으로 더 큰 ^^파트^^로 병합됩니다.

SELECT 
    mark_number + 1 AS entry,
    town,
    street
FROM mergeTreeIndex('uk', 'uk_price_paid_simple')
WHERE part_name = (SELECT any(part_name) FROM mergeTreeIndex('uk', 'uk_price_paid_simple')) 
ORDER BY mark_number ASC
LIMIT 10;
    ┌─entry─┬─town───────────┬─street───────────┐
 1. │     1 │ ABBOTS LANGLEY │ ABBEY DRIVE      │
 2. │     2 │ ABERDARE       │ RICHARDS TERRACE │
 3. │     3 │ ABERGELE       │ PEN Y CAE        │
 4. │     4 │ ABINGDON       │ CHAMBRAI CLOSE   │
 5. │     5 │ ABINGDON       │ THORNLEY CLOSE   │
 6. │     6 │ ACCRINGTON     │ MAY HILL CLOSE   │
 7. │     7 │ ADDLESTONE     │ HARE HILL        │
 8. │     8 │ ALDEBURGH      │ LINDEN ROAD      │
 9. │     9 │ ALDERSHOT      │ HIGH STREET      │
10. │    10 │ ALFRETON       │ ALMA STREET      │
    └───────┴────────────────┴──────────────────┘

마지막으로 EXPLAIN 절을 사용하여, 모든 데이터 ^^파트^^의 기본 인덱스(primary index)가 예제 쿼리의 조건과 일치하는 행을 포함할 가능성이 전혀 없는 그래뉼을 어떻게 건너뛰는지 확인합니다. 이러한 그래뉼은 로딩 및 처리에서 제외됩니다:

EXPLAIN indexes = 1
SELECT
    max(price)
FROM
    uk.uk_price_paid_simple
WHERE
    town = 'LONDON' AND street = 'OXFORD STREET';
    ┌─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. │               street                                                                                       │
11. │             Condition: and((street in ['OXFORD STREET', 'OXFORD STREET']), (town in ['LONDON', 'LONDON'])) │
12. │             Parts: 3/3                                                                                     │
13. │             Granules: 3/3609                                                                               │
    └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

위의 EXPLAIN 출력에서 13번째 행을 보면, 전체 데이터 ^^파트^^에 걸쳐 존재하는 3,609개의 그래뉼 중 단 3개만이 기본 인덱스(primary index) 분석을 통해 처리 대상으로 선택되었음을 알 수 있습니다. 나머지 그래뉼은 모두 완전히 건너뛰었습니다.

또한 쿼리를 단순히 실행해 보기만 해도 대부분의 데이터가 건너뛰어졌음을 확인할 수 있습니다.

SELECT max(price)
FROM uk.uk_price_paid_simple
WHERE (town = 'LONDON') AND (street = 'OXFORD STREET');
   ┌─max(price)─┐
1. │  263100000 │ -- 263.10 million
   └────────────┘

1 row in set. Elapsed: 0.010 sec. Processed 24.58 thousand rows, 159.04 KB (2.53 million rows/s., 16.35 MB/s.)
Peak memory usage: 13.00 MiB.

위에서 본 것처럼 예제 테이블의 약 3,000만 행 가운데 약 2만5,000행만 처리되었습니다:

SELECT count() FROM uk.uk_price_paid_simple;
   ┌──count()─┐
1. │ 29556244 │ -- 29.56 million
   └──────────┘

핵심 요약

  • 희소 기본 인덱스는 ClickHouse가 ^^기본 키(primary key)^^ 컬럼의 쿼리 조건과 일치하는 행을 포함할 가능성이 있는 그래뉼을 식별하여 불필요한 데이터를 건너뛸 수 있도록 돕습니다.

  • 각 인덱스는 각 ^^그래뉼^^의 첫 번째 행의 ^^기본 키(primary key)^^ 값만 저장합니다(기본적으로 하나의 ^^그래뉼^^은 8,192개의 행을 가집니다). 이렇게 함으로써 메모리에 적재할 수 있을 만큼 충분히 작게 유지됩니다.

  • ^^MergeTree^^ 테이블의 **각 데이터 파트(data part)**는 자체 기본 인덱스를 가지며, 쿼리 실행 중에 서로 독립적으로 사용됩니다.

  • 쿼리 수행 중 인덱스를 사용하면 ClickHouse가 그래뉼을 건너뛰어 I/O와 메모리 사용량을 줄이고 성능을 향상시킬 수 있습니다.

  • mergeTreeIndex 테이블 함수를 사용하여 인덱스 내용을 확인하고, EXPLAIN 절을 사용하여 인덱스 사용 현황을 모니터링할 수 있습니다.

추가 정보

희소 기본 키 인덱스가 ClickHouse에서 어떻게 동작하는지, 기존 데이터베이스 인덱스와 어떤 차이가 있는지, 그리고 활용 시 모범 사례가 무엇인지 더 깊이 살펴보려면 인덱싱을 심층적으로 다룬 상세 가이드를 참고하십시오.

기본 키 인덱스 검색으로 선택된 데이터를 ClickHouse가 어떻게 고도로 병렬 처리하는지에 대해 알아보려면 쿼리 병렬 처리 가이드는 여기를 참조하십시오.