첫 번째 Projection 만들기
첫 번째 Projection 만들기
uk_price_paid 테이블은 (postcode, addr1, addr2)로 정렬되어 있어서 town으로 조회하면 전체 스캔이 필요해요. 이 퀵스타트에서는 projection — 같은 테이블 안에 저장되는 추가 정렬 표현 — 을 만들어 이 문제를 해결해요. 매터리얼라이즈드 뷰와 달리 별도 테이블이 필요 없고 mutation과 동기화되며 쿼리 최적화기가 투명하게 선택해요.
본문
사전 요구사항 (Prerequisites)
이 가이드를 성공적으로 따라 하려면 다음이 필요해요:
- 실행 중인 ClickHouse Cloud 서비스. 아직 없다면 ClickHouse Cloud 퀵 스타트를 먼저 완료하세요.
또한 이 가이드가 uk_price_paid 테이블과 거기서 소개한 개념을 직접 기반으로 하므로, 다음 퀵스타트도 완료했어야 해요:
만들게 될 것 (What you'll build)
MergeTree 퀵스타트에서 uk_price_paid를 town이나 county로 조회하려면 테이블이 (postcode, addr1, addr2)로 정렬되어 있어 전체 테이블 스캔이 필요하다는 걸 봤어요. 이 퀵스타트에서는 projection — 동일한 테이블 안에 저장되는, 데이터의 추가 정렬 표현 — 을 만들어 이 문제를 해결해요.
매터리얼라이즈드 뷰와 달리 projection은 별도의 destination 테이블이 필요 없고, mutation(삭제·업데이트)과 동기화를 유지하며, 쿼리 최적화기가 투명하게 사용해요 — 계속 같은 테이블 이름을 쿼리하면 돼요. 끝나면 projection을 추가·materialize하는 방법, ClickHouse가 자동으로 선택하는 방법, 그리고 언제 projection을 매터리얼라이즈드 뷰 대신 선택해야 하는지를 이해하게 돼요.
1. projection이 왜 필요한지 이해하기
uk_price_paid 테이블은 (postcode, addr1, addr2)로 정렬되어 있어요. postcode, addr1, addr2로 필터링하면 큰 블록을 건너뛸 수 있지만, town으로 필터링하는 쿼리는 모든 행 — 3천만 개 — 을 스캔해야 해요.
projection은 (일부 또는 모든) 컬럼의 추가 정렬 복사본을 같은 테이블 안에 저장해요. 테이블을 쿼리하면 쿼리 최적화기가 projection에서 읽는 것이 기본 데이터보다 더 적은 granule을 건드릴지 자동으로 확인하고, 그렇다면 투명하게 사용해요.
매터리얼라이즈드 뷰와의 핵심 차이점:
- 별도 테이블 없음 — projection은
uk_price_paid자체 안에 살아 있어요 - 투명한 쿼리 최적화 — 평소처럼
uk_price_paid를 쿼리하면 ClickHouse가 projection을 자동으로 선택해요 - mutation과 동기화 유지 — 테이블에 적용된 삭제·업데이트가 projection에도 반영돼요
자세한 내용은 projections 레퍼런스 문서에서 확인하세요.
2. 테이블에 projection 추가하기
town, date, price, type을 (town, date)로 정렬해서 저장하는 projection을 uk_price_paid에 정의해요:
ALTER TABLE uk_price_paid
ADD PROJECTION uk_price_paid_by_town
(
SELECT town, date, price, type
ORDER BY (town, date)
);
이렇게 하면 projection이 테이블 메타데이터에 등록되지만 기존 데이터에 대해서는 materialize되지 않아요 — 앞으로의 삽입만 채워질 거예요. 테이블 정의에 projection이 나타나는지 확인해 보세요:
SHOW CREATE TABLE uk_price_paid;
출력에 PROJECTION uk_price_paid_by_town 블록이 보일 거예요.
3. 기존 데이터에 대해 projection materialize하기
매터리얼라이즈드 뷰와 마찬가지로, 새로 추가된 projection은 앞으로의 삽입에만 적용돼요. 이미 테이블에 있는 3천만 행으로 채우려면 명시적으로 materialize해요:
ALTER TABLE uk_price_paid
MATERIALIZE PROJECTION uk_price_paid_by_town;
이것은 백그라운드 mutation으로 실행돼요. 진행 상황을 확인할 수 있어요:
SELECT
mutation_id,
command,
is_done
FROM system.mutations
WHERE table = 'uk_price_paid'
ORDER BY create_time DESC
LIMIT 5;
is_done = 1이 되면 projection이 완전히 materialize된 거예요. system.projection_parts로 확인할 수도 있어요:
SELECT
name,
count() AS parts,
sum(rows) AS total_rows,
formatReadableSize(sum(bytes_on_disk)) AS size
FROM system.projection_parts
WHERE table = 'uk_price_paid'
AND active = true
GROUP BY name;
4. 테이블을 쿼리하고 자동 projection 사용 관찰하기
이제 town으로 필터링하는 쿼리를 실행해 보세요 —지금까지 늘 쿼리해 온 같은 테이블에서 말이죠:
SELECT
toYear(date) AS year,
round(avg(price)) AS avg_price,
count() AS sales
FROM uk_price_paid
WHERE town = 'LONDON'
GROUP BY year
ORDER BY year DESC;
쿼리 통계를 확인해 보세요 — projection이 존재하기 전보다 훨씬 적은 행이 읽혀요. ClickHouse가 기본 데이터를 스캔하는 대신 uk_price_paid_by_town projection에서 자동으로 읽기로 선택했기 때문이에요.
EXPLAIN으로 projection이 사용됐는지 확인할 수 있어요:
EXPLAIN
SELECT
toYear(date) AS year,
round(avg(price)) AS avg_price,
count() AS sales
FROM uk_price_paid
WHERE town = 'LONDON'
GROUP BY year
ORDER BY year DESC;
출력에서 projection 이름을 참조하는 ReadFromMergeTree를 찾아보세요. 성능을 명시적으로 비교하고 싶다면 단일 쿼리에 대해 projection 최적화를 비활성화할 수 있어요:
SELECT
toYear(date) AS year,
round(avg(price)) AS avg_price,
count() AS sales
FROM uk_price_paid
WHERE town = 'LONDON'
GROUP BY year
ORDER BY year DESC
SETTINGS optimize_use_projections = 0;
이렇게 하면 전체 테이블 스캔이 강제되어 읽힌 행 수의 차이를 볼 수 있어요.
5. projection과 매터리얼라이즈드 뷰 비교하기
Projection과 매터리얼라이즈드 뷰는 같은 문제 — 대안적 접근 패턴에서 더 빠른 읽기 — 를 해결하지만 서로 다른 트레이드오프를 가져요. 요약하면, 같은 데이터에 다른 정렬 순서만 필요하면 projection이 최고이고, 데이터를 다른 스키마로 변환·집계·라우팅해야 할 때는 매터리얼라이즈드 뷰가 더 유연해요. 자세한 비교는 Materialized views versus projections를 참고하세요.
6. 저장 공간 오버헤드 관찰하기
Projection은 선택된 컬럼의 두 번째 복사본을 같은 테이블 안에 저장해서 디스크 사용을 늘려요. system.parts를 쿼리해서 (이제 projection 데이터를 포함하는) uk_price_paid의 총 크기를 확인해 보세요:
SELECT
table,
count() AS parts,
sum(rows) AS total_rows,
formatReadableSize(sum(bytes_on_disk)) AS compressed_size
FROM system.parts
WHERE table = 'uk_price_paid'
AND active = true
GROUP BY table;
projection 전용 저장 공간도 확인할 수 있어요:
SELECT
name,
count() AS parts,
sum(rows) AS total_rows,
formatReadableSize(sum(bytes_on_disk)) AS projection_size
FROM system.projection_parts
WHERE table = 'uk_price_paid'
AND active = true
GROUP BY name;
이것은 매터리얼라이즈드 뷰와 동일한 근본적인 트레이드오프예요: 더 빠른 읽기를 얻는 대가로 더 많은 디스크 공간. projection은 선택된 네 개 컬럼만 포함하고 압축이 정렬 순서에 따라 달라지므로 전체 복사본보다 작을 수 있어요.
다음 단계 (Next steps)
이 퀵스타트에서 uk_price_paid에 (town, date)로 정렬된 데이터를 저장하는 projection을 추가해서, 별도 테이블을 만들지 않고 town으로 빠르게 조회할 수 있게 했어요. projection이 쿼리 최적화기에 의해 투명하게 선택되고, mutation과 동기화되며, 디스크 공간을 읽기 성능과 맞바꾼다는 걸 배웠어요. 다음 퀵스타트를 계속 진행하거나 레퍼런스 문서로 더 깊이 들어가 보세요: