홈으로 돌아가기

ClickHouse 파티셔닝: 전략 및 작업

이 기사는 ClickHouse의 파티셔닝을 폴더 수준에서 물리적 데이터 분리를 위한 메커니즘으로 설명합니다. PARTITION BY 함수(toYYYYMM, toYYYYMMDD), system.parts를 통한 보기, DROP/DETACH/ATTACH/FREEZE/MOVE PARTITION 작업, 파티션 크기 선택 전략(서버당 100–1000개), 스크립트를 통한 오래된 데이터 자동 삭제를 다룹니다.

ClickHouse 파티셔닝: 완벽 가이드
Advertisement 728x90

ClickHouse 파티셔닝: 폴더 수준에서 데이터 관리하기

1. 파티셔닝이 필요한 이유 — 시간별 데이터 격리

온라인 카지노의 모든 베팅 데이터를 지난 3년간 저장한다고 가정해 보세요. 수십억 개의 행입니다. 사업주가 갑자기 "최근 6개월 데이터만 필요하니 나머지는 삭제"라고 요청합니다.

일반 데이터베이스에서는 DELETE FROM bets WHERE created_at < '2025-01-01'을 작성할 것입니다. ClickHouse에서 이러한 쿼리는 매우 오래 걸립니다. ClickHouse의 DELETE는 즉시 삭제가 아니라, 표시된 행이 없는 데이터 파트를 비동기적으로 다시 쓰는 작업이기 때문입니다.

하지만 삭제를 즉시 수행할 방법이 있습니다 — 바로 파티셔닝입니다. 데이터가 파티션(논리적 청크, 각각 디스크의 별도 폴더에 물리적으로 저장됨)으로 분할되어 있으면 DROP PARTITION이 밀리초 단위로 전체 폴더를 삭제합니다. 행을 스캔하거나 다시 쓸 필요 없이 파일 시스템 수준에서 rm -rf를 실행하는 것과 같습니다.

Google AdInline article slot

실생활 비유: 수년간의 종이 기록 보관소가 있다고 상상해 보세요. 각 상자는 한 달 분량입니다. 2024년 1월 데이터를 삭제해야 한다면 "2024년 1월"이라고 적힌 상자를 쓰레기통에 버리기만 하면 됩니다. 모든 종이를 하나하나 살펴볼 필요가 없습니다. ClickHouse의 파티션이 바로 그 상자입니다.

파티셔닝이 필요한 다른 이유:

  • 백업 — 필요한 파티션만 동결(FREEZE)할 수 있습니다.
  • 데이터 이동 — 자주 조회되는 핫 데이터는 빠른 SSD에, 드물게 조회되는 콜드 데이터는 느린 HDD나 클라우드 스토리지(S3)에 저장할 수 있습니다.
  • SELECT 가속화 — WHERE 절에 파티셔닝 컬럼이 포함되면 ClickHouse가 어떤 폴더를 읽고 무시할지 즉시 알 수 있습니다.

2. PARTITION BY — 구문 및 예제

파티셔닝은 테이블 생성 시 PARTITION BY를 사용하여 정의합니다. 가장 일반적인 패턴은 날짜별 분할입니다.

Google AdInline article slot
-- 월별 파티션이 있는 bets 테이블 생성
CREATE TABLE bets
(
    user_id     UInt64,
    amount      Decimal(18,2),
    created_at  DateTime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)   -- 파티션 = 연도 + 월, 예: 202512
ORDER BY (user_id, created_at);

여기서 일어나는 일:

  • toYYYYMM(created_at) — 날짜/시간 2025-12-15 14:30:00을 숫자 202512(2025년 12월)로 변환하는 ClickHouse 함수입니다. 동일한 202512 값을 가진 모든 행은 하나의 파티션에 속합니다.
  • 파티션 이름은 단순한 문자열입니다. ClickHouse는 디스크에 202512_1_1_0과 같은 이름의 폴더를 생성합니다(세부 사항은 중요하지 않지만 내부적으로 202512와 연결됩니다).

