ClickHouse 데이터 삽입

ClickHouse 데이터 삽입 (Inserting ClickHouse data)

ClickHouse는 OLAP(Online Analytical Processing) 데이터베이스로, 초당 수백만 행을 삽입할 수 있는 고성능과 확장성에 최적화되어 있습니다. 즉각적인 일관성 대신 궁극적 일관성을 제공합니다. 삽입 성능을 유지하며 강한 일관성 보장을 지키려면 아래 규칙을 따르는 것이 좋아요.

출처: 문서

본문

ClickHouse에 삽입 vs OLTP 데이터베이스

OLAP(Online Analytical Processing) 데이터베이스로서 ClickHouse는 고성능과 확장성에 최적화되어 초당 수백만 행을 삽입할 수 있습니다. 이것은 고도로 병렬화된 아키텍처와 효율적인 컬럼 지향 압축의 조합으로 이루어지지만, 즉각적인 일관성에서는 타협이 있습니다. 더 구체적으로, ClickHouse는 append-only 연산에 최적화되어 있으며 궁극적 일관성(eventual consistency)만을 보장합니다.

대조적으로 Postgres 같은 OLTP 데이터베이스는 완전한 ACID 준수와 함께 트랜잭션 삽입에 특화되어 있어 강한 일관성과 신뢰성 보장을 보장합니다. PostgreSQL은 동시 트랜잭션을 처리하기 위해 MVCC(Multi-Version Concurrency Control)를 사용하는데, 이는 데이터의 여러 버전을 유지하는 것을 포함합니다. 이 트랜잭션들은 한 번에 적은 수의 행을 포함할 수 있으며, 신뢰성 보장으로 인한 상당한 오버헤드가 삽입 성능을 제한합니다.

강한 일관성 보장을 유지하면서 높은 삽입 성능을 달성하려면 ClickHouse에 데이터를 삽입할 때 아래 설명된 간단한 규칙을 따라야 합니다. 이 규칙들을 따르면 사용자가 ClickHouse를 처음 사용할 때 흔히 겪고, OLTP 데이터베이스에서 동작하는 삽입 전략을 복제하려 할 때 발생하는 문제들을 피하는 데 도움이 됩니다.

삽입 모범 사례

큰 배치 크기로 삽입

기본적으로 ClickHouse에 보내진 각 삽입은 즉시 삽입의 데이터와 함께 저장해야 하는 다른 메타데이터를 포함하는 스토리지 파트를 만듭니다. 따라서 각각 더 많은 데이터를 담는 더 적은 수의 삽입을 보내는 것이, 각각 더 적은 데이터를 담는 더 많은 수의 삽입을 보내는 것보다 필요한 쓰기 횟수를 줄입니다. 일반적으로 한 번에 최소 1000행, 이상적으로는 10,000~100,000행의 상당히 큰 배치로 데이터를 삽입할 것을 권장합니다. (자세한 내용은 여기).

큰 배치가 불가능하면 아래 설명된 비동기 삽입을 사용하세요.

멱등 재시도를 위한 일관된 배치 보장

기본적으로 ClickHouse에 대한 삽입은 동기적이며 멱등입니다(즉, 같은 삽입 연산을 여러 번 수행하는 것이 한 번 수행하는 것과 같은 효과). MergeTree 엔진 패밀리의 테이블에 대해 ClickHouse는 기본적으로 삽입을 자동으로 중복 제거합니다.

이것은 삽입이 다음 경우에도 견딜 수 있게 합니다:

  • 데이터를 받는 노드에 문제가 있으면 삽입 쿼리가 타임아웃(또는 더 구체적인 오류)되고 인정(acknowledgment)을 받지 못합니다.

  • 데이터가 노드에 쓰여졌지만 네트워크 중단으로 인해 승인이 쿼리 발신자에게 반환될 수 없으면, 발신자는 타임아웃이나 네트워크 오류를 받습니다.

클라이언트 관점에서 (i)와 (ii)는 구분하기 어려울 수 있습니다. 그러나 두 경우 모두 인정되지 않은 삽입을 즉시 재시도할 수 있습니다. 재시도된 삽입 쿼리가 같은 순서로 같은 데이터를 담고 있는 한, (인정되지 않은) 원래 삽입이 성공했다면 ClickHouse는 재시도된 삽입을 자동으로 무시합니다.

MergeTree 테이블 또는 분산 테이블에 삽입

