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

# 첫 번째 프로젝션 만들기

> ClickHouse에서 프로젝션을 사용해 동일한 테이블 내에 정렬된 데이터 사본을 추가로 저장하고, 프라이머리 키(primary key)에 포함되지 않은 컬럼도 빠르게 조회하는 방법을 알아봅니다.

<a href="/ko/get-started/quickstarts/home"><Badge size="lg" color="gray" icon="arrow-left">모든 빠른 시작</Badge></a>

<div className="mt-2 flex flex-wrap gap-2">
  <Badge size="lg" color="blue">실시간 분석</Badge>
  <Badge size="lg" color="blue">데이터 웨어하우징</Badge>
  <Badge size="lg" color="blue">관측성</Badge>
  <Badge size="lg" color="blue">AI/ML</Badge>
  <Badge size="lg" color="orange">Cloud</Badge>
  <Badge size="lg" color="orange">Oss</Badge>
</div>

<div id="prerequisites">
  ## 사전 요구사항
</div>

To successfully follow this guide, you'll need the following:

* A running ClickHouse Cloud service. If you don't have one yet, complete the [Create your first Cloud service](/get-started/quickstarts/create-your-first-service-on-cloud) quickstart first.

이 가이드는 `uk_price_paid` 테이블과 앞서 소개한 개념을 바탕으로 진행되므로, 다음 빠른 시작도 완료되어 있어야 합니다:

* [첫 번째 MergeTree 테이블 만들기](/ko/get-started/quickstarts/create-your-first-mergetree-table)
* [첫 번째 materialized view 만들기](/ko/get-started/quickstarts/create-your-first-materialized-view)

<div id="what-youll-build">
  ## 만들게 될 내용
</div>

MergeTree 퀵스타트에서는 테이블이 `(postcode, addr1, addr2)` 기준으로 정렬되어 있으므로 `town` 또는 `county`로 `uk_price_paid`를 쿼리하려면 전체 테이블 스캔이 필요하다는 점을 살펴보았습니다.
이 퀵스타트에서는 **프로젝션**을 만들어 이 문제를 해결합니다. 프로젝션은 같은 테이블 *내부에* 저장되는 추가 정렬 데이터 표현입니다. materialized view와 달리 프로젝션은 별도의 대상 테이블이 필요 없고, 뮤테이션(삭제 및 업데이트)과 동기화된 상태를 유지하며, 쿼리 최적화기가 이를 투명하게 활용하므로 계속 같은 테이블 이름으로 쿼리하면 됩니다.
이 과정을 마치면 프로젝션을 추가하고 구체화하는 방법, ClickHouse가 이를 자동으로 선택하는 방식, 그리고 materialized view보다 프로젝션이 더 적합한 경우를 이해하게 됩니다.