기타 시간 기반 파티셔닝 옵션:

-- 일별 (주의: 너무 많은 파티션이 생성될 수 있음)
PARTITION BY toYYYYMMDD(created_at)   -- 20251215

-- 시간별 (매우 세분화되어 거의 필요하지 않음)
PARTITION BY toStartOfHour(created_at)   -- 2025-12-15 14:00:00

-- 주별
PARTITION BY toWeek(created_at)   -- 연중 주 번호

-- 연별
PARTITION BY toYear(created_at)   -- 2025

PARTITION BY를 전혀 지정하지 않으면 어떻게 되나요? ClickHouse는 all이라는 단일 파티션을 생성합니다. 모든 데이터가 하나의 폴더에 저장됩니다. DELETE(느림) 또는 TRUNCATE(한 번에 모두)로만 삭제할 수 있습니다. 대부분의 시계열 테이블에서는 좋지 않은 방법입니다.

Google AdInline article slot

3. 파티션 선택 전략 — 적정선 찾기

파티션이 너무 작으면 안 되는 이유는?

ClickHouse는 너무 많은 파티션을 좋아하지 않습니다(권장 최대 1000~2000개). 이유는 다음과 같습니다.

  • 각 파티션은 메타데이터가 있는 별도의 폴더입니다.
  • 데이터 삽입 시 ClickHouse는 여러 파티션에 파트를 생성할 수 있습니다.
  • 백그라운드 병합은 파티션 내에서만 작동하며 파티션 간에는 작동하지 않습니다.
  • system.parts 테이블(파트 메타데이터)이 거대해집니다.

시간당 100만 행의 로드로 시간별 파티셔닝을 하면 어떻게 될까요? 1년이면 365 * 24 = 8760개의 파티션이 생깁니다. 이미 좋지 않습니다. 삽입할 때마다 ClickHouse는 수천 개의 폴더에 대한 파일 디스크립터를 열어야 합니다. 날짜 필터링이 있는 SELECT는 빠르지만, 시스템 작업(백업, 삭제, 파티션 목록 조회)이 느려집니다.

파티션이 너무 크면 안 되는 이유는?

파티션이 1년 단위라면 DROP PARTITION으로 해당 연도의 모든 데이터를 즉시 삭제할 수 있어 편리하지만:

  • 하루만 조회하는 SELECT도 연간 파티션 전체를 스캔해야 합니다.
  • 한 달만 빠르게 삭제할 수 없습니다. 연간 파티션을 삭제하거나 DELETE(느림)를 사용해야 합니다.
  • 핫/콜드 데이터 이동(예: 지난 달은 SSD, 나머지는 HDD)을 월 단위로 수행할 수 없습니다.

경험 법칙

로드 파티션 크기 예시
월 수십억 행 일 (toYYYYMMDD) IoT 센서 데이터, CDN 로그
월 수억 행 월 (toYYYYMM) 카지노 베팅, 트랜잭션
월 수천만 행 월 또는 분기 판매 분석
월 수천 행 느리게 변경되는 참조 데이터

주요 기준: 파티셔닝 후 서버당 100~1000개의 파티션이 있어야 합니다. 10,000개가 넘으면 과도한 것입니다.

4. 파티션 조회 — system.parts

테이블에 어떤 파티션이 있고 얼마나 많은 데이터가 있는지 확인하려면 시스템 테이블 system.parts를 사용합니다.

-- bets 테이블의 모든 파티션 조회
SELECT 
    partition,                          -- 파티션 이름 (예: 202512)
    name,                               -- 특정 파트 이름
    rows,                               -- 이 파트의 행 수
    bytes_on_disk,                      -- 디스크 상 크기 (바이트)
    modification_time,                  -- 마지막 수정 시간
    active                              -- 파트가 활성(1)인지 삭제됨(0)인지
FROM system.parts
WHERE table = 'bets' AND active = 1    -- 활성 파트만
ORDER BY partition DESC;

설명:

  • partitionPARTITION BY에 지정한 값입니다. 그룹화에 유용합니다.
  • name — 데이터 파트의 내부 이름으로, 파티션 번호와 버전을 포함합니다.
  • active = 1 — 읽기에 사용되는 파트입니다. 0이면 삭제 표시되었지만 물리적으로는 여전히 존재합니다.
  • bytes_on_disk — 파티션이 차지하는 공간을 보여줍니다.

하나의 파티션에 여러 파트가 있을 수 있는 이유는? 데이터를 배치로 삽입하기 때문입니다. ClickHouse는 즉시 병합하지 않습니다. 단일 파티션(202512) 내에 5~10개의 작은 파트가 있을 수 있으며, 결국 하나의 큰 파트로 병합됩니다.

집계 쿼리 — 파티션별 합계:

SELECT 
    partition,
    sum(rows) AS total_rows,
    sum(bytes_on_disk) AS total_bytes,
    formatReadableSize(sum(bytes_on_disk)) AS human_size
FROM system.parts
WHERE table = 'bets' AND active = 1
GROUP BY partition
ORDER BY partition DESC;

5. 파티션 작업 — DROP, DETACH, ATTACH, FREEZE

이것이 파티셔닝이 인기 있는 주된 이유입니다. 행 수준에서는 불가능한 작업을 파티션에 대해 수행할 수 있습니다.

DROP PARTITION — 즉시 삭제

-- 2025년 12월 데이터 모두 삭제
ALTER TABLE bets DROP PARTITION '202512';

-- 90일보다 오래된 데이터 동적 삭제
-- 먼저 90일 전의 파티션 이름 계산
ALTER TABLE bets DROP PARTITION toYYYYMM(today() - interval 90 day);

이 명령 후 파티션 폴더가 디스크에서 사라집니다. 작업은 밀리초 단위로 완료되며, 내부에 백만 개든 10억 개든 행 수에 관계없이 동일합니다.

왜 안전한가요? 파티션이 격리되어 있기 때문입니다. 하나를 삭제해도 다른 파티션에 영향을 주지 않습니다.

DETACH PARTITION — 임시 비활성화

-- 파티션 분리 (데이터는 디스크에 남아 있지만 SELECT에서 사용 불가)
ALTER TABLE bets DETACH PARTITION '202512';

파티션이 detached 하위 폴더로 이동합니다. 쿼리에 참여하지 않지만 다시 복원할 수 있습니다.

언제 필요한가:

  • 데이터 손상이 의심되어 일시적으로 숨기고 싶을 때.
  • 파티션을 다른 서버에 복사하려고 할 때 (그런 다음 새 서버에서 ATTACH).

ATTACH PARTITION — 분리된 파티션 복원

-- 파티션 복원 (detached 폴더에 있는 경우)
ALTER TABLE bets ATTACH PARTITION '202512';

FREEZE PARTITION — 즉시 백업

-- 2025년 12월 파티션의 백업 생성
ALTER TABLE bets FREEZE PARTITION '202512';

ClickHouse는 shadow/ 폴더에 파티션 파일에 대한 하드 링크를 생성합니다. 데이터를 복사하는 것이 아니라(빠름) 디스크의 동일한 블록에 대한 추가 포인터만 생성합니다. 그런 다음 shadow를 다른 서버에 복사할 수 있습니다.

일반 백업보다 나은 이유는? 쓰기를 중지할 필요가 없고 데이터 중복에 공간을 낭비하지 않기 때문입니다.

MOVE PARTITION — 계층형 스토리지용

자세한 내용은 6절에서 다룹니다.

6. MOVE PARTITION — 계층형 스토리지 (SSD → HDD → S3)

ClickHouse에서는 비용과 속도가 다른 여러 스토리지 계층을 구성할 수 있습니다. 예:

  • 핫 데이터 (지난 달) — 빠른 NVMe SSD
  • 웜 데이터 (2~6개월) — 일반 HDD
  • 콜드 데이터 (6개월 이상) — 클라우드 S3 (저렴하지만 느림)

파티션을 사용하면 단일 명령으로 데이터를 이러한 계층 간에 이동할 수 있습니다.

config.xml 구성 (간소화):

<storage_configuration>
    <disks>
        <ssd>
            <path>/mnt/ssd/clickhouse/</path>
        </ssd>
        <hdd>
            <path>/mnt/hdd/clickhouse/</path>
        </hdd>
    </disks>
    <policies>
        <hot_to_cold>
            <volumes>
                <hot>
                    <disk>ssd</disk>
                </hot>
                <cold>
                    <disk>hdd</disk>
                </cold>
            </volumes>
        </hot_to_cold>
    </policies>
</storage_configuration>

SSD에서 HDD로 파티션 이동:

-- 2025년 1월 데이터 (더 이상 빠른 속도 필요 없음)를 HDD로 이동
ALTER TABLE bets MOVE PARTITION '202501' TO VOLUME 'cold';

결과: ClickHouse가 파티션 폴더를 SSD에서 HDD로 물리적으로 이동합니다. SELECT는 계속 작동하지만 HDD로 인해 약간 느려집니다. DROP PARTITION은 여전히 빠릅니다.

비유: 가까운 빠른 캐비닛에서 먼 아카이브 캐비닛으로 오래된 폴더를 옮기는 것과 같습니다. 접근은 가능하지만 시간이 더 걸립니다.

7. 여러 컬럼으로 파티셔닝

때로는 시간뿐만 아니라 다른 차원으로도 데이터를 분할해야 합니다. 예를 들어, 여러 브랜드의 플랫폼(하나의 데이터베이스에 여러 카지노)이 있습니다. 각 브랜드별로 오래된 데이터를 별도로 삭제하고 다른 디스크에 저장하려고 합니다.

-- 5개 브랜드의 베팅 테이블, 월 및 브랜드별 파티션
CREATE TABLE bets_multi_brand
(
    brand_id    UInt8,          -- 1 = casino_A, 2 = casino_B, ...
    user_id     UInt64,
    amount      Decimal(18,2),
    created_at  DateTime
)
ENGINE = MergeTree()
PARTITION BY (toYYYYMM(created_at), brand_id)   -- 두 개의 컬럼!
ORDER BY (brand_id, user_id, created_at);

작동 방식:

  • (연도+월, brand_id)의 각 고유 조합이 별도의 파티션이 됩니다.
  • 2025년 12월 및 브랜드 1의 경우 파티션 ('202512', 1).
  • 2025년 12월 및 브랜드 2의 경우 파티션 ('202512', 2).

유용한 이유:

  • DROP PARTITION으로 특정 월의 특정 브랜드 데이터를 삭제할 수 있습니다.
  • MOVE PARTITION으로 브랜드 A의 오래된 데이터는 HDD로 이동하고, 브랜드 B(프리미엄)는 SSD에 유지할 수 있습니다.

특정 브랜드의 파티션 삭제 방법:

-- casino_A의 2025년 12월 데이터 삭제
ALTER TABLE bets_multi_brand DROP PARTITION ('202512', 1);

하지만 주의할 점: 파티션 이름이 이제 튜플입니다. system.parts에서 조회하면 ('202512', '1')로 표시됩니다. 정상입니다.

8. 파티셔닝이 성능에 미치는 영향

INSERT에 미치는 영향

데이터를 삽입할 때 ClickHouse는 PARTITION BY 표현식을 보고 행을 적절한 파티션으로 라우팅합니다. 파티션이 1000개이면 빠릅니다. 100,000개이면 각 삽입 시 올바른 폴더를 결정해야 하므로 쓰기 속도가 느려집니다.

단일 INSERT에 여러 파티션의 행이 포함되면 어떻게 되나요? ClickHouse가 여러 폴더에 분산합니다. 정상이지만 모든 행이 하나의 파티션에 있을 때보다 약간 느립니다.