MergeTree(또는 Replicated) 테이블에 직접 삽입하고, 데이터가 샤딩되어 있으면 노드 집합에 걸쳐 요청을 분산하며, internal_replication=true를 설정할 것을 권장합니다. 이렇게 하면 ClickHouse가 사용 가능한 복제본 샤드에 데이터를 복제하게 하고 데이터가 궁극적으로 일관되게 만듭니다.

이 클라이언트 측 로드 밸런싱이 불편하다면 분산 테이블을 통해 삽입할 수 있는데, 그러면 분산 테이블이 노드 간에 쓰기를 분산합니다. 다시 한번 internal_replication=true를 설정하는 것이 좋습니다. 그러나 이 접근 방식은 분산 테이블이 있는 노드에서 로컬로 쓰기가 이루어진 다음 샤드로 보내져야 하므로 성능이 약간 떨어짐을 주의해야 합니다.

작은 배치에는 비동기 삽입 사용

클라이언트 측 배칭이 실현 불가능한 시나리오가 있습니다. 예: 수백·수천 개의 단일 목적 에이전트가 로그·메트릭·트레이스 등을 보내는 관측 가능성 사용 사례. 이 시나리오에서는 문제와 이상 징후를 최대한 빨리 감지하는 것이 데이터의 실시간 전송의 핵심입니다. 게다가 관측 시스템의 이벤트 급증 위험이 있어, 클라이언트 측에서 관측 가능성 데이터를 버퍼링하려 할 때 큰 메모리 급증과 관련 문제를 일으킬 수 있습니다. 큰 배치를 삽입할 수 없으면 비동기 삽입을 사용해 배칭을 ClickHouse에 위임할 수 있습니다.

비동기 삽입에서는 데이터가 먼저 버퍼에 삽입된 다음 아래 다이어그램처럼 3단계로 나중에 데이터베이스 스토리지에 기록됩니다:

비동기 삽입이 활성화되면 ClickHouse는:

(1) 삽입 쿼리를 비동기로 받습니다. (2) 먼저 쿼리의 데이터를 인메모리 버퍼에 씁니다. (3) 다음 버퍼 플러시가 일어날 때만 데이터를 정렬해 파트로 데이터베이스 스토리지에 씁니다.

버퍼가 플러시되기 전에 같은 또는 다른 클라이언트의 다른 비동기 삽입 쿼리의 데이터가 버퍼에 모일 수 있습니다. 버퍼 플러시에서 만들어진 파트는 여러 비동기 삽입 쿼리의 데이터를 포함할 가능성이 있습니다. 일반적으로 이 메커니즘은 데이터 배칭을 클라이언트 측에서 서버 측(ClickHouse 인스턴스)으로 옮깁니다.

데이터는 데이터베이스 스토리지로 플러시되기 전에는 쿼리로 검색할 수 없고 플러시는 구성 가능함을 주의하세요. 비동기 삽입 구성에 대한 전체 세부 사항은 여기에서, 심층 분석은 여기에서 찾을 수 있습니다.

공식 ClickHouse 클라이언트 사용

ClickHouse는 가장 인기 있는 프로그래밍 언어로 클라이언트를 제공합니다. 이것들은 삽입이 올바르게 수행되도록 최적화되어 있으며 비동기 삽입을 Go 클라이언트처럼 직접, 또는 쿼리·사용자·연결 수준 설정에서 활성화될 때 간접적으로 네이티브 지원합니다.

사용 가능한 ClickHouse 클라이언트와 드라이버의 전체 목록은 Native Clients and Interfaces를 참고하세요.

네이티브 포맷 선호

ClickHouse는 삽입(및 쿼리) 시간에 많은 입력 포맷을 지원합니다. 이것은 OLTP 데이터베이스와의 중요한 차이이며, 특히 테이블 함수와 디스크의 파일에서 데이터를 로드하는 능력과 결합될 때 외부 소스에서 데이터를 로드하는 것을 훨씬 쉽게 만듭니다. 이 포맷들은 임시 데이터 로딩과 데이터 엔지니어링 작업에 이상적입니다.

최적의 삽입 성능을 얻으려는 애플리케이션은 Native 포맷으로 삽입해야 합니다. 이것은 대부분의 클라이언트(Go, Python 등)에서 지원되며, 이 포맷이 이미 컬럼 지향이므로 서버가 최소한의 작업만 하게 합니다. 그렇게 함으로써 데이터를 컬럼 지향 포맷으로 변환하는 책임이 클라이언트 측에 놓입니다. 이것은 삽입을 효율적으로 확장하는 데 중요합니다.

대안으로 행 포맷을 선호한다면(Java 클라이언트가 사용하는) RowBinary 포맷을 사용할 수 있습니다 — 이것은 Native 포맷보다 쓰기가 보통 더 쉽습니다. 이것은 압축, 네트워크 오버헤드, 서버 처리 측면에서 JSON 같은 다른 행 포맷보다 더 효율적입니다. JSONEachRow 포맷은 쓰기 처리량이 낮고 빠르게 통합하려는 경우 고려할 수 있습니다. 이 포맷은 ClickHouse에서 파싱에 CPU 오버헤드가 발생함을 알아야 합니다.

HTTP 인터페이스 사용

많은 전통적인 데이터베이스와 달리 ClickHouse는 HTTP 인터페이스를 지원합니다. 이것을 위의 어떤 포맷이든 데이터 삽입과 쿼리 모두에 사용할 수 있습니다. 이것은 로드 밸런서로 트래픽을 쉽게 전환할 수 있게 해 주므로 종종 ClickHouse 네이티브 프로토콜보다 선호됩니다. 네이티브 프로토콜과의 삽입 성능에는 약간의 차이가 있을 것으로 예상하는데, 네이티브 프로토콜은 오버헤드가 조금 더 적습니다. 기존 클라이언트는 이 프로토콜 중 하나를 사용합니다(어떤 경우엔 둘 다, 예: Go 클라이언트). 네이티브 프로토콜은 쿼리 진행 상황을 쉽게 추적할 수 있게 합니다.

자세한 내용은 HTTP Interface를 참고하세요.

기본 예제

익숙한 INSERT INTO TABLE 명령을 ClickHouse와 함께 사용할 수 있습니다. 시작 가이드 "Creating Tables in ClickHouse"에서 만든 테이블에 데이터를 삽입해 봅시다.

INSERT INTO helloworld.my_first_table (user_id, message, timestamp, metric) VALUES
    (101, 'Hello, ClickHouse!',                                 now(),       -1.0    ),
    (102, 'Insert a lot of rows per batch',                     yesterday(), 1.41421 ),
    (102, 'Sort your data based on your commonly-used queries', today(),     2.718   ),
    (101, 'Granules are the smallest chunks of data read',      now() + 5,   3.14159 )

동작했는지 확인하려면 다음 SELECT 쿼리를 실행합니다:

SELECT * FROM helloworld.my_first_table

다음을 반환합니다:

user_id message                                             timestamp           metric
101         Hello, ClickHouse!                                  2024-11-13 20:01:22     -1
101         Granules are the smallest chunks of data read           2024-11-13 20:01:27 3.14159
102         Insert a lot of rows per batch                          2024-11-12 00:00:00 1.41421
102         Sort your data based on your commonly-used queries  2024-11-13 00:00:00     2.718

Postgres에서 데이터 로드

Postgres에서 데이터를 로드하려면 다음을 사용할 수 있습니다:

  • ClickPipes: PostgreSQL 데이터베이스 복제를 위해 특별히 설계된 ETL 도구. 두 가지로 제공됩니다:
  • PostgreSQL 테이블 엔진을 사용해 이전 예제처럼 데이터를 직접 읽을 수 있습니다. 알려진 워터마크(예: 타임스탬프)에 기반한 배치 복제가 충분하거나 일회성 마이그레이션일 때 보통 적절합니다. 이 접근 방식은 수천만 행까지 확장할 수 있습니다. 더 큰 데이터셋을 마이그레이션하려는 사용자는 각각 데이터의 일부를 다루는 여러 요청을 고려해야 합니다. 각 청크의 파티션이 최종 테이블로 이동되기 전에 스테이징 테이블을 각 청크에 사용할 수 있습니다. 이것은 실패한 요청을 재시도할 수 있게 합니다. 이 대량 로딩 전략에 대한 자세한 내용은 여기를 참고하세요.
  • 데이터를 PostgreSQL에서 CSV 형식으로 내보낼 수 있습니다. 그런 다음 테이블 함수를 사용해 로컬 파일이나 객체 스토리지에서 ClickHouse로 삽입할 수 있습니다.

대량 데이터셋 삽입 도움이 필요하세요? 대량 데이터셋을 삽입하는 데 도움이 필요하거나 ClickHouse Cloud로 데이터를 가져올 때 오류가 발생하면 [email protected]으로 연락 주시면 도와드리겠습니다.

명령줄에서 데이터 삽입

