> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# S3와 ClickHouse 통합

> S3와 ClickHouse를 통합하는 방법을 설명하는 페이지

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

S3의 데이터를 ClickHouse에 삽입할 수 있으며, S3를 내보내기 대상으로도 사용할 수 있으므로 「데이터 레이크」 아키텍처와 연동할 수 있습니다. 또한 S3는 「콜드」 스토리지 계층을 제공하고 스토리지와 컴퓨트를 분리하는 데도 도움이 됩니다. 아래 섹션에서는 뉴욕시 택시 데이터셋을 사용해 S3와 ClickHouse 간에 데이터를 이동하는 방법을 설명하고, 주요 구성 매개변수를 살펴보며, 성능 최적화를 위한 힌트도 제공합니다.

<div id="s3-table-functions">
  ## S3 테이블 함수
</div>

`s3` 테이블 함수를 사용하면 S3 호환 스토리지에서 파일을 읽고 쓸 수 있습니다. 구문은 다음과 같습니다:

```sql theme={null}
s3(path, [aws_access_key_id, aws_secret_access_key,] [format, [structure, [compression]]])
```

여기서:

* path — 파일 경로를 포함하는 버킷 URL입니다. 읽기 전용 모드에서는 다음 와일드카드를 지원합니다: `*`, `?`, `{abc,def}`, `{N..M}`. 여기서 `N`, `M`은 숫자이고 `'abc'`, `'def'`는 문자열입니다. 자세한 내용은 [경로에서 와일드카드 사용](/docs/ko/reference/engines/table-engines/integrations/s3#wildcards-in-path) 문서를 참조하십시오.
* format — 파일의 [포맷](/docs/ko/reference/formats/index#formats-overview)입니다.
* structure — 테이블의 구조입니다. 포맷은 `'column1_name column1_type, column2_name column2_type, ...'`입니다.
* compression — 이 매개변수는 선택 사항입니다. 지원되는 값은 `none`, `gzip/gz`, `brotli/br`, `xz/LZMA`, `zstd/zst`입니다. 기본적으로 파일 확장자를 기준으로 압축을 자동 감지합니다.

경로 표현식에서 와일드카드를 사용하면 여러 파일을 참조할 수 있으므로 병렬 처리도 가능합니다.

<div id="preparation">
  ### 준비
</div>

ClickHouse에서 테이블을 생성하기 전에 먼저 S3 버킷의 데이터를 자세히 살펴보는 것이 좋습니다. ClickHouse에서 `DESCRIBE` 문을 사용하면 이를 직접 확인할 수 있습니다:

```sql theme={null}
DESCRIBE TABLE s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz', 'TabSeparatedWithNames');
```

`DESCRIBE TABLE` 문의 출력에는 S3 버킷에 저장된 이 데이터를 ClickHouse가 어떻게 자동으로 추론하는지가 표시됩니다. 또한 gzip 압축 포맷도 자동으로 인식하고 압축을 해제합니다:

```sql theme={null}
DESCRIBE TABLE s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz', 'TabSeparatedWithNames') SETTINGS describe_compact_output=1
```

```response theme={null}
┌─name──────────────────┬─type───────────────┐
│ trip_id               │ Nullable(Int64)    │
│ vendor_id             │ Nullable(Int64)    │
│ pickup_date           │ Nullable(Date)     │
│ pickup_datetime       │ Nullable(DateTime) │
│ dropoff_date          │ Nullable(Date)     │
│ dropoff_datetime      │ Nullable(DateTime) │
│ store_and_fwd_flag    │ Nullable(Int64)    │
│ rate_code_id          │ Nullable(Int64)    │
│ pickup_longitude      │ Nullable(Float64)  │
│ pickup_latitude       │ Nullable(Float64)  │
│ dropoff_longitude     │ Nullable(Float64)  │
│ dropoff_latitude      │ Nullable(Float64)  │
│ passenger_count       │ Nullable(Int64)    │
│ trip_distance         │ Nullable(String)   │
│ fare_amount           │ Nullable(String)   │
│ extra                 │ Nullable(String)   │
│ mta_tax               │ Nullable(String)   │
│ tip_amount            │ Nullable(String)   │
│ tolls_amount          │ Nullable(Float64)  │
│ ehail_fee             │ Nullable(Int64)    │
│ improvement_surcharge │ Nullable(String)   │
│ total_amount          │ Nullable(String)   │
│ payment_type          │ Nullable(String)   │
│ trip_type             │ Nullable(Int64)    │
│ pickup                │ Nullable(String)   │
│ dropoff               │ Nullable(String)   │
│ cab_type              │ Nullable(String)   │
│ pickup_nyct2010_gid   │ Nullable(Int64)    │
│ pickup_ctlabel        │ Nullable(Float64)  │
│ pickup_borocode       │ Nullable(Int64)    │
│ pickup_ct2010         │ Nullable(String)   │
│ pickup_boroct2010     │ Nullable(String)   │
│ pickup_cdeligibil     │ Nullable(String)   │
│ pickup_ntacode        │ Nullable(String)   │
│ pickup_ntaname        │ Nullable(String)   │
│ pickup_puma           │ Nullable(Int64)    │
│ dropoff_nyct2010_gid  │ Nullable(Int64)    │
│ dropoff_ctlabel       │ Nullable(Float64)  │
│ dropoff_borocode      │ Nullable(Int64)    │
│ dropoff_ct2010        │ Nullable(String)   │
│ dropoff_boroct2010    │ Nullable(String)   │
│ dropoff_cdeligibil    │ Nullable(String)   │
│ dropoff_ntacode       │ Nullable(String)   │
│ dropoff_ntaname       │ Nullable(String)   │
│ dropoff_puma          │ Nullable(Int64)    │
└───────────────────────┴────────────────────┘
```

S3 기반 데이터셋과 상호작용하기 위해 대상 테이블로 표준 `MergeTree` 테이블을 준비합니다. 아래 구문은 기본 데이터베이스에 `trips`라는 이름의 테이블을 생성합니다. 특히 앞서 추론한 일부 데이터 타입을 조정했으며, 불필요한 추가 저장 공간 사용과 성능 오버헤드를 초래할 수 있는 [`Nullable()`](/docs/ko/reference/data-types/nullable) 데이터 타입 수정자는 사용하지 않도록 했습니다:

```sql theme={null}
CREATE TABLE trips
(
    `trip_id` UInt32,
    `vendor_id` Enum8('1' = 1, '2' = 2, '3' = 3, '4' = 4, 'CMT' = 5, 'VTS' = 6, 'DDS' = 7, 'B02512' = 10, 'B02598' = 11, 'B02617' = 12, 'B02682' = 13, 'B02764' = 14, '' = 15),
    `pickup_date` Date,
    `pickup_datetime` DateTime,
    `dropoff_date` Date,
    `dropoff_datetime` DateTime,
    `store_and_fwd_flag` UInt8,
    `rate_code_id` UInt8,
    `pickup_longitude` Float64,
    `pickup_latitude` Float64,
    `dropoff_longitude` Float64,
    `dropoff_latitude` Float64,
    `passenger_count` UInt8,
    `trip_distance` Float64,
    `fare_amount` Float32,
    `extra` Float32,
    `mta_tax` Float32,
    `tip_amount` Float32,
    `tolls_amount` Float32,
    `ehail_fee` Float32,
    `improvement_surcharge` Float32,
    `total_amount` Float32,
    `payment_type` Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4),
    `trip_type` UInt8,
    `pickup` FixedString(25),
    `dropoff` FixedString(25),
    `cab_type` Enum8('yellow' = 1, 'green' = 2, 'uber' = 3),
    `pickup_nyct2010_gid` Int8,
    `pickup_ctlabel` Float32,
    `pickup_borocode` Int8,
    `pickup_ct2010` String,
    `pickup_boroct2010` String,
    `pickup_cdeligibil` String,
    `pickup_ntacode` FixedString(4),
    `pickup_ntaname` String,
    `pickup_puma` UInt16,
    `dropoff_nyct2010_gid` UInt8,
    `dropoff_ctlabel` Float32,
    `dropoff_borocode` UInt8,
    `dropoff_ct2010` String,
    `dropoff_boroct2010` String,
    `dropoff_cdeligibil` String,
    `dropoff_ntacode` FixedString(4),
    `dropoff_ntaname` String,
    `dropoff_puma` UInt16
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(pickup_date)
ORDER BY pickup_datetime
```

`pickup_date` 필드에서 [파티셔닝](/docs/ko/reference/engines/table-engines/mergetree-family/custom-partitioning-key)을 사용한 점에 유의하세요. 일반적으로 파티션 키는 데이터 관리를 위해 사용하지만, 이후에는 이 키를 사용해 S3 쓰기를 병렬화합니다.

택시 데이터셋의 각 항목은 택시 운행 1건에 해당합니다. 이 익명화된 데이터는 S3 버킷 [https://datasets-documentation.s3.eu-west-3.amazonaws.com/](https://datasets-documentation.s3.eu-west-3.amazonaws.com/) 의 **nyc-taxi** 폴더 아래에 압축된 2천만 개의 레코드로 구성되어 있습니다. 데이터는 TSV 포맷이며, 파일당 약 100만 개의 행이 들어 있습니다.

<div id="reading-data-from-s3">
  ### S3에서 데이터 읽기
</div>

ClickHouse에 영구적으로 저장하지 않아도 S3 데이터를 소스로 직접 쿼리할 수 있습니다. 다음 쿼리에서는 10개 행을 샘플링합니다. 버킷이 공개적으로 접근 가능하므로 여기에는 자격 증명이 필요하지 않습니다:

```sql theme={null}
SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz', 'TabSeparatedWithNames')
LIMIT 10;
```

`TabSeparatedWithNames` 포맷은 첫 번째 행에 컬럼 이름을 포함하므로, 컬럼을 따로 나열할 필요가 없다는 점에 유의하십시오. `CSV` 또는 `TSV` 같은 다른 포맷에서는 이 쿼리에 대해 `c1`, `c2`, `c3` 등과 같이 자동 생성된 컬럼이 반환됩니다.

또한 쿼리는 버킷 경로와 파일 이름 정보를 각각 제공하는 `_path`, `_file` 같은 [가상 컬럼](/docs/ko/reference/functions/table-functions/s3#virtual-columns)도 지원합니다. 예시는 다음과 같습니다:

```sql theme={null}
SELECT  _path, _file, trip_id
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', 'TabSeparatedWithNames')
LIMIT 5;
```

```response theme={null}
┌─_path──────────────────────────────────────┬─_file──────┬────trip_id─┐
│ datasets-documentation/nyc-taxi/trips_0.gz │ trips_0.gz │ 1199999902 │
│ datasets-documentation/nyc-taxi/trips_0.gz │ trips_0.gz │ 1199999919 │
│ datasets-documentation/nyc-taxi/trips_0.gz │ trips_0.gz │ 1199999944 │
│ datasets-documentation/nyc-taxi/trips_0.gz │ trips_0.gz │ 1199999969 │
│ datasets-documentation/nyc-taxi/trips_0.gz │ trips_0.gz │ 1199999990 │
└────────────────────────────────────────────┴────────────┴────────────┘
```

이 샘플 데이터셋의 행 수를 확인하십시오. 파일 경로 확장에 와일드카드를 사용하므로 20개 파일 전체를 대상으로 한다는 점에 유의하십시오. 이 쿼리는 ClickHouse 인스턴스의 코어 수에 따라 약 10초 정도 걸립니다:

```sql theme={null}
SELECT count() AS count
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz', 'TabSeparatedWithNames');
```

```response theme={null}
┌────count─┐
│ 20000000 │
└──────────┘
```

데이터를 샘플링하거나 임시적인 탐색용 쿼리를 실행할 때는 유용하지만, S3에서 데이터를 직접 읽는 방식을 정기적으로 사용하는 것은 바람직하지 않습니다. 본격적으로 활용할 단계가 되면 데이터를 ClickHouse의 `MergeTree` 테이블로 가져오십시오.

<div id="using-clickhouse-local">
  ### clickhouse-local 사용
</div>

`clickhouse-local` 프로그램을 사용하면 ClickHouse 서버를 배포하거나 구성하지 않고도 로컬 파일을 빠르게 처리할 수 있습니다. `s3` 테이블 함수를 사용하는 모든 쿼리는 이 유틸리티로 실행할 수 있습니다. 예시:

```sql theme={null}
clickhouse-local --query "SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz', 'TabSeparatedWithNames') LIMIT 10"
```

<div id="inserting-data-from-s3">
  ### S3에서 데이터 삽입
</div>

ClickHouse의 기능을 최대한 활용하기 위해, 다음으로 데이터를 읽어 인스턴스에 삽입합니다.
이를 위해 `s3` 함수와 간단한 `INSERT` 구문을 함께 사용합니다. 대상 테이블이 필요한 구조를 제공하므로 컬럼을 나열할 필요는 없습니다. 이 경우 컬럼은 테이블 DDL 구문에 지정된 순서대로 나타나야 합니다. 컬럼은 `SELECT` 절에서의 위치를 기준으로 매핑됩니다. 1,000만 개의 모든 행을 삽입하는 작업은 ClickHouse 인스턴스에 따라 몇 분 정도 걸릴 수 있습니다. 아래에서는 빠른 응답을 위해 100만 개의 행만 삽입합니다. 필요에 따라 `LIMIT` 절이나 컬럼 선택을 조정해 일부 데이터만 가져오십시오:

```sql theme={null}
INSERT INTO trips
   SELECT *
   FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz', 'TabSeparatedWithNames')
   LIMIT 1000000;
```

<div id="remote-insert-using-clickhouse-local">
  ### ClickHouse Local을 사용한 원격 삽입
</div>

네트워크 보안 정책으로 인해 ClickHouse 클러스터에서 외부 연결을 시작할 수 없는 경우, `clickhouse-local`을 사용해 S3 데이터를 삽입할 수 있습니다. 아래 예시에서는 S3 버킷에서 데이터를 읽어 `remote` 함수를 사용해 ClickHouse에 삽입합니다:

```sql theme={null}
clickhouse-local --query "INSERT INTO TABLE FUNCTION remote('localhost:9000', 'default.trips', 'username', 'password') (*) SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz', 'TabSeparatedWithNames') LIMIT 10"
```

<Note>
  이를 보안 SSL connection을 통해 실행하려면 `remoteSecure` 함수를 사용하십시오.
</Note>

<div id="exporting-data">
  ### 데이터 내보내기
</div>

`s3` 테이블 함수를 사용하면 S3의 파일에 쓸 수 있습니다. 이를 위해서는 적절한 권한이 필요합니다. 필요한 자격 증명은 요청에 함께 전달하지만, 더 많은 옵션은 [자격 증명 관리](#managing-credentials) 페이지를 참조하십시오.

아래의 간단한 예시에서는 테이블 함수를 소스가 아니라 대상으로 사용합니다. 여기서는 `trips` 테이블의 10,000개 행을 버킷으로 스트리밍하면서 `lz4` 압축과 `CSV` 출력 유형을 지정합니다:

```sql theme={null}
INSERT INTO FUNCTION
   s3(
       'https://datasets-documentation.s3.eu-west-3.amazonaws.com/csv/trips.csv.lz4',
       's3_key',
       's3_secret',
       'CSV'
    )
SELECT *
FROM trips
LIMIT 10000;
```

여기서 파일의 포맷은 확장자에서 자동으로 추론된다는 점에 유의하십시오. 또한 `s3` 함수에서 컬럼을 지정할 필요도 없습니다. 이 역시 `SELECT`에서 추론됩니다.

<div id="splitting-large-files">
  ### 대용량 파일 분할
</div>

데이터를 하나의 파일로 내보낼 일은 거의 없습니다. ClickHouse를 포함한 대부분의 도구는 병렬 처리가 가능하므로, 여러 파일을 읽고 쓸 때 더 높은 처리량을 얻습니다. `INSERT` 명령을 여러 번 실행하여 데이터의 일부만 대상으로 지정할 수도 있습니다. ClickHouse는 `PARTITION` 키를 사용해 파일을 자동으로 분할하는 기능을 제공합니다.

아래 예시에서는 `rand()` 함수의 modulus를 사용해 10개의 파일을 생성합니다. 생성된 파티션 ID가 파일 이름에서 어떻게 참조되는지 확인하십시오. 그 결과 `trips_0.csv.lz4`, `trips_1.csv.lz4`처럼 숫자 접미사가 붙은 10개의 파일이 생성됩니다:

```sql theme={null}
INSERT INTO FUNCTION
   s3(
       'https://datasets-documentation.s3.eu-west-3.amazonaws.com/csv/trips_{_partition_id}.csv.lz4',
       's3_key',
       's3_secret',
       'CSV'
    )
    PARTITION BY rand() % 10
SELECT *
FROM trips
LIMIT 100000;
```

또는 데이터 내 필드를 참조할 수 있습니다. 이 데이터셋에서는 `payment_type`이 카디널리티가 5인 자연스러운 파티셔닝 키 역할을 합니다.

```sql theme={null}
INSERT INTO FUNCTION
   s3(
       'https://datasets-documentation.s3.eu-west-3.amazonaws.com/csv/trips_{_partition_id}.csv.lz4',
       's3_key',
       's3_secret',
       'CSV'
    )
    PARTITION BY payment_type
SELECT *
FROM trips
LIMIT 100000;
```

<div id="utilizing-clusters">
  ### 클러스터 활용
</div>

위 함수들은 모두 단일 노드에서만 실행할 수 있습니다. 읽기 속도는 다른 리소스(일반적으로 네트워크)가 포화될 때까지 CPU 코어 수에 따라 선형적으로 증가하므로 수직 확장이 가능합니다. 하지만 이 방식에도 한계가 있습니다. `INSERT INTO SELECT` 쿼리를 수행할 때 분산 테이블에 삽입하면 일부 리소스 부담을 줄일 수 있지만, 여전히 단일 노드가 데이터를 읽고, 파싱하고, 처리해야 합니다. 이 문제를 해결하고 읽기를 수평 확장할 수 있도록 [s3Cluster](/docs/ko/reference/functions/table-functions/s3Cluster) 함수를 제공합니다.

쿼리를 수신하는 노드(initiator)는 클러스터의 모든 노드에 연결을 생성합니다. 읽어야 할 파일을 결정하는 glob pattern은 파일 집합으로 해석됩니다. initiator는 이 파일들을 worker 역할을 하는 클러스터의 노드들에 분배합니다. 그리고 각 worker는 읽기를 마칠 때마다 다음에 처리할 파일을 요청합니다. 이 과정을 통해 읽기를 수평 확장할 수 있습니다.

`s3Cluster` 함수는 단일 노드 variant와 동일한 포맷을 사용하지만, worker 노드를 지정하기 위해 대상 클러스터를 반드시 지정해야 합니다:

```sql theme={null}
s3Cluster(cluster_name, source, [access_key_id, secret_access_key,] format, structure)
```

* `cluster_name` — 원격 및 로컬 서버에 대한 주소 집합과 연결 매개변수를 구성하는 데 사용되는 클러스터의 이름입니다.
* `source` — 파일 하나 또는 여러 파일에 대한 URL입니다. 읽기 전용 모드에서 다음 와일드카드를 지원합니다: `*`, `?`, `{'abc','def'}` 및 `{N..M}`. 여기서 N, M은 숫자이고 abc, def는 문자열입니다. 자세한 내용은 [Wildcards In Path](/docs/ko/reference/engines/table-engines/integrations/s3#wildcards-in-path)를 참조하십시오.
* `access_key_id` and `secret_access_key` — 지정된 엔드포인트와 함께 사용할 자격 증명을 지정하는 키입니다. 선택 사항입니다.
* `format` — 파일의 [포맷](/docs/ko/reference/formats/index#formats-overview)입니다.
* `structure` — 테이블의 구조입니다. 포맷은 'column1\_name column1\_type, column2\_name column2\_type, ...'입니다.

다른 모든 `s3` 함수와 마찬가지로, 버킷이 보안되지 않았거나 환경을 통해 보안을 구성한 경우(예: IAM roles) 자격 증명은 선택 사항입니다. 그러나 s3 함수와 달리, 22.3.1부터는 요청에서 구조를 지정해야 합니다. 즉, 스키마는 추론되지 않습니다.

이 함수는 대부분의 경우 `INSERT INTO SELECT`의 일부로 사용됩니다. 이 경우 대개 분산 테이블에 삽입하게 됩니다. 아래에서는 trips\_all이 분산 테이블인 간단한 예시를 보여 줍니다. 이 테이블은 events 클러스터를 사용하지만, 읽기와 쓰기에 사용되는 노드의 일관성은 필수 요구 사항이 아닙니다:

```sql theme={null}
INSERT INTO default.trips_all
   SELECT *
   FROM s3Cluster(
       'events',
       'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_*.gz',
       'TabSeparatedWithNames'
    )
```

삽입은 initiator 노드에서 수행됩니다. 즉, 읽기는 각 노드에서 수행되지만, 생성된 결과 행은 분산을 위해 initiator로 전달됩니다. 고처리량 시나리오에서는 이것이 병목이 될 수 있습니다. 이를 해결하려면 `s3cluster` 함수에 대해 [parallel\_distributed\_insert\_select](/docs/ko/reference/settings/session-settings#parallel_distributed_insert_select) 매개변수를 설정하십시오.

<div id="s3-table-engines">
  ## S3 테이블 엔진
</div>

`s3` 함수는 S3에 저장된 데이터에 대해 ad-hoc 쿼리를 수행할 수 있게 해주지만, 구문이 다소 장황합니다. `S3` 테이블 엔진을 사용하면 버킷 URL과 자격 증명을 반복해서 지정할 필요가 없습니다. 이를 위해 ClickHouse는 S3 테이블 엔진을 제공합니다.

```sql theme={null}
CREATE TABLE s3_engine_table (name String, value UInt32)
    ENGINE = S3(path, [aws_access_key_id, aws_secret_access_key,] format, [compression])
    [SETTINGS ...]
```

* `path` — 파일 경로를 포함한 버킷 URL입니다. 읽기 전용 모드에서는 다음 와일드카드를 지원합니다: `*`, `?`, `{abc,def}` 및 `{N..M}`. 여기서 N, M은 숫자이고, 'abc', 'def'는 문자열입니다. 자세한 내용은 [여기](/docs/ko/reference/engines/table-engines/integrations/s3#wildcards-in-path)를 참조하십시오.
* `format` — 파일의 [포맷](/docs/ko/reference/formats/index#formats-overview)입니다.
* `aws_access_key_id`, `aws_secret_access_key` - AWS 계정 사용자의 장기 자격 증명입니다. 이를 사용해 요청을 인증할 수 있습니다. 이 매개변수는 선택 사항입니다. 자격 증명을 지정하지 않으면 설정 파일의 값을 사용합니다. 자세한 내용은 [자격 증명 관리](#managing-credentials)를 참조하십시오.
* `compression` — 압축 유형입니다. 지원되는 값: none, gzip/gz, brotli/br, xz/LZMA, zstd/zst. 이 매개변수는 선택 사항입니다. 기본적으로 파일 확장자를 기준으로 압축을 자동 감지합니다.

<div id="reading-data">
  ### 데이터 읽기
</div>

다음 예시에서는 `https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/` 버킷에 있는 처음 10개의 TSV 파일을 사용해 `trips_raw`라는 테이블을 생성합니다. 각 파일에는 100만 개의 행이 포함되어 있습니다:

```sql theme={null}
CREATE TABLE trips_raw
(
   `trip_id`               UInt32,
   `vendor_id`             Enum8('1' = 1, '2' = 2, '3' = 3, '4' = 4, 'CMT' = 5, 'VTS' = 6, 'DDS' = 7, 'B02512' = 10, 'B02598' = 11, 'B02617' = 12, 'B02682' = 13, 'B02764' = 14, '' = 15),
   `pickup_date`           Date,
   `pickup_datetime`       DateTime,
   `dropoff_date`          Date,
   `dropoff_datetime`      DateTime,
   `store_and_fwd_flag`    UInt8,
   `rate_code_id`          UInt8,
   `pickup_longitude`      Float64,
   `pickup_latitude`       Float64,
   `dropoff_longitude`     Float64,
   `dropoff_latitude`      Float64,
   `passenger_count`       UInt8,
   `trip_distance`         Float64,
   `fare_amount`           Float32,
   `extra`                 Float32,
   `mta_tax`               Float32,
   `tip_amount`            Float32,
   `tolls_amount`          Float32,
   `ehail_fee`             Float32,
   `improvement_surcharge` Float32,
   `total_amount`          Float32,
   `payment_type_`         Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4),
   `trip_type`             UInt8,
   `pickup`                FixedString(25),
   `dropoff`               FixedString(25),
   `cab_type`              Enum8('yellow' = 1, 'green' = 2, 'uber' = 3),
   `pickup_nyct2010_gid`   Int8,
   `pickup_ctlabel`        Float32,
   `pickup_borocode`       Int8,
   `pickup_ct2010`         String,
   `pickup_boroct2010`     FixedString(7),
   `pickup_cdeligibil`     String,
   `pickup_ntacode`        FixedString(4),
   `pickup_ntaname`        String,
   `pickup_puma`           UInt16,
   `dropoff_nyct2010_gid`  UInt8,
   `dropoff_ctlabel`       Float32,
   `dropoff_borocode`      UInt8,
   `dropoff_ct2010`        String,
   `dropoff_boroct2010`    FixedString(7),
   `dropoff_cdeligibil`    String,
   `dropoff_ntacode`       FixedString(4),
   `dropoff_ntaname`       String,
   `dropoff_puma`          UInt16
) ENGINE = S3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_{0..9}.gz', 'TabSeparatedWithNames', 'gzip');
```

처음 10개 파일만 대상으로 제한하기 위해 `{0..9}` pattern을 사용한 점에 유의하십시오. 생성이 완료되면 이 table은 다른 table과 마찬가지로 쿼리할 수 있습니다:

```sql theme={null}
SELECT DISTINCT(pickup_ntaname)
FROM trips_raw
LIMIT 10;
```

```response theme={null}
┌─pickup_ntaname───────────────────────────────────┐
│ Lenox Hill-Roosevelt Island                      │
│ Airport                                          │
│ SoHo-TriBeCa-Civic Center-Little Italy           │
│ West Village                                     │
│ Chinatown                                        │
│ Hudson Yards-Chelsea-Flatiron-Union Square       │
│ Turtle Bay-East Midtown                          │
│ Upper West Side                                  │
│ Murray Hill-Kips Bay                             │
│ DUMBO-Vinegar Hill-Downtown Brooklyn-Boerum Hill │
└──────────────────────────────────────────────────┘
```

<div id="inserting-data">
  ### 데이터 삽입
</div>

`S3` 테이블 엔진은 병렬 읽기를 지원합니다. 쓰기는 테이블 정의에 글롭 패턴이 포함되지 않은 경우에만 지원됩니다. 따라서 위 테이블에는 쓸 수 없습니다.

쓰기 동작을 보여주기 위해, 쓰기 가능한 S3 버킷을 가리키는 테이블을 생성하십시오:

```sql theme={null}
CREATE TABLE trips_dest
(
   `trip_id`               UInt32,
   `pickup_date`           Date,
   `pickup_datetime`       DateTime,
   `dropoff_datetime`      DateTime,
   `tip_amount`            Float32,
   `total_amount`          Float32
) ENGINE = S3('<bucket path>/trips.bin', 'Native');
```

```sql theme={null}
INSERT INTO trips_dest
   SELECT
      trip_id,
      pickup_date,
      pickup_datetime,
      dropoff_datetime,
      tip_amount,
      total_amount
   FROM trips
   LIMIT 10;
```

```sql theme={null}
SELECT * FROM trips_dest LIMIT 5;
```

```response theme={null}
┌────trip_id─┬─pickup_date─┬─────pickup_datetime─┬────dropoff_datetime─┬─tip_amount─┬─total_amount─┐
│ 1200018648 │  2015-07-01 │ 2015-07-01 00:00:16 │ 2015-07-01 00:02:57 │          0 │          7.3 │
│ 1201452450 │  2015-07-01 │ 2015-07-01 00:00:20 │ 2015-07-01 00:11:07 │       1.96 │        11.76 │
│ 1202368372 │  2015-07-01 │ 2015-07-01 00:00:40 │ 2015-07-01 00:05:46 │          0 │          7.3 │
│ 1200831168 │  2015-07-01 │ 2015-07-01 00:01:06 │ 2015-07-01 00:09:23 │          2 │         12.3 │
│ 1201362116 │  2015-07-01 │ 2015-07-01 00:01:07 │ 2015-07-01 00:03:31 │          0 │          5.3 │
└────────────┴─────────────┴─────────────────────┴─────────────────────┴────────────┴──────────────┘
```

행은 새 파일에만 삽입할 수 있습니다. 머지 주기나 파일 분할 작업은 없습니다. 파일이 한 번 기록되면 이후 삽입은 실패합니다. 여기서는 두 가지 옵션이 있습니다:

* `s3_create_new_file_on_insert=1` 설정을 지정합니다. 그러면 삽입할 때마다 새 파일이 생성됩니다. 각 파일 끝에는 삽입 작업이 수행될 때마다 단조롭게 증가하는 숫자 접미사가 추가됩니다. 위 예시에서는 다음 삽입 시 trips\_1.bin 파일이 생성됩니다.
* `s3_truncate_on_insert=1` 설정을 지정합니다. 그러면 파일이 잘리며, 완료 후에는 새로 삽입된 행만 포함하게 됩니다.

이 두 설정의 기본값은 모두 0이므로, 사용자가 이 중 하나를 설정해야 합니다. 둘 다 설정된 경우에는 `s3_truncate_on_insert`가 우선 적용됩니다.

`S3` 테이블 엔진에 대한 몇 가지 참고 사항:

* 일반적인 `MergeTree` 계열 테이블과 달리 `S3` 테이블을 삭제해도 기반 데이터는 삭제되지 않습니다.
* 이 테이블 유형의 전체 설정은 [여기](/docs/ko/reference/engines/table-engines/integrations/s3#settings)에서 확인할 수 있습니다.
* 이 엔진을 사용할 때는 다음 사항에 유의하십시오:
  * `ALTER` 쿼리는 지원되지 않습니다
  * `SAMPLE` 작업은 지원되지 않습니다
  * 인덱스, 즉 프라이머리 인덱스나 스킵 인덱스 개념이 없습니다.

<div id="managing-credentials">
  ## 자격 증명 관리
</div>

이전 예시에서는 `s3` 함수 또는 `S3` 테이블 정의에 자격 증명을 전달했습니다. 가끔 사용하는 경우에는 이 방법도 괜찮을 수 있지만, 운영 환경에서는 자격 증명을 더 직접적으로 드러내지 않는 인증 메커니즘이 필요합니다. 이를 위해 ClickHouse는 여러 가지 옵션을 제공합니다.

* **config.xml** 또는 **conf.d** 아래의 이에 해당하는 설정 파일에 연결 정보를 지정합니다. 아래에는 debian 패키지로 설치했다고 가정한 예시 파일의 내용이 나와 있습니다.

  ```xml theme={null}
  ubuntu@single-node-clickhouse:/etc/clickhouse-server/config.d$ cat s3.xml
  <clickhouse>
      <s3>
          <endpoint-name>
              <endpoint>https://dalem-files.s3.amazonaws.com/test/</endpoint>
              <access_key_id>key</access_key_id>
              <secret_access_key>secret</secret_access_key>
              {/* <use_environment_credentials>false</use_environment_credentials> */}
              {/* <header>Authorization: Bearer SOME-TOKEN</header> */}
          </endpoint-name>
      </s3>
  </clickhouse>
  ```

  이 자격 증명은 위 엔드포인트가 요청 URL과 정확히 접두사 일치하는 모든 요청에 사용됩니다. 또한 이 예시에서는 액세스 키와 시크릿 키 대신 authorization header를 선언할 수 있다는 점도 확인할 수 있습니다. 지원되는 설정의 전체 목록은 [여기](/docs/ko/reference/engines/table-engines/integrations/s3#settings)에서 확인할 수 있습니다.

* 위 예시에서는 구성 매개변수 `use_environment_credentials`를 사용할 수 있음을 보여 줍니다. 이 구성 매개변수는 `s3` 수준에서 전역으로도 설정할 수 있습니다.

  ```xml theme={null}
  <clickhouse>
      <s3>
      <use_environment_credentials>true</use_environment_credentials>
      </s3>
  </clickhouse>
  ```

  이 설정을 사용하면 환경에서 S3 자격 증명을 가져오도록 시도하므로 IAM 역할을 통해 액세스할 수 있습니다. 구체적으로는 다음 순서로 자격 증명을 조회합니다.

  * 환경 변수 `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` 조회
  * **\$HOME/.aws** 확인
  * AWS Security Token Service, 즉 [`AssumeRole`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) API를 통해 획득한 임시 자격 증명
  * ECS 환경 변수 `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` 또는 `AWS_CONTAINER_CREDENTIALS_FULL_URI` 및 `AWS_ECS_CONTAINER_AUTHORIZATION_TOKEN`에서 자격 증명 확인
  * [Amazon EC2 instance metadata](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-metadata.html)를 통해 자격 증명을 가져옵니다. 단, [AWS\_EC2\_METADATA\_DISABLED](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-list-AWS_EC2_METADATA_DISABLED)가 true로 설정되어 있지 않아야 합니다.
  * 동일한 설정을 같은 접두사 일치 규칙을 사용하여 특정 엔드포인트에 대해서도 설정할 수 있습니다.

<div id="s3-optimizing-performance">
  ## 성능 최적화
</div>

s3 함수를 사용한 읽기 및 삽입 최적화 방법은 [별도의 성능 가이드](/docs/ko/integrations/connectors/data-ingestion/AWS/performance)를 참조하십시오.

<div id="s3-storage-tuning">
  ### S3 저장소 튜닝
</div>

내부적으로 ClickHouse MergeTree는 두 가지 주요 저장 포맷인 [`Wide` and `Compact`](/docs/ko/reference/engines/table-engines/mergetree-family/mergetree#mergetree-data-storage)를 사용합니다. 현재 구현은 ClickHouse의 기본 동작(`min_bytes_for_wide_part` 및 `min_rows_for_wide_part` 설정으로 제어됨)을 따르지만, 향후 릴리스에서는 S3의 경우 이 동작이 달라질 것으로 예상합니다. 예를 들어 `min_bytes_for_wide_part`의 기본값을 더 크게 설정해 `Compact` 포맷을 더 많이 사용하도록 유도하여 파일 수를 줄일 수 있습니다. 따라서 S3 저장소만 사용하는 경우 이러한 설정을 조정하는 것이 좋습니다.

<div id="s3-backed-mergetree">
  ## S3 기반 MergeTree
</div>

`s3` 함수와 관련 테이블 엔진을 사용하면 익숙한 ClickHouse 구문으로 S3의 데이터를 쿼리할 수 있습니다. 하지만 데이터 관리 기능과 성능 면에서는 한계가 있습니다. 프라이머리 인덱스를 지원하지 않고, no-cache도 지원하지 않으며, 파일 삽입은 사용자가 직접 관리해야 합니다.

ClickHouse는 특히 상대적으로 "콜드한" 데이터에 대해서는 쿼리 성능이 덜 중요하고, 스토리지와 컴퓨트를 분리하려는 경우 S3가 매력적인 스토리지 솔루션이 될 수 있음을 인식하고 있습니다. 이를 위해 MergeTree 엔진의 스토리지로 S3를 사용할 수 있도록 지원합니다. 이를 통해 S3의 확장성과 비용상 이점을 활용하는 동시에 MergeTree 엔진의 삽입 및 쿼리 성능도 누릴 수 있습니다.

<div id="storage-tiers">
  ### 스토리지 계층
</div>

ClickHouse 스토리지 볼륨을 사용하면 물리 디스크를 MergeTree 테이블 엔진과 분리해 추상화할 수 있습니다. 하나의 볼륨은 순서가 지정된 디스크 집합으로 구성될 수 있습니다. 이 추상화는 기본적으로 데이터 저장에 여러 블록 디바이스를 사용할 수 있게 해줄 뿐만 아니라, S3를 비롯한 다른 스토리지 유형도 사용할 수 있게 합니다. ClickHouse 데이터 파트는 스토리지 정책과 공간 사용률에 따라 볼륨 간에 이동할 수 있으므로, 스토리지 계층이라는 개념이 성립합니다.

스토리지 계층을 사용하면 최신 데이터처럼 일반적으로 가장 자주 쿼리되는 데이터를 고성능 스토리지(예: NVMe SSD)에 소량만 두는 핫-콜드 아키텍처를 구현할 수 있습니다. 데이터가 오래될수록 쿼리 시점 SLA는 완화되고 쿼리 빈도도 낮아집니다. 이렇게 긴 꼬리를 이루는 데이터는 HDD와 같은 더 느리고 성능이 낮은 스토리지나 S3와 같은 객체 스토리지에 저장할 수 있습니다.

<div id="creating-a-disk">
  ### 디스크 생성
</div>

S3 버킷을 디스크로 사용하려면 먼저 ClickHouse 설정 파일에 이를 선언해야 합니다. `config.xml`을 확장하거나, 가능하면 `conf.d` 아래에 새 파일을 추가하십시오. S3 디스크 선언 예시는 아래와 같습니다.

```xml theme={null}
<clickhouse>
    <storage_configuration>
        ...
        <disks>
            <s3>
                <type>s3</type>
                <endpoint>https://sample-bucket.s3.us-east-2.amazonaws.com/tables/</endpoint>
                <access_key_id>your_access_key_id</access_key_id>
                <secret_access_key>your_secret_access_key</secret_access_key>
                <region></region>
                <metadata_path>/var/lib/clickhouse/disks/s3/</metadata_path>
            </s3>
            <s3_cache>
                <type>cache</type>
                <disk>s3</disk>
                <path>/var/lib/clickhouse/disks/s3_cache/</path>
                <max_size>10Gi</max_size>
            </s3_cache>
        </disks>
        ...
    </storage_configuration>
</clickhouse>

```

이 디스크 선언과 관련된 설정의 전체 목록은 [여기](/docs/ko/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-s3)에서 확인할 수 있습니다. 자격 증명은 [자격 증명 관리](#managing-credentials)에서 설명한 것과 동일한 방식으로 여기에서 관리할 수 있습니다. 즉, IAM 역할을 사용하려면 위 설정 블록에서 use\_environment\_credentials를 true로 설정할 수 있습니다.

<div id="creating-a-storage-policy">
  ### 스토리지 정책 만들기
</div>

구성이 완료되면 이 "디스크"를 정책 내에 선언된 스토리지 볼륨에서 사용할 수 있습니다. 아래 예시에서는 S3만 스토리지로 사용한다고 가정합니다. 여기서는 TTL과 공간 사용률에 따라 데이터를 재배치할 수 있는 더 복잡한 핫-콜드 아키텍처는 고려하지 않습니다.

```xml theme={null}
<clickhouse>
    <storage_configuration>
        <disks>
            <s3>
            ...
            </s3>
            <s3_cache>
            ...
            </s3_cache>
        </disks>
        <policies>
            <s3_main>
                <volumes>
                    <main>
                        <disk>s3</disk>
                    </main>
                </volumes>
            </s3_main>
        </policies>
    </storage_configuration>
</clickhouse>
```

<div id="creating-a-table">
  ### 테이블 생성
</div>

쓰기 권한이 있는 버킷을 사용하도록 디스크를 구성했다면, 아래 예시와 같이 테이블을 생성할 수 있습니다. 간단히 설명하기 위해 NYC 택시 컬럼 일부만 사용하고, 데이터를 S3 기반 테이블로 직접 스트리밍합니다:

```sql theme={null}
CREATE TABLE trips_s3
(
   `trip_id` UInt32,
   `pickup_date` Date,
   `pickup_datetime` DateTime,
   `dropoff_datetime` DateTime,
   `pickup_longitude` Float64,
   `pickup_latitude` Float64,
   `dropoff_longitude` Float64,
   `dropoff_latitude` Float64,
   `passenger_count` UInt8,
   `trip_distance` Float64,
   `tip_amount` Float32,
   `total_amount` Float32,
   `payment_type` Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(pickup_date)
ORDER BY pickup_datetime
SETTINGS storage_policy='s3_main'
```

```sql theme={null}
INSERT INTO trips_s3 SELECT trip_id, pickup_date, pickup_datetime, dropoff_datetime, pickup_longitude, pickup_latitude, dropoff_longitude, dropoff_latitude, passenger_count, trip_distance, tip_amount, total_amount, payment_type FROM s3('https://ch-nyc-taxi.s3.eu-west-3.amazonaws.com/tsv/trips_{0..9}.tsv.gz', 'TabSeparatedWithNames') LIMIT 1000000;
```

하드웨어에 따라 앞서 언급한 100만 행 삽입은 실행하는 데 몇 분 정도 걸릴 수 있습니다. 진행 상황은 system.processes 테이블을 통해 확인할 수 있습니다. 행 수는 최대 1000만까지 조정하고 몇 가지 예제 쿼리도 살펴보세요.

```sql theme={null}
SELECT passenger_count, avg(tip_amount) AS avg_tip, avg(total_amount) AS avg_amount FROM trips_s3 GROUP BY passenger_count;
```

<div id="modifying-a-table">
  ### 테이블 수정
</div>

경우에 따라 특정 테이블의 스토리지 정책를 변경해야 할 수 있습니다. 이는 가능하지만 몇 가지 제약이 있습니다. 새 대상 policy에는 이전 policy의 모든 디스크와 볼륨이 포함되어야 합니다. 즉, policy 변경에 맞추기 위해 데이터가 마이그레이션되지는 않습니다. 이러한 제약을 검증할 때 볼륨과 디스크는 이름으로 식별되며, 이를 위반하려고 하면 오류가 발생합니다. 다만 앞선 예시를 기준으로 하면, 다음 변경은 유효합니다.

```xml theme={null}
<policies>
   <s3_main>
       <volumes>
           <main>
               <disk>s3</disk>
           </main>
       </volumes>
   </s3_main>
   <s3_tiered>
       <volumes>
           <hot>
               <disk>default</disk>
           </hot>
           <main>
               <disk>s3</disk>
           </main>
       </volumes>
       <move_factor>0.2</move_factor>
   </s3_tiered>
</policies>
```

```sql theme={null}
ALTER TABLE trips_s3 MODIFY SETTING storage_policy='s3_tiered'
```

여기서는 새로운 s3\_tiered 정책에서 기본 볼륨을 재사용하고, 새로운 hot 볼륨을 추가합니다. 이는 기본 디스크를 사용하며, 이 디스크는 매개변수 `<path>``를` 통해 구성된 단일 디스크로만 이루어져 있습니다. 볼륨 이름과 디스크는 바뀌지 않는다는 점에 유의하십시오. 테이블(table)에 새로 삽입되는 데이터는 move\_factor \* disk\_size에 도달할 때까지 기본 디스크에 저장되며, 그 시점에 S3로 이동됩니다.

<div id="handling-replication">
  ### 복제 처리
</div>

S3 디스크를 사용할 때는 `ReplicatedMergeTree` 테이블 엔진으로 복제를 구성할 수 있습니다. 자세한 내용은 [S3 객체 스토리지를 사용해 두 AWS 리전에 걸쳐 단일 세그먼트를 복제하는](#s3-multi-region) 가이드를 참조하십시오.

<div id="read--writes">
  ### 읽기 및 쓰기
</div>

다음 참고 사항은 ClickHouse의 S3 상호작용 구현을 설명합니다. 주로 참고용 정보이지만, [성능 최적화](#s3-optimizing-performance) 시 도움이 될 수 있습니다.

* 기본적으로 쿼리 처리 파이프라인의 각 단계에서 사용할 수 있는 최대 쿼리 처리 스레드 수는 코어 수와 같습니다. 일부 단계는 다른 단계보다 병렬화가 더 잘되므로, 이 값은 상한선 역할을 합니다. 데이터가 디스크에서 스트리밍되기 때문에 여러 쿼리 단계가 동시에 실행될 수 있습니다. 따라서 실제 쿼리에 사용되는 스레드 수는 이 값을 초과할 수 있습니다. 이 값은 [max\_threads](/docs/ko/reference/settings/session-settings#max_threads) 설정으로 변경할 수 있습니다.
* S3 읽기는 기본적으로 비동기 방식으로 수행됩니다. 이 동작은 `remote_filesystem_read_method` 설정으로 결정되며, 기본값은 `threadpool`입니다. 요청을 처리할 때 ClickHouse는 그래뉼을 스트라이프 단위로 읽습니다. 각 스트라이프에는 여러 컬럼이 포함될 수 있습니다. 스레드는 해당 그래뉼의 컬럼을 하나씩 읽습니다. 이를 동기식으로 처리하는 대신, 데이터를 기다리기 전에 모든 컬럼에 대해 프리페치를 수행합니다. 이 방식은 각 컬럼마다 동기적으로 기다리는 것보다 성능이 크게 향상됩니다. 대부분의 경우 이 설정을 변경할 필요는 없습니다. 자세한 내용은 [성능 최적화](#s3-optimizing-performance)를 참조하십시오.
* 쓰기는 병렬로 수행되며, 동시에 사용할 수 있는 파일 쓰기 스레드는 최대 100개입니다. 기본값이 1000인 `max_insert_delayed_streams_for_parallel_write`는 병렬로 기록되는 S3 blob 수를 제어합니다. 기록 중인 각 파일마다 버퍼(\~1MB)가 필요하므로, 이는 사실상 INSERT의 메모리 사용량을 제한합니다. 서버 메모리가 적은 환경에서는 이 값을 낮추는 것이 적절할 수 있습니다.

<div id="configuring-s3-for-clickhouse-use">
  ## S3 객체 스토리지를 ClickHouse 디스크로 사용
</div>

버킷과 IAM 역할을 생성하는 단계별 지침이 필요하면 ["AWS IAM 사용자 및 S3 버킷 생성 방법"](/docs/ko/integrations/connectors/data-ingestion/AWS/creating-an-s3-iam-role-and-bucket)을 참조하십시오.

<div id="configure-clickhouse-to-use-the-s3-bucket-as-a-disk">
  ### ClickHouse에서 S3 버킷을 디스크로 사용하도록 구성
</div>

다음 예시는 기본 ClickHouse 디렉터리를 사용하는 Linux Deb 패키지를 서비스로 설치한 환경을 기준으로 합니다.

1. 스토리지 구성을 저장할 새 파일을 ClickHouse `config.d` 디렉터리에 생성합니다.

```bash theme={null}
vim /etc/clickhouse-server/config.d/storage_config.xml
```

2. 스토리지 구성을 위해 다음을 추가합니다. 이때 버킷 경로, 액세스 키, 시크릿 키는 앞선 단계의 값으로 바꾸십시오.

```xml theme={null}
<clickhouse>
  <storage_configuration>
    <disks>
      <s3_disk>
        <type>s3</type>
        <endpoint>https://mars-doc-test.s3.amazonaws.com/clickhouse3/</endpoint>
        <access_key_id>ABC123</access_key_id>
        <secret_access_key>Abc+123</secret_access_key>
        <metadata_path>/var/lib/clickhouse/disks/s3_disk/</metadata_path>
      </s3_disk>
      <s3_cache>
        <type>cache</type>
        <disk>s3_disk</disk>
        <path>/var/lib/clickhouse/disks/s3_cache/</path>
        <max_size>10Gi</max_size>
      </s3_cache>
    </disks>
    <policies>
      <s3_main>
        <volumes>
          <main>
            <disk>s3_disk</disk>
          </main>
        </volumes>
      </s3_main>
    </policies>
  </storage_configuration>
</clickhouse>
```

<Note>
  `<disks>` 태그 안의 `s3_disk` 및 `s3_cache` 태그는 임의의 레이블입니다. 다른 이름으로 설정할 수 있지만, 디스크를 참조하려면 `<policies>` 태그 아래의 `<disk>` 태그에서도 동일한 레이블을 사용해야 합니다.
  `<S3_main>` 태그 역시 임의이며, ClickHouse에서 리소스를 생성할 때 식별자 스토리지 대상으로 사용할 정책 이름입니다.

  위에 표시된 구성은 ClickHouse 버전 22.8 이상 기준입니다. 이전 버전을 사용 중이라면 [데이터 저장](/docs/ko/concepts/features/configuration/server-config/storing-data#using-local-cache) 문서를 참조하십시오.

  S3 사용에 관한 자세한 내용:
  통합 가이드: [S3 기반 MergeTree](#s3-backed-mergetree)
</Note>

3. 파일 소유자를 `clickhouse` 사용자와 그룹으로 변경합니다.

```bash theme={null}
chown clickhouse:clickhouse /etc/clickhouse-server/config.d/storage_config.xml
```

4. 변경 사항이 적용되도록 ClickHouse 인스턴스를 다시 시작합니다.

```bash theme={null}
service clickhouse-server restart
```

<div id="testing">
  ### 테스트
</div>

1. 다음과 같이 ClickHouse client로 로그인합니다

```bash theme={null}
clickhouse-client --user default --password ClickHouse123!
```

2. 새 S3 스토리지 정책을 지정하여 테이블을 생성합니다

```sql theme={null}
CREATE TABLE s3_table1
           (
               `id` UInt64,
               `column1` String
           )
           ENGINE = MergeTree
           ORDER BY id
           SETTINGS storage_policy = 's3_main';
```

3. 테이블이 올바른 정책으로 생성되었는지 확인하세요

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

```response theme={null}
┌─statement────────────────────────────────────────────────────
│ CREATE TABLE default.s3_table1
(
    `id` UInt64,
    `column1` String
)
ENGINE = MergeTree
ORDER BY id
SETTINGS storage_policy = 's3_main', index_granularity = 8192
└──────────────────────────────────────────────────────────────
```

4. 테이블에 테스트 행을 삽입합니다

```sql theme={null}
INSERT INTO s3_table1
           (id, column1)
           VALUES
           (1, 'abc'),
           (2, 'xyz');
```

```response theme={null}
INSERT INTO s3_table1 (id, column1) FORMAT Values

Query id: 0265dd92-3890-4d56-9d12-71d4038b85d5

Ok.

2 rows in set. Elapsed: 0.337 sec.
```

5. 행 확인하기

```sql theme={null}
SELECT * FROM s3_table1;
```

```response theme={null}
┌─id─┬─column1─┐
│  1 │ abc     │
│  2 │ xyz     │
└────┴─────────┘

2 rows in set. Elapsed: 0.284 sec.
```

6. AWS 콘솔에서 버킷으로 이동한 다음, 새로 만든 버킷과 폴더를 선택합니다.
   다음과 비슷한 화면이 표시됩니다:

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/s3/s3-j.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=2ac66fbed68c9cc2e64a4b5ef1cf9a26" size="lg" border alt="S3에 저장된 ClickHouse 데이터 파일을 보여주는 AWS 콘솔의 S3 버킷 보기" width="1208" height="736" data-path="images/integrations/data-ingestion/s3/s3-j.webp" />

<div id="s3-multi-region">
  ## S3 객체 스토리지를 사용해 단일 세그먼트를 2개의 AWS 리전에 걸쳐 복제하기
</div>

<Tip>
  ClickHouse Cloud에서는 기본적으로 객체 스토리지를 사용하므로, ClickHouse Cloud를 실행 중이라면 이 절차를 따를 필요가 없습니다.
</Tip>

<div id="plan-the-deployment">
  ### 배포 계획 수립
</div>

이 튜토리얼은 AWS EC2에 ClickHouse 서버 노드 2개와 ClickHouse Keeper 노드 3개를 배포하는 구성을 기준으로 합니다. ClickHouse 서버의 데이터 저장소는 S3입니다. 재해 복구를 지원하기 위해 2개의 AWS 리전을 사용하며, 각 리전에는 ClickHouse 서버 1대와 S3 버킷 1개가 있습니다.

ClickHouse 테이블은 두 서버 간에 복제되므로, 두 리전 간에도 복제됩니다.

<div id="install-software">
  ### 소프트웨어 설치
</div>

<div id="clickhouse-server-nodes">
  #### ClickHouse 서버 노드
</div>

ClickHouse 서버 노드에서 배포 단계를 진행할 때는 [설치 안내](/docs/ko/get-started/setup/install)를 참조하십시오.

<div id="deploy-clickhouse">
  #### ClickHouse 배포
</div>

두 개의 호스트에 ClickHouse를 배포하십시오. 예시 구성에서는 각 호스트의 이름을 `chnode1`, `chnode2`로 지정합니다.

`chnode1`은 한 AWS 리전에, `chnode2`는 다른 AWS 리전에 배치하십시오.

<div id="deploy-clickhouse-keeper">
  #### ClickHouse Keeper 배포
</div>

세 개의 호스트에 ClickHouse Keeper를 배포합니다. 예시 구성에서는 이 호스트의 이름을 `keepernode1`, `keepernode2`, `keepernode3`로 지정합니다. `keepernode1`은 `chnode1`과 동일한 리전에, `keepernode2`는 `chnode2`와 동일한 리전에 배포할 수 있습니다. `keepernode3`는 두 리전 중 어느 곳에든 배포할 수 있지만, 해당 리전의 ClickHouse 노드와는 서로 다른 가용 영역에 배포해야 합니다.

ClickHouse Keeper 노드에서 배포 단계를 수행할 때는 [설치 지침](/docs/ko/get-started/setup/install)을 참조하십시오.

<div id="create-s3-buckets">
  ### S3 버킷 만들기
</div>

`chnode1` 및 `chnode2`를 배치한 각 리전에 하나씩, 총 2개의 S3 버킷을 만드십시오.

버킷과 IAM 역할을 만드는 단계별 지침이 필요하면 **S3 버킷 및 IAM 역할 만들기**를 펼쳐서 안내에 따라 진행하십시오.

<Accordion title="S3 버킷과 IAM 사용자 생성">
  이 문서에서는 AWS IAM 사용자를 구성하고, S3 버킷을 생성하며, ClickHouse가 해당 버킷을 S3 디스크로 사용하도록 설정하는 기본적인 방법을 설명합니다.
  실제로 적용할 권한은 보안 팀과 협의하여 결정하시고, 이 문서의 내용은 시작점으로 참고하시기 바랍니다.

  ### AWS IAM 사용자 생성

  다음 단계에서는 서비스 계정 사용자(로그인 사용자가 아닌)를 생성합니다.

  1. AWS IAM Management Console에 로그인합니다.

  2. `Users` 메뉴에서 `Create user`를 선택합니다.

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-1.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=20e707f821991442e19c148412f5bc77" alt="AWS IAM Management Console - 신규 사용자 추가" width="1493" height="307" data-path="images/_snippets/s3/s3-1.webp" />
    </Frame>
  </div>

  3. 사용자 이름을 입력하고 자격 증명 유형을 `Access key - Programmatic access`로 설정한 후 `Next: Permissions`를 선택하세요

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-2.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=6c7a4126e66eaa2d910c8ce6e65a524e" alt="IAM user 이름 및 액세스 유형 설정" width="984" height="556" data-path="images/_snippets/s3/s3-2.webp" />
    </Frame>
  </div>

  4. 사용자를 어떤 그룹에도 추가하지 말고 `다음: Tags`를 선택하세요

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-3.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=0dcb52d53dc77997d940c4d643393328" alt="IAM 사용자의 그룹 할당 건너뛰기" width="999" height="557" data-path="images/_snippets/s3/s3-3.webp" />
    </Frame>
  </div>

  5. 태그를 추가할 필요가 없다면 `Next: Review`를 선택하세요

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-4.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=648e411838e6a660aeac2aa8a3616a58" alt="IAM user의 태그 할당 스키핑" width="983" height="386" data-path="images/_snippets/s3/s3-4.webp" />
    </Frame>
  </div>

  6. `Create User`를 클릭하세요

  <Note>
    사용자에게 권한이 없다는 경고 메시지는 무시해도 됩니다. 다음 섹션에서 해당 사용자에게 버킷 권한이 부여됩니다.
  </Note>

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-5.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=857c0f0f5100e3dbbe41809a0743f7e5" alt="권한 없음 경고가 표시된 IAM user 생성" width="987" height="581" data-path="images/_snippets/s3/s3-5.webp" />
    </Frame>
  </div>

  7. 이제 사용자가 생성되었으므로 `show`를 클릭한 다음 액세스 키와 시크릿 키를 복사하십시오.

  <Note>
    키는 다른 곳에 저장해 두십시오. 비밀 액세스 키는 지금만 확인할 수 있습니다.
  </Note>

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-6.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=ba6d29afe5d04ff408c4bc0f0ebafd68" alt="IAM 사용자 액세스 키 보기 및 복사" width="983" height="576" data-path="images/_snippets/s3/s3-6.webp" />
    </Frame>
  </div>

  8. 해지를 클릭한 다음 사용자 화면에서 해당 사용자를 찾습니다.

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-7.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=bbd4d1c7a069f10809a5c714b11902b6" alt="사용자 목록에서 새로 생성된 IAM 사용자 찾기" width="837" height="54" data-path="images/_snippets/s3/s3-7.webp" />
    </Frame>
  </div>

  9. ARN(Amazon Resource Name)을 복사한 다음, 버킷의 액세스 정책을 구성할 때 사용할 수 있도록 저장합니다.

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-8.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=b5bf97f932344605d21cdbf3c9ca908c" alt="IAM 사용자의 ARN 복사" width="595" height="265" data-path="images/_snippets/s3/s3-8.webp" />
    </Frame>
  </div>

  ### S3 버킷 생성

  1. S3 버킷 섹션에서 `Create bucket`을 선택하십시오

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-9.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=23ec432c53962401f8e31677d09ea84b" alt="S3 버킷 생성 시작" width="1465" height="326" data-path="images/_snippets/s3/s3-9.webp" />
    </Frame>
  </div>

  2. 버킷 이름을 입력하고, 나머지 옵션은 기본값으로 그대로 둡니다

  <Note>
    버킷 이름은 조직 내에서만이 아니라 AWS 전체에서 고유해야 하며, 그렇지 않으면 오류가 발생합니다.
  </Note>

  3. `Block all Public Access`는 활성화된 상태로 유지하십시오. 공개 액세스는 필요하지 않습니다.

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-a.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=7ca0194d8bd932f723d42bd037f471da" alt="퍼블릭 액세스 차단이 설정된 S3 버킷 설정" width="841" height="754" data-path="images/_snippets/s3/s3-a.webp" />
    </Frame>
  </div>

  4. 페이지 하단의 `Create Bucket`을 선택하세요

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-b.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=fe4b8c6a79181561d17800b89e808034" alt="S3 버킷 생성 완료" width="826" height="132" data-path="images/_snippets/s3/s3-b.webp" />
    </Frame>
  </div>

  5. 링크를 선택하여 ARN을 복사한 다음, 버킷의 액세스 정책을 구성할 때 사용할 수 있도록 저장하십시오.

  6. 버킷이 생성되면 S3 버킷 목록에서 새 S3 버킷을 찾은 다음 해당 링크를 선택하십시오.

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-c.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=4006ee32da0d3f8870ba7e3dc06e8d60" alt="버킷 목록에서 새로 생성한 S3 버킷 찾기" width="1088" height="56" data-path="images/_snippets/s3/s3-c.webp" />
    </Frame>
  </div>

  7. `Create folder`를 선택하세요

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-d.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=813b77190d1eb1ff2d377f5058681534" alt="S3 버킷에 새 폴더를 만드는 모습" width="1134" height="448" data-path="images/_snippets/s3/s3-d.webp" />
    </Frame>
  </div>

  8. ClickHouse S3 디스크의 대상 폴더 이름을 입력하고 `Create folder`를 선택합니다

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-e.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=750f65d9e7a9745cc1c0f0c0b3998e0e" alt="ClickHouse S3 디스크에 사용할 폴더 이름 설정" width="853" height="788" data-path="images/_snippets/s3/s3-e.webp" />
    </Frame>
  </div>

  9. 이제 버킷 목록에 폴더가 표시됩니다

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-f.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=3d35274252c80befbc0509932f8bb749" alt="S3 버킷에서 새로 생성된 폴더를 보는 모습" width="1207" height="569" data-path="images/_snippets/s3/s3-f.webp" />
    </Frame>
  </div>

  10. 새 폴더의 체크박스를 선택하고 `Copy URL`을 클릭하세요. 다음 섹션의 ClickHouse 스토리지 구성에서 사용할 수 있도록 복사한 URL을 저장해 두세요.

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-g.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=7c15648627a4df208d6b6061f6b13257" alt="ClickHouse 구성에 사용할 S3 폴더 URL 복사" width="1200" height="569" data-path="images/_snippets/s3/s3-g.webp" />
    </Frame>
  </div>

  11. `Permissions` 탭을 선택한 다음 `Bucket Policy` 섹션에서 `Edit` 버튼을 클릭하세요

  <div className="ch-image-md">
    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99/CFFsa2agBPbviR4r/images/_snippets/s3/s3-h.webp?fit=max&auto=format&n=CFFsa2agBPbviR4r&q=85&s=c5cd7c3206d4c758064aa6d3516f7ad3" alt="S3 버킷 정책 구성에 액세스" width="1176" height="762" data-path="images/_snippets/s3/s3-h.webp" />
    </Frame>
  </div>

  12. 버킷 정책을 추가하세요. 아래는 예시입니다:

  ```json theme={null}
  {
    "Version" : "2012-10-17",
    "Id" : "Policy123456",
    "Statement" : [
      {
        "Sid" : "abc123",
        "Effect" : "Allow",
        "Principal" : {
          "AWS" : "arn:aws:iam::921234567898:user/mars-s3-user"
        },
        "Action" : "s3:*",
        "Resource" : [
          "arn:aws:s3:::mars-doc-test",
          "arn:aws:s3:::mars-doc-test/*"
        ]
      }
    ]
  }
  ```

  ```response theme={null}
  |Parameter | Description | Example Value |
  |----------|-------------|----------------|
  |Version | Version of the policy interpreter, leave as-is | 2012-10-17 |
  |Sid | User-defined policy id | abc123 |
  |Effect | Whether user requests will be allowed or denied | Allow |
  |Principal | The accounts or user that will be allowed | arn:aws:iam::921234567898:user/mars-s3-user |
  |Action | What operations are allowed on the bucket| s3:*|
  |Resource | Which resources in the bucket will operations be allowed in | "arn:aws:s3:::mars-doc-test", "arn:aws:s3:::mars-doc-test/*" |
  ```

  <Note>
    사용할 권한을 결정할 때는 보안 팀과 협의하고, 아래 내용은 출발점으로 삼으십시오.
    정책 및 설정에 대한 자세한 내용은 AWS 문서를 참조하십시오:
    [https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html)
  </Note>

  13. 정책 구성을 저장하세요.
</Accordion>

그런 다음 설정 파일은 `/etc/clickhouse-server/config.d/`에 배치됩니다. 아래는 한 버킷에 대한 예시 설정 파일이며, 다른 하나도 거의 동일하고 강조 표시된 3개 줄만 다릅니다:

```xml title="/etc/clickhouse-server/config.d/storage_config.xml" highlight={6-8} theme={null}
<clickhouse>
  <storage_configuration>
     <disks>
        <s3_disk>
           <type>s3</type>
           <endpoint>https://docs-clickhouse-s3.s3.us-east-2.amazonaws.com/clickhouses3/</endpoint>
           <access_key_id>ABCDEFGHIJKLMNOPQRST</access_key_id>
           <secret_access_key>Tjdm4kf5snfkj303nfljnev79wkjn2l3knr81007</secret_access_key>
           <metadata_path>/var/lib/clickhouse/disks/s3_disk/</metadata_path>
        </s3_disk>

        <s3_cache>
           <type>cache</type>
           <disk>s3_disk</disk>
           <path>/var/lib/clickhouse/disks/s3_cache/</path>
           <max_size>10Gi</max_size>
        </s3_cache>
     </disks>
        <policies>
            <s3_main>
                <volumes>
                    <main>
                        <disk>s3_disk</disk>
                    </main>
                </volumes>
            </s3_main>
    </policies>
   </storage_configuration>
</clickhouse>
```

<Note>
  이 가이드의 여러 단계에서는 설정 파일을 `/etc/clickhouse-server/config.d/`에 배치해야 합니다. 이 경로는 Linux 시스템에서 설정 재정의 파일에 사용하는 기본 위치입니다. 이 디렉터리에 이러한 파일을 넣으면 ClickHouse가 해당 내용을 사용해 기본 구성을 재정의합니다. 이처럼 재정의 디렉터리에 파일을 배치하면 업그레이드 중 구성이 손실되는 것을 방지할 수 있습니다.
</Note>

<div id="configure-clickhouse-keeper">
  ### ClickHouse Keeper 구성
</div>

ClickHouse Keeper를 standalone으로(ClickHouse 서버와 분리하여) 실행하는 경우, 구성은 단일 XML 파일로 이루어집니다. 이 튜토리얼에서 사용하는 파일은 `/etc/clickhouse-keeper/keeper_config.xml`입니다. 세 개의 Keeper 서버는 모두 동일한 구성을 사용하며, 단 하나의 설정인 `<server_id>`만 다릅니다.

`server_id`는 해당 설정 파일을 사용하는 호스트에 할당할 ID를 나타냅니다. 아래 예시에서 `server_id`는 `3`이며, 파일의 아래쪽 `<raft_configuration>` 섹션을 보면 서버 3의 호스트명이 `keepernode3`인 것을 확인할 수 있습니다. ClickHouse Keeper 프로세스는 이를 바탕으로 리더를 선출할 때와 그 밖의 모든 작업에서 어떤 다른 서버에 연결해야 하는지 판단합니다.

```xml title="/etc/clickhouse-keeper/keeper_config.xml" highlight={12,33-37} theme={null}
<clickhouse>
    <logger>
        <level>trace</level>
        <log>/var/log/clickhouse-keeper/clickhouse-keeper.log</log>
        <errorlog>/var/log/clickhouse-keeper/clickhouse-keeper.err.log</errorlog>
        <size>1000M</size>
        <count>3</count>
    </logger>
    <listen_host>0.0.0.0</listen_host>
    <keeper_server>
        <tcp_port>9181</tcp_port>
        <server_id>3</server_id>
        <log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
        <snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>

        <coordination_settings>
            <operation_timeout_ms>10000</operation_timeout_ms>
            <session_timeout_ms>30000</session_timeout_ms>
            <raft_logs_level>warning</raft_logs_level>
        </coordination_settings>

        <raft_configuration>
            <server>
                <id>1</id>
                <hostname>keepernode1</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>2</id>
                <hostname>keepernode2</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>3</id>
                <hostname>keepernode3</hostname>
                <port>9234</port>
            </server>
        </raft_configuration>
    </keeper_server>
</clickhouse>
```

ClickHouse Keeper의 설정 파일을 해당 위치에 복사합니다(`<server_id>`를 설정하는 것을 잊지 마십시오):

```bash theme={null}
sudo -u clickhouse \
  cp keeper.xml /etc/clickhouse-keeper/keeper.xml
```

<div id="configure-clickhouse-server">
  ### ClickHouse 서버 설정
</div>

<div id="define-a-cluster">
  #### 클러스터 정의
</div>

ClickHouse 클러스터는 구성의 `<remote_servers>` 섹션에 정의됩니다. 이 예시에서는 `cluster_1S_2R` 클러스터 1개를 정의하며, 이 클러스터는 2개의 레플리카가 있는 단일 세그먼트로 구성됩니다. 레플리카는 `chnode1` 및 `chnode2` 호스트에 있습니다.

```xml title="/etc/clickhouse-server/config.d/remote-servers.xml" theme={null}
<clickhouse>
    <remote_servers replace="true">
        <cluster_1S_2R>
            <shard>
                <replica>
                    <host>chnode1</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>chnode2</host>
                    <port>9000</port>
                </replica>
            </shard>
        </cluster_1S_2R>
    </remote_servers>
</clickhouse>
```

클러스터에서 작업할 때는 DDL 쿼리에 클러스터, 세그먼트, 레플리카 설정을 자동으로 채워 넣는 매크로를 정의해 두면 편리합니다.  이 예제에서는 `shard` 및 `replica` 세부 정보를 직접 지정하지 않고도 복제된 테이블 엔진을 사용할 수 있습니다.  테이블을 생성한 후 `system.tables`를 쿼리하면 `shard` 및 `replica` 매크로가 어떻게 사용되는지 확인할 수 있습니다.

```xml title="/etc/clickhouse-server/config.d/macros.xml" theme={null}
<clickhouse>
    <distributed_ddl>
            <path>/clickhouse/task_queue/ddl</path>
    </distributed_ddl>
    <macros>
        <cluster>cluster_1S_2R</cluster>
        <shard>1</shard>
        <replica>replica_1</replica>
    </macros>
</clickhouse>
```

<Note>
  위 매크로는 `chnode1`에 대한 설정이며, `chnode2`에서는 `replica`를 `replica_2`로 설정하십시오.
</Note>

<div id="disable-zero-copy-replication">
  #### zero-copy 복제 비활성화
</div>

ClickHouse 버전 22.7 이하에서는 S3 및 HDFS 디스크의 `allow_remote_fs_zero_copy_replication` 설정이 기본적으로 `true`입니다. 이 재해 복구 시나리오에서는 이 설정을 `false`로 지정해야 하며, 버전 22.8 이상에서는 기본값이 `false`입니다.

이 설정이 `false`여야 하는 이유는 두 가지입니다. 1) 이 기능은 아직 프로덕션 환경에서 사용할 준비가 되어 있지 않습니다. 2) 재해 복구 시나리오에서는 데이터와 메타데이터를 모두 여러 리전(Region)에 저장해야 합니다. `allow_remote_fs_zero_copy_replication`을 `false`로 설정하십시오.

```xml title="/etc/clickhouse-server/config.d/remote-servers.xml" theme={null}
<clickhouse>
   <merge_tree>
        <allow_remote_fs_zero_copy_replication>false</allow_remote_fs_zero_copy_replication>
   </merge_tree>
</clickhouse>
```

ClickHouse Keeper는 ClickHouse 노드 간 데이터 복제를 조정하는 역할을 합니다. ClickHouse가 ClickHouse Keeper 노드를 인식할 수 있도록 각 ClickHouse 노드에 설정 파일을 추가하십시오.

```xml title="/etc/clickhouse-server/config.d/use_keeper.xml" theme={null}
<clickhouse>
    <zookeeper>
        <node index="1">
            <host>keepernode1</host>
            <port>9181</port>
        </node>
        <node index="2">
            <host>keepernode2</host>
            <port>9181</port>
        </node>
        <node index="3">
            <host>keepernode3</host>
            <port>9181</port>
        </node>
    </zookeeper>
</clickhouse>
```

<div id="configure-networking">
  ### 네트워킹 구성
</div>

서버끼리 서로 통신하고 서버에 접속할 수 있도록 AWS에서 보안 설정을 구성할 때 [network ports](/docs/ko/concepts/features/security/network-ports) 목록을 참조하십시오.

세 서버 모두 네트워크 연결을 수신 대기해야 서버 간 통신과 S3와의 통신이 가능합니다. 기본적으로 ClickHouse는 루프백 주소에서만 수신 대기하므로 이를 변경해야 합니다. 이 설정은 `/etc/clickhouse-server/config.d/`에서 구성합니다. 다음은 ClickHouse와 ClickHouse Keeper가 모든 IPv4 인터페이스에서 수신 대기하도록 구성하는 예시입니다. 자세한 내용은 문서 또는 기본 설정 파일 `/etc/clickhouse/config.xml`을 참조하십시오.

```xml title="/etc/clickhouse-server/config.d/networking.xml" theme={null}
<clickhouse>
    <listen_host>0.0.0.0</listen_host>
</clickhouse>
```

<div id="start-the-servers">
  ### 서버 시작하기
</div>

<div id="run-clickhouse-keeper">
  #### ClickHouse Keeper 실행
</div>

각 Keeper 서버에서 사용 중인 운영 체제에 맞는 명령을 실행하십시오. 예시는 다음과 같습니다:

```bash theme={null}
sudo systemctl enable clickhouse-keeper
sudo systemctl start clickhouse-keeper
sudo systemctl status clickhouse-keeper
```

<div id="check-clickhouse-keeper-status">
  #### ClickHouse Keeper 상태 확인
</div>

`netcat`을 사용해 ClickHouse Keeper에 명령을 보냅니다. 예를 들어 `mntr`는 ClickHouse Keeper 클러스터의 상태를 반환합니다. 각 Keeper 노드에서 이 명령을 실행하면 한 노드는 리더이고, 나머지 두 노드는 팔로워임을 확인할 수 있습니다:

```bash theme={null}
echo mntr | nc localhost 9181
```

```response highlight={7-9,18-19} theme={null}
zk_version      v22.7.2.15-stable-f843089624e8dd3ff7927b8a125cf3a7a769c069
zk_avg_latency  0
zk_max_latency  11
zk_min_latency  0
zk_packets_received     1783
zk_packets_sent 1783
zk_num_alive_connections        2
zk_outstanding_requests 0
zk_server_state leader
zk_znode_count  135
zk_watch_count  8
zk_ephemerals_count     3
zk_approximate_data_size        42533
zk_key_arena_size       28672
zk_latest_snapshot_size 0
zk_open_file_descriptor_count   182
zk_max_file_descriptor_count    18446744073709551615
zk_followers    2
zk_synced_followers     2
```

<div id="run-clickhouse-server">
  #### ClickHouse 서버 실행
</div>

각 ClickHouse 서버에서 다음을 실행합니다.

```bash theme={null}
sudo service clickhouse-server start
```

<div id="verify-clickhouse-server">
  #### ClickHouse 서버 확인
</div>

[클러스터 구성](#define-a-cluster)을 추가할 때, 두 ClickHouse 노드에 걸쳐 복제되는 단일 세그먼트가 정의되었습니다. 이 확인 단계에서는 ClickHouse가 시작될 때 클러스터가 생성되었는지 확인하고, 해당 클러스터를 사용해 복제된 테이블을 생성합니다.

* 클러스터가 존재하는지 확인합니다:
  ```sql theme={null}
  show clusters
  ```
  ```response theme={null}
  ┌─cluster───────┐
  │ cluster_1S_2R │
  └───────────────┘

  1 row in set. Elapsed: 0.009 sec. `
  ```

* `ReplicatedMergeTree` 테이블 엔진을 사용하여 클러스터에 테이블을 생성합니다:
  ```sql theme={null}
  create table trips on cluster 'cluster_1S_2R' (
   `trip_id` UInt32,
   `pickup_date` Date,
   `pickup_datetime` DateTime,
   `dropoff_datetime` DateTime,
   `pickup_longitude` Float64,
   `pickup_latitude` Float64,
   `dropoff_longitude` Float64,
   `dropoff_latitude` Float64,
   `passenger_count` UInt8,
   `trip_distance` Float64,
   `tip_amount` Float32,
   `total_amount` Float32,
   `payment_type` Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4))
  ENGINE = ReplicatedMergeTree
  PARTITION BY toYYYYMM(pickup_date)
  ORDER BY pickup_datetime
  SETTINGS storage_policy='s3_main'
  ```
  ```response theme={null}
  ┌─host────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
  │ chnode1 │ 9000 │      0 │       │                   1 │                0 │
  │ chnode2 │ 9000 │      0 │       │                   0 │                0 │
  └─────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘
  ```

* 앞에서 정의한 매크로의 사용 방식을 이해합니다

  `shard` 및 `replica` 매크로는 [앞에서 정의](#define-a-cluster)되었으며, 아래 강조된 줄에서 각 ClickHouse 노드에 값이 어떻게 대입되는지 확인할 수 있습니다. 또한 `uuid` 값도 사용되는데, `uuid`는 시스템에서 생성되므로 매크로에는 정의되어 있지 않습니다.

  ```sql theme={null}
  SELECT create_table_query
  FROM system.tables
  WHERE name = 'trips'
  FORMAT Vertical
  ```

  ```response highlight={6} theme={null}
  Query id: 4d326b66-0402-4c14-9c2f-212bedd282c0

  Row 1:
  ──────
  create_table_query: CREATE TABLE default.trips (`trip_id` UInt32, `pickup_date` Date, `pickup_datetime` DateTime, `dropoff_datetime` DateTime, `pickup_longitude` Float64, `pickup_latitude` Float64, `dropoff_longitude` Float64, `dropoff_latitude` Float64, `passenger_count` UInt8, `trip_distance` Float64, `tip_amount` Float32, `total_amount` Float32, `payment_type` Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4))
  ENGINE = ReplicatedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')
  PARTITION BY toYYYYMM(pickup_date) ORDER BY pickup_datetime SETTINGS storage_policy = 's3_main'

  1 row in set. Elapsed: 0.012 sec.
  ```

<Note>
  위에 표시된 `'clickhouse/tables/{uuid}/{shard}` ZooKeeper 경로는 `default_replica_path`와 `default_replica_name`을 설정하여 사용자 지정할 수 있습니다. 관련 문서는 [여기](/docs/ko/reference/settings/server-settings/settings#default_replica_path)에서 확인할 수 있습니다.
</Note>

<div id="testing">
  ### 테스트
</div>

이 테스트는 데이터가 두 서버에 걸쳐 복제되는지, 그리고 로컬 디스크가 아니라 S3 버킷에 저장되는지를 확인합니다.

* New York City 택시 데이터셋에서 데이터를 추가합니다:
  ```sql theme={null}
  INSERT INTO trips
  SELECT trip_id,
         pickup_date,
         pickup_datetime,
         dropoff_datetime,
         pickup_longitude,
         pickup_latitude,
         dropoff_longitude,
         dropoff_latitude,
         passenger_count,
         trip_distance,
         tip_amount,
         total_amount,
         payment_type
     FROM s3('https://ch-nyc-taxi.s3.eu-west-3.amazonaws.com/tsv/trips_{0..9}.tsv.gz', 'TabSeparatedWithNames') LIMIT 1000000;
  ```
* 데이터가 S3에 저장되는지 확인합니다.

  이 쿼리는 디스크에 저장된 데이터 크기와, 어떤 디스크를 사용할지 결정하는 데 적용되는 정책을 보여줍니다.

  ```sql theme={null}
  SELECT
      engine,
      data_paths,
      metadata_path,
      storage_policy,
      formatReadableSize(total_bytes)
  FROM system.tables
  WHERE name = 'trips'
  FORMAT Vertical
  ```

  ```response theme={null}
  Query id: af7a3d1b-7730-49e0-9314-cc51c4cf053c

  Row 1:
  ──────
  engine:                          ReplicatedMergeTree
  data_paths:                      ['/var/lib/clickhouse/disks/s3_disk/store/551/551a859d-ec2d-4512-9554-3a4e60782853/']
  metadata_path:                   /var/lib/clickhouse/store/e18/e18d3538-4c43-43d9-b083-4d8e0f390cf7/trips.sql
  storage_policy:                  s3_main
  formatReadableSize(total_bytes): 36.42 MiB

  1 row in set. Elapsed: 0.009 sec.
  ```

  로컬 디스크의 데이터 크기를 확인합니다. 위 결과에서 수백만 개의 행이 저장된 디스크 크기는 36.42 MiB입니다. 이 데이터는 로컬 디스크가 아니라 S3에 있어야 합니다. 위 쿼리는 로컬 디스크에서 데이터와 메타데이터가 저장되는 위치도 보여줍니다. 로컬 데이터를 확인합니다:

  ```response theme={null}
  root@chnode1:~# du -sh /var/lib/clickhouse/disks/s3_disk/store/551
  536K  /var/lib/clickhouse/disks/s3_disk/store/551
  ```

  각 S3 버킷의 데이터를 확인합니다(합계는 표시되지 않지만, 삽입 후 두 버킷 모두에 약 36 MiB가 저장되어 있습니다):

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/s3/bucket1.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=e4a70e052e157f121c40714488d57687" size="lg" border alt="스토리지 사용량 메트릭을 보여주는 첫 번째 S3 버킷의 데이터 크기" width="1315" height="935" data-path="images/integrations/data-ingestion/s3/bucket1.webp" />

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/s3/bucket2.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=f3c8cda6880b271ab68e865ab580bd4c" size="lg" border alt="스토리지 사용량 메트릭을 보여주는 두 번째 S3 버킷의 데이터 크기" width="1315" height="935" data-path="images/integrations/data-ingestion/s3/bucket2.webp" />

<div id="s3express">
  ## S3Express
</div>

[S3Express](https://aws.amazon.com/s3/storage-classes/express-one-zone/)는 Amazon S3의 새로운 고성능 단일 가용 영역(Availability Zone) 스토리지 클래스입니다.

ClickHouse에서 S3Express를 테스트한 경험에 대해서는 이 [블로그](https://aws.amazon.com/blogs/storage/clickhouse-cloud-amazon-s3-express-one-zone-making-a-blazing-fast-analytical-database-even-faster/)를 참고하십시오.

<Note>
  S3Express는 단일 AZ 내에 데이터를 저장합니다. 즉, AZ에 장애가 발생하면 데이터를 사용할 수 없게 됩니다.
</Note>

<div id="s3-disk">
  ### S3 디스크
</div>

S3Express 버킷을 백엔드로 사용하는 저장소로 테이블을 생성하려면 다음 단계를 수행합니다.

1. `Directory` 유형의 버킷을 생성합니다
2. 필요한 모든 권한을 S3 사용자에게 부여하도록 적절한 버킷 정책을 적용합니다(예: 제한 없이 액세스를 허용하려면 `"Action": "s3express:*"`)
3. 스토리지 정책을 구성할 때 `region` 매개변수를 지정하십시오

스토리지 구성은 일반 S3와 동일하며, 예시는 다음과 같습니다:

```sql theme={null}
<storage_configuration>
    <disks>
        <s3_express>
            <type>s3</type>
            <endpoint>https://my-test-bucket--eun1-az1--x-s3.s3express-eun1-az1.eu-north-1.amazonaws.com/store/</endpoint>
            <region>eu-north-1</region>
            <access_key_id>...</access_key_id>
            <secret_access_key>...</secret_access_key>
        </s3_express>
    </disks>
    <policies>
        <s3_express>
            <volumes>
                <main>
                    <disk>s3_express</disk>
                </main>
            </volumes>
        </s3_express>
    </policies>
</storage_configuration>
```

그런 다음 새 스토리지에 테이블을 생성합니다:

```sql theme={null}
CREATE TABLE t
(
    a UInt64,
    s String
)
ENGINE = MergeTree
ORDER BY a
SETTINGS storage_policy = 's3_express';
```

<div id="s3-storage">
  ### S3 저장소
</div>

S3 저장소도 지원하지만 `Object URL` 경로에서만 사용할 수 있습니다. 예시:

```sql theme={null}
SELECT * FROM s3('https://test-bucket--eun1-az1--x-s3.s3express-eun1-az1.eu-north-1.amazonaws.com/file.csv', ...)
```

또한 config에서 버킷 리전도 지정해야 합니다:

```xml theme={null}
<s3>
    <perf-bucket-url>
        <endpoint>https://test-bucket--eun1-az1--x-s3.s3express-eun1-az1.eu-north-1.amazonaws.com</endpoint>
        <region>eu-north-1</region>
    </perf-bucket-url>
</s3>
```

<div id="backups">
  ### 백업
</div>

앞서 생성한 디스크에 백업을 저장할 수 있습니다:

```sql theme={null}
BACKUP TABLE t TO Disk('s3_express', 't.zip')
```

```response theme={null}
┌─id───────────────────────────────────┬─status─────────┐
│ c61f65ac-0d76-4390-8317-504a30ba7595 │ BACKUP_CREATED │
└──────────────────────────────────────┴────────────────┘
```

```sql theme={null}
RESTORE TABLE t AS t_restored FROM Disk('s3_express', 't.zip')
```

```response theme={null}
┌─id───────────────────────────────────┬─status───┐
│ 4870e829-8d76-4171-ae59-cffaf58dea04 │ RESTORED │
└──────────────────────────────────────┴──────────┘
```