팁: 배치로 데이터를 삽입하는 경우(예: 1분에 한 번) 배치 내 행이 짧은 시간 범위 내에 있도록 하세요. 그러면 하나 또는 두 개의 파티션에 속하게 되어 삽입이 더 빨라집니다.

SELECT에 미치는 영향

파티셔닝은 WHERE 절에 파티셔닝 컬럼에 대한 조건이 포함된 경우에만 SELECT 속도를 높입니다. ClickHouse는 파티션을 대략적인 가지치기에 사용합니다. 즉, 필요한 파티션을 확인하고 해당 폴더만 읽습니다.

-- 빠름: 파티션이 2025년 12월 외부의 모든 데이터를 가지치기
SELECT count() FROM bets 
WHERE created_at BETWEEN '2025-12-01' AND '2025-12-31';

-- 느림: 파티션 사용 안 함, 모든 파티션 스캔
SELECT count() FROM bets 
WHERE amount > 1000;   -- created_at 필터 없음

왜 항상 매우 세분화된 파티셔닝을 하지 않나요? 이유:

  • 쿼리가 넓은 범위를 다루는 경우 1000개의 작은 파티션(예: 3년간 일별)에서 읽는 것이 36개의 큰 파티션(월별)보다 느릴 수 있습니다. ClickHouse가 파티션당 하나의 파일 디스크립터를 열기 때문입니다.
  • 인덱스(기본 키)가 세분화된 파티셔닝보다 더 효과적인 경우가 많습니다.

경험 법칙: 먼저 ORDER BY(인덱스)를 최적화한 다음 파티셔닝을 고려하세요. 파티션은 SELECT 속도 향상만이 아니라 데이터 관리(DROP, MOVE)를 위한 도구입니다.

9. 흔한 실수: 과도하게 세분화된 파티셔닝

이것은 ClickHouse 초보자에게 가장 흔한 문제점입니다.

잘못된 접근 방식의 예:

-- 나쁨: 하루 1000만 행 테이블에 일별 파티션
CREATE TABLE logs_bad
(
    created_at DateTime,
    message String
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(created_at)   -- 주의!
ORDER BY created_at;

3년이면 365 * 3 = 1095개의 파티션이 생깁니다. 여전히 감당할 수 있지만(경계선) 10년이면 3650개로 좋지 않습니다. 시스템이 느려지기 시작합니다.

더 나쁜 경우 — 시간당 100만 레코드 로드에 시간별 파티셔닝: 1년에 8760개의 파티션. 삽입할 때마다 ClickHouse가 현재 시간의 폴더를 찾거나 생성합니다. SELECT * FROM system.parts가 몇 초 걸립니다. 백그라운드 병합이 중단됩니다.

파티션이 너무 많다는 신호:

  1. SELECT * FROM system.parts WHERE table = 'mytable' 쿼리가 1초 이상 걸립니다.
  2. ClickHouse 로그에 Too many parts (300+) 경고가 표시됩니다.
  3. ALTER TABLE ... DROP PARTITION이 즉시 작동하지 않고 몇 초 걸립니다.

이미 너무 세분화된 파티셔닝을 한 경우 어떻게 해야 하나요? ALTER TABLE ... MODIFY PARTITION BY ...를 통해 파티션을 병합할 수 있지만 복잡합니다. 적절한 파티셔닝으로 새 테이블을 만들고 데이터를 마이그레이션한 후 이름을 바꾸는 것이 더 쉽습니다.

10. 오래된 파티션 자동 삭제 스크립트

실제로는 지난 90일과 같은 특정 기간의 데이터만 유지해야 합니다. 매달 오래된 파티션을 삭제하는 것을 기억하는 것은 현실적이지 않습니다. 자동화가 필요합니다.

옵션 1: cron 또는 Task를 통한 주기적 쿼리

-- 90일보다 오래된 모든 파티션 삭제
-- 매일 오전 3시에 실행
ALTER TABLE bets DROP PARTITION WHERE partition < toYYYYMM(today() - interval 90 day);

작동 방식:

  • toYYYYMM(today() - interval 90 day) — 90일 전 날짜의 파티션 이름을 계산합니다. 예를 들어 오늘이 2026-06-11이면 90일 전은 2026-03-13이고 toYYYYMM202603을 반환합니다.
  • partition < 202603 — 이름이 202602, 202601, 202512 등인 모든 파티션을 삭제합니다.

중요: 이 쿼리는 파티션 이름이 숫자(연도+월)인 경우에만 작동합니다. 문자열 이름(예: '2025-12')의 경우 다른 비교가 필요합니다.

옵션 2: 저장된 목록을 사용하는 더 안전한 스크립트

-- N일보다 오래된 파티션 목록 찾기
SELECT partition
FROM system.parts
WHERE table = 'bets' 
  AND active = 1
  AND toDate(parseDateTimeBestEffort(partition)) < today() - interval 90 day
GROUP BY partition;

-- 찾은 각 파티션에 대해 DROP 실행 (외부 스크립트에서, SQL 내에서 직접 실행하지 않음)

왜 동적 계산으로 DROP PARTITION WHERE partition < ...을 하지 않나요? 테이블에 비표준 이름(예: 'all' 또는 수동 생성)의 파티션이 있는 경우 조건이 예상치 못하게 작동할 수 있습니다. 먼저 목록을 확인하는 것이 좋습니다.

Bash / Python용 전체 예제 스크립트:

#!/bin/bash
# bets 테이블에서 90일보다 오래된 파티션 삭제

CLICKHOUSE_CLIENT="clickhouse-client --host localhost"
TABLE="bets"
DAYS_TO_KEEP=90

# 경계 날짜 계산
BORDER_DATE=$(date -d "today - $DAYS_TO_KEEP days" +%Y%m)

# 경계보다 오래된 파티션 목록 가져오기
PARTITIONS=$($CLICKHOUSE_CLIENT --query="
    SELECT DISTINCT partition 
    FROM system.parts 
    WHERE table = '$TABLE' 
      AND active = 1 
      AND partition < '$BORDER_DATE'
      AND partition != 'all'
    FORMAT TabSeparated
")

# 각 파티션 삭제
for PARTITION in $PARTITIONS; do
    echo "$TABLE에서 파티션 $PARTITION 삭제 중"
    $CLICKHOUSE_CLIENT --query="ALTER TABLE $TABLE DROP PARTITION '$PARTITION'"
done

cron을 통한 자동화:

# 매일 오전 3시에 실행
0 3 * * * /usr/local/bin/cleanup_clickhouse_partitions.sh >> /var/log/cleanup.log 2>&1

다음 단계

이제 파티셔닝이 데이터 삭제를 고통스러운 작업에서 즉각적인 작업으로 바꾸는 방법을 이해했습니다. 다음 주제:

  • 고급 TTL(Time-To-Live) 전략 — 사용자 개입 없이 행을 자동으로 삭제하거나 이동합니다.
  • 스토리지 정책 구성 — 일정에 따라 SSD에서 S3로 파티션 이동을 자동화하는 방법.
  • 파티션을 활용한 SELECT 최적화 — ClickHouse가 99%의 파티션을 가지치기하도록 쿼리를 작성하는 방법.

결론: ClickHouse의 파티셔닝은 쿼리 속도 향상뿐만 아니라 데이터 수명 주기를 관리하기 위한 도구입니다. 서버당 100~1000개의 파티션이 되도록 파티션 크기를 선택하세요. 너무 세분화하지도, 너무 크게 하지도 마세요. 그리고 항상 system.parts를 확인하세요. 진실을 알려줄 것입니다.


이전 글:
다음 글: ClickHouse의 ORDER BY와 PRIMARY KEY: 인덱스를 올바르게 설정하는 방법

— Editorial Team

Advertisement 728x90

다음 읽기