사전 요구사항

  • ClickHouse를 설치했습니다.
  • clickhouse-server가 실행 중입니다.
  • wget, zcat, curl이 있는 터미널에 접근할 수 있습니다.

이 예제에서 배치 모드의 clickhouse-client로 명령줄에서 CSV 파일을 ClickHouse에 삽입하는 방법을 보게 됩니다. 배치 모드에서 clickhouse-client로 명령줄 삽입에 대한 더 많은 정보와 예제는 "Batch mode"를 참고하세요.

예제로 Hacker News 데이터셋을 사용하겠습니다. 2800만 행의 Hacker News 데이터를 포함합니다.

  1. CSV 다운로드. 공개 S3 버킷에서 데이터셋의 CSV 버전을 다운로드하려면 다음 명령을 실행하세요:
wget https://datasets-documentation.s3.eu-west-3.amazonaws.com/hackernews/hacknernews.csv.gz

4.6GB, 2800만 행으로 이 압축 파일은 다운로드에 5-10분 걸려야 합니다.

  1. 테이블 생성. clickhouse-server가 실행 중인 상태에서 clickhouse-client를 배치 모드로 사용해 명령줄에서 직접 다음 스키마의 빈 테이블을 만들 수 있습니다:
clickhouse-client <<'_EOF'
CREATE TABLE hackernews(
    `id` UInt32,
    `deleted` UInt8,
    `type` Enum('story' = 1, 'comment' = 2, 'poll' = 3, 'pollopt' = 4, 'job' = 5),
    `by` LowCardinality(String),
    `time` DateTime,
    `text` String,
    `dead` UInt8,
    `parent` UInt32,
    `poll` UInt32,
    `kids` Array(UInt32),
    `url` String,
    `score` Int32,
    `title` String,
    `parts` Array(UInt32),
    `descendants` Int32
)
ENGINE = MergeTree
ORDER BY id
_EOF

오류가 없으면 테이블이 성공적으로 생성된 것입니다. 위 명령에서 heredoc 구분자(_EOF) 주위에 작은따옴표를 사용해 보간을 방지합니다. 작은따옴표가 없으면 컬럼 이름 주위의 백틱을 이스케이프해야 합니다.

  1. 명령줄에서 데이터 삽입. 다음으로 이전에 다운로드한 파일의 데이터를 테이블에 삽입하려면 아래 명령을 실행하세요:
zcat < hacknernews.csv.gz | ./clickhouse client --query "INSERT INTO hackernews FORMAT CSV"

데이터가 압축되어 있으므로 먼저 gzip, zcat 같은 도구로 파일을 압축 해제한 다음, 적절한 INSERT 문과 FORMAT으로 압축 해제된 데이터를 clickhouse-client로 파이프해야 합니다. clickhouse-client로 대화형 모드에서 데이터를 삽입할 때는 COMPRESSION 절을 사용해 삽입 시 ClickHouse가 압축 해제를 처리하게 할 수 있습니다. ClickHouse는 파일 확장자에서 압축 타입을 자동으로 감지할 수 있지만, 명시적으로 지정할 수도 있습니다. 삽입 쿼리는 다음과 같습니다:

clickhouse-client --query "INSERT INTO hackernews FROM INFILE 'hacknernews.csv.gz' COMPRESSION 'gzip' FORMAT CSV;"

데이터 삽입이 끝나면 다음 명령을 실행해 hackernews 테이블의 행 수를 볼 수 있습니다:

clickhouse-client --query "SELECT formatReadableQuantity(count(*)) FROM hackernews"
28.74 million
  1. curl로 명령줄 삽입. 이전 단계에서 wget으로 CSV 파일을 먼저 로컬 머신에 다운로드했습니다. 단일 명령으로 원격 URL에서 직접 데이터를 삽입하는 것도 가능합니다. 로컬 머신에 다운로드하는 중간 단계 없이 다시 삽입할 수 있도록 hackernews 테이블의 데이터를 비우려면 다음 명령을 실행하세요:
clickhouse-client --query "TRUNCATE hackernews"

이제 실행하세요:

curl https://datasets-documentation.s3.eu-west-3.amazonaws.com/hackernews/hacknernews.csv.gz | zcat | clickhouse-client --query "INSERT INTO hackernews FORMAT CSV"

이제 이전과 같은 명령을 실행해 데이터가 다시 삽입되었는지 확인할 수 있습니다:

clickhouse-client --query "SELECT formatReadableQuantity(count(*)) FROM hackernews"
28.74 million

더 알아보기 (Learn more)