<Steps titleSize="h3">
  <Step>
    ### 프로젝션이 필요한 이유 이해하기

    `uk_price_paid` 테이블은 `(postcode, addr1, addr2)` 기준으로 정렬되어 있습니다. 즉, `postcode`, `addr1`, `addr2`로 필터링할 때 ClickHouse는 큰 데이터 블록을 건너뛸 수 있지만, `town`으로 필터링하는 쿼리는 3천만 행 전체를 모두 스캔해야 합니다.

    **프로젝션**은 *같은 테이블 내부에* (일부 또는 전체) 컬럼의 추가 정렬 복사본을 저장합니다. 테이블을 쿼리할 때 쿼리 최적화기는 프로젝션을 읽는 편이 기본 데이터보다 더 적은 그래뉼에 접근하는지 자동으로 확인하고, 그렇다면 이를 투명하게 사용합니다.

    materialized view와의 주요 차이점:

    * **별도의 테이블이 없습니다** - 프로젝션은 `uk_price_paid` 자체 내부에 존재합니다
    * **투명한 쿼리 최적화** - 평소처럼 `uk_price_paid`를 쿼리하면 ClickHouse가 자동으로 프로젝션을 선택합니다
    * **뮤테이션과 동기화 상태를 유지합니다** - 테이블에 적용된 삭제 및 업데이트가 프로젝션에도 반영됩니다

    자세한 내용은 [프로젝션 참고 문서](/ko/reference/statements/alter/projection)를 참조하십시오.
  </Step>

  <Step>
    ### 테이블에 프로젝션 추가하기

    `uk_price_paid`에 `town`, `date`, `price`, `type`을 `(town, date)` 기준으로 정렬된 상태로 저장하는 프로젝션을 정의합니다:

    ```sql theme={null}
    ALTER TABLE uk_price_paid
        ADD PROJECTION uk_price_paid_by_town
        (
            SELECT town, date, price, type
            ORDER BY (town, date)
        );
    ```

    이렇게 하면 프로젝션이 테이블의 메타데이터에 등록되지만, 기존 데이터에는 **구체화되지 않습니다**. 향후 삽입되는 데이터에만 적용됩니다.

    프로젝션이 테이블 정의에 표시되는지 확인하십시오:

    ```sql theme={null}
    SHOW CREATE TABLE uk_price_paid;
    ```

    출력 결과에서 `PROJECTION uk_price_paid_by_town` 블록을 확인할 수 있어야 합니다.
  </Step>

  <Step>
    ### 기존 데이터에 대해 프로젝션 구체화하기

    materialized view와 마찬가지로, 새로 추가한 프로젝션은 이후 삽입되는 데이터에만 적용됩니다. 테이블에 이미 있는 3천만 행으로 이를 채우려면 명시적으로 구체화하십시오:

    ```sql theme={null}
    ALTER TABLE uk_price_paid
        MATERIALIZE PROJECTION uk_price_paid_by_town;
    ```

    이 작업은 백그라운드 mutation으로 실행됩니다. 진행 상황은 다음과 같이 확인할 수 있습니다:

    ```sql theme={null}
    SELECT
        mutation_id,
        command,
        is_done
    FROM system.mutations
    WHERE table = 'uk_price_paid'
    ORDER BY create_time DESC
    LIMIT 5;
    ```

    `is_done = 1`이 되면 프로젝션이 완전히 구체화됩니다. `system.projection_parts`를 확인해서도 검증할 수 있습니다:

    ```sql theme={null}
    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;
    ```
  </Step>

  <Step>
    ### 테이블(table)을 쿼리하고 projection이 자동으로 사용되는지 확인하기

    이제 `town`으로 필터링하는 쿼리를 실행하세요. 지금까지 계속 쿼리해 온 **같은 테이블**에서 실행합니다:

    ```sql theme={null}
    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;
    ```

    쿼리 통계를 확인해 보세요. 프로젝션이 생기기 전보다 읽는 행 수가 훨씬 적습니다. ClickHouse가 기본 데이터를 스캔하는 대신 `uk_price_paid_by_town` 프로젝션에서 읽도록 자동으로 선택했기 때문입니다.

    `EXPLAIN`을 사용하면 프로젝션이 사용되었는지 확인할 수 있습니다:

    ```sql theme={null}
    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;
    ```

    출력에서 프로젝션 이름을 참조하는 `ReadFromMergeTree`를 확인하십시오. 성능을 명확히 비교하려면 단일 쿼리에서 프로젝션 최적화를 비활성화할 수 있습니다:

    ```sql theme={null}
    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;
    ```

    이렇게 하면 테이블 전체를 스캔하게 되므로 읽은 행 수의 차이를 확인할 수 있습니다.
  </Step>

  <Step>
    ### 프로젝션과 materialized view 비교

    프로젝션과 materialized view는 모두 같은 문제, 즉 서로 다른 액세스 패턴에서 더 빠른 읽기를 제공하는 문제를 해결하지만, 각기 다른 트레이드오프가 있습니다. 요약하면, 동일한 데이터에 대해 다른 정렬 순서만 필요할 때는 프로젝션이 가장 적합합니다. 반면 데이터를 변환하거나 집계하거나 다른 스키마(schema)로 보내야 할 때는 materialized view가 더 유연합니다. 자세한 비교는 [Materialized views versus projections](/ko/concepts/features/projections/materialized-views-versus-projections)를 참조하십시오.
  </Step>

  <Step>
    ### 스토리지 오버헤드 확인

    프로젝션은 동일한 테이블 내에 선택한 컬럼의 사본을 한 벌 더 저장하므로 디스크 사용량이 증가합니다. `system.parts`를 쿼리하여 `uk_price_paid`의 전체 크기(이제 프로젝션 데이터가 포함됨)를 확인하세요:

    ```sql theme={null}
    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;
    ```

    프로젝션별 스토리지도 확인할 수 있습니다.

    ```sql theme={null}
    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;
    ```

    이는 구체화된 뷰(Materialized View)와 같은 근본적인 절충입니다. 즉, 더 빠른 읽기를 위해 더 많은 디스크 공간을 사용합니다. 프로젝션은 선택한 4개의 컬럼만 포함하고 정렬 순서에 따라 압축률이 달라지므로 전체 복사본보다 작을 수 있습니다.
  </Step>
</Steps>

## 다음 단계

이 빠른 시작에서는 `uk_price_paid`에 `(town, date)` 기준으로 정렬된 데이터를 저장하는 프로젝션을 추가해, 별도의 테이블을 만들지 않고도 town 기준으로 빠르게 조회할 수 있도록 했습니다. 또한 프로젝션은 쿼리 최적화기가 자동으로 선택하고, 뮤테이션과 동기화된 상태를 유지하며, 디스크 공간을 사용하는 대신 읽기 성능을 높인다는 점을 배웠습니다.

다음으로 아래 빠른 시작도 확인해 보십시오.

또는 참고 문서에서 더 자세히 알아보십시오.

* [materialized view와 프로젝션 비교](/ko/concepts/features/projections/materialized-views-versus-projections)
* [프로젝션 참고](/ko/reference/statements/alter/projection)
* [MergeTree 테이블 엔진](/ko/reference/engines/table-engines/mergetree-family/mergetree)

<Frame caption="Check out the ClickHouse academy for on-demand and live training">
  <a href="https://learn.clickhouse.com/" target="_blank">
    <img src="https://mintcdn.com/private-7c7dfe99-fix-nav-issues/Y9kcWM6RbYppspJn/images/academy.png?fit=max&auto=format&n=Y9kcWM6RbYppspJn&q=85&s=d842bc871e006c08da3026a8a09e1d61" alt="ClickHouse Academy — Master ClickHouse with expert-designed training for every skill level" width="560" noZoom data-path="images/academy.png" />
  </a>
</Frame>

<div className="mt-8">
  <a href="/ko/get-started/quickstarts/home"><Badge size="lg" color="gray" icon="arrow-left">모든 빠른 시작</Badge></a>
</div>
