데이터 복제

데이터 복제 (1 샤드 2 레플리카)

이 예제에서는 데이터를 복제하는 간단한 ClickHouse 클러스터를 설정하는 방법을 배워볼게요. 다섯 대의 서버가 구성돼요. 두 대는 데이터의 복사본을 호스팅하는 데, 나머지 세 대는 데이터 복제를 조정하는 데 사용돼요.

출처: 문서

본문

설정할 클러스터의 아키텍처는 아래와 같아요.

ClickHouse Server와 ClickHouse Keeper를 같은 서버에서 함께 실행하는 것도 가능하지만, 프로덕션 환경에서는 ClickHouse Keeper에 전용 호스트를 사용할 것을 강력히 권장해요. 이는 이 예제에서 보여줄 방식이에요. Keeper 서버는 더 작을 수 있고, ClickHouse Server가 커질 때까지 각 Keeper 서버에는 일반적으로 4GB RAM이면 충분해요.

전제 조건 (Prerequisites)

  • 이전에 로컬 ClickHouse 서버를 설정한 적이 있어야 해요
  • 구성 파일 같은 ClickHouse의 기본 구성 개념에 익숙해야 해요
  • 머신에 docker가 설치되어 있어야 해요

1. 디렉터리 구조와 테스트 환경 설정

예제 파일 다음 단계는 클러스터를 처음부터 설정하는 과정을 안내해요. 이 단계를 건너뛰고 바로 클러스터 실행으로 뛰어들고 싶다면 examples 저장소의 'docker-compose-recipes' 디렉터리에서 예제 파일을 얻을 수 있어요.

이 튜토리얼에서는 Docker compose를 사용해 ClickHouse 클러스터를 설정해요. 이 설정은 별도의 로컬 머신, 가상 머신 또는 클라우드 인스턴스에서도 동작하도록 수정할 수 있어요.

이 예제의 디렉터리 구조를 설정하려면 다음 명령을 실행해요:

mkdir cluster_1S_2R
cd cluster_1S_2R

# Create clickhouse-keeper directories
for i in {01..03}; do
  mkdir -p fs/volumes/clickhouse-keeper-${i}/etc/clickhouse-keeper
done

# Create clickhouse-server directories
for i in {01..02}; do
  mkdir -p fs/volumes/clickhouse-${i}/etc/clickhouse-server
done

cluster_1S_2R 디렉터리에 다음 docker-compose.yml 파일을 추가해요:

docker-compose.yml

version: '3.8'
services:
  clickhouse-01:
    image: "clickhouse/clickhouse-server:latest"
    user: "101:101"
    container_name: clickhouse-01
    hostname: clickhouse-01
    volumes:
      - ${PWD}/fs/volumes/clickhouse-01/etc/clickhouse-server/config.d/config.xml:/etc/clickhouse-server/config.d/config.xml
      - ${PWD}/fs/volumes/clickhouse-01/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
    ports:
      - "127.0.0.1:8123:8123"
      - "127.0.0.1:9000:9000"
    depends_on:
      - clickhouse-keeper-01
      - clickhouse-keeper-02
      - clickhouse-keeper-03
  clickhouse-02:
    image: "clickhouse/clickhouse-server:latest"
    user: "101:101"
    container_name: clickhouse-02
    hostname: clickhouse-02
    volumes:
      - ${PWD}/fs/volumes/clickhouse-02/etc/clickhouse-server/config.d/config.xml:/etc/clickhouse-server/config.d/config.xml
      - ${PWD}/fs/volumes/clickhouse-02/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
    ports:
      - "127.0.0.1:8124:8123"
      - "127.0.0.1:9001:9000"
    depends_on:
      - clickhouse-keeper-01
      - clickhouse-keeper-02
      - clickhouse-keeper-03
  clickhouse-keeper-01:
    image: "clickhouse/clickhouse-keeper:latest-alpine"
    user: "101:101"
    container_name: clickhouse-keeper-01
    hostname: clickhouse-keeper-01
    volumes:
     - ${PWD}/fs/volumes/clickhouse-keeper-01/etc/clickhouse-keeper/keeper_config.xml:/etc/clickhouse-keeper/keeper_config.xml
    ports:
        - "127.0.0.1:9181:9181"
  clickhouse-keeper-02:
    image: "clickhouse/clickhouse-keeper:latest-alpine"
    user: "101:101"
    container_name: clickhouse-keeper-02
    hostname: clickhouse-keeper-02
    volumes:
     - ${PWD}/fs/volumes/clickhouse-keeper-02/etc/clickhouse-keeper/keeper_config.xml:/etc/clickhouse-keeper/keeper_config.xml
    ports:
        - "127.0.0.1:9182:9181"
  clickhouse-keeper-03:
    image: "clickhouse/clickhouse-keeper:latest-alpine"
    user: "101:101"
    container_name: clickhouse-keeper-03
    hostname: clickhouse-keeper-03
    volumes:
     - ${PWD}/fs/volumes/clickhouse-keeper-03/etc/clickhouse-keeper/keeper_config.xml:/etc/clickhouse-keeper/keeper_config.xml
    ports:
        - "127.0.0.1:9183:9181"

다음 하위 디렉터리와 파일을 만들어요:

for i in {01..02}; do
  mkdir -p fs/volumes/clickhouse-${i}/etc/clickhouse-server/config.d
  mkdir -p fs/volumes/clickhouse-${i}/etc/clickhouse-server/users.d
  touch fs/volumes/clickhouse-${i}/etc/clickhouse-server/config.d/config.xml
  touch fs/volumes/clickhouse-${i}/etc/clickhouse-server/users.d/users.xml
done
  • config.d 디렉터리는 ClickHouse 서버 구성 파일 config.xml을 포함하며, 여기서 각 ClickHouse 노드의 사용자 지정 구성을 정의해요. 이 구성은 모든 ClickHouse 설치에 포함된 기본 config.xml ClickHouse 구성 파일과 결합돼요.
  • users.d 디렉터리는 사용자 구성 파일 users.xml을 포함하며, 여기서 사용자에 대한 사용자 지정 구성을 정의해요. 이 구성은 모든 ClickHouse 설치에 포함된 기본 ClickHouse users.xml 구성 파일과 결합돼요.

사용자 지정 구성 디렉터리 자신의 구성을 작성할 때는 /etc/clickhouse-server/config.xmletc/clickhouse-server/users.xml의 기본 구성을 직접 수정하기보다 config.dusers.d 디렉터리를 활용하는 것이 모범 사례예요.

<clickhouse replace="true">

이 줄은 config.dusers.d 디렉터리에 정의된 구성 섹션이 기본 config.xmlusers.xml 파일에 정의된 기본 구성 섹션을 덮어쓰도록 보장해요.

2. ClickHouse 노드 구성

서버 설정 (Server setup)

이제 fs/volumes/clickhouse-{}/etc/clickhouse-server/config.d에 있는 각 빈 구성 파일 config.xml을 수정해요. 아래에서 강조된 줄은 각 노드에 맞게 변경해야 해요:

<clickhouse replace="true">
    <logger>
        <level>debug</level>
        <log>/var/log/clickhouse-server/clickhouse-server.log</log>
        <errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
        <size>1000M</size>
        <count>3</count>
    </logger>
    <display_name>cluster_1S_2R node 1</display_name>
    <listen_host>0.0.0.0</listen_host>
    <http_port>8123</http_port>
    <tcp_port>9000</tcp_port>
    <user_directories>
        <users_xml>
            <path>users.xml</path>
        </users_xml>
        <local_directory>
            <path>/var/lib/clickhouse/access/</path>
        </local_directory>
    </user_directories>
    <distributed_ddl>
        <path>/clickhouse/task_queue/ddl</path>
    </distributed_ddl>
    <remote_servers>
        <cluster_1S_2R>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>clickhouse-01</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>clickhouse-02</host>
                    <port>9000</port>
                </replica>
            </shard>
        </cluster_1S_2R>
    </remote_servers>
    <zookeeper>
        <node>
            <host>clickhouse-keeper-01</host>
            <port>9181</port>
        </node>
        <node>
            <host>clickhouse-keeper-02</host>
            <port>9181</port>
        </node>
        <node>
            <host>clickhouse-keeper-03</host>
            <port>9181</port>
        </node>
    </zookeeper>
    <macros>
        <shard>01</shard>
        <replica>01</replica>
        <cluster>cluster_1S_2R</cluster>
    </macros>
</clickhouse>
디렉터리 파일
fs/volumes/clickhouse-01/etc/clickhouse-server/config.d config.xml
fs/volumes/clickhouse-02/etc/clickhouse-server/config.d config.xml

위 구성 파일의 각 섹션은 아래에서 자세히 설명돼요.

네트워킹과 로깅

listen host 설정을 활성화해 네트워크 인터페이스에 대한 외부 통신을 켜요. 이는 ClickHouse 서버 호스트가 다른 호스트에서 도달 가능하도록 보장해요:

<listen_host>0.0.0.0</listen_host>

HTTP API 포트는 8123으로 설정돼요:

<http_port>8123</http_port>

clickhouse-client와 다른 네이티브 ClickHouse 도구 사이, 그리고 clickhouse-server와 다른 clickhouse-server 사이에서 ClickHouse의 네이티브 프로토콜로 상호작용하는 TCP 포트는 9000으로 설정돼요:

<tcp_port>9000</tcp_port>

로깅은 <logger> 블록에서 정의돼요. 이 예제 구성은 1000M에서 세 번 롤오버되는 디버그 로그를 제공해요:

<logger>
    <level>debug</level>
    <log>/var/log/clickhouse-server/clickhouse-server.log</log>
    <errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
    <size>1000M</size>
    <count>3</count>
</logger>

로깅 구성에 대한 자세한 내용은 기본 ClickHouse 구성 파일에 포함된 주석을 참고해요.

클러스터 구성

클러스터 구성은 <remote_servers> 블록에서 설정돼요. 여기서 클러스터 이름 cluster_1S_2R이 정의돼요. <cluster_1S_2R></cluster_1S_2R> 블록은 <shard></shard><replica></replica> 설정을 사용해 클러스터의 레이아웃을 정의하며, ON CLUSTER 절을 사용해 클러스터 전역에서 실행되는 쿼리(분산 DDL 쿼리)의 템플릿 역할을 해요. 기본적으로 분산 DDL 쿼리는 허용되지만 allow_distributed_ddl_queries 설정으로 끌 수도 있어요. internal_replication은 레플리카 중 하나에만 데이터가 쓰이도록 true로 설정돼요.

<remote_servers>
    <!-- cluster name (should not contain dots) -->
    <cluster_1S_2R>
        <!-- <allow_distributed_ddl_queries>false</allow_distributed_ddl_queries> -->
        <shard>
            <!-- Optional. Whether to write data to just one of the replicas. Default: false (write data to all replicas). -->
            <internal_replication>true</internal_replication>
            <replica>
                <host>clickhouse-01</host>
                <port>9000</port>
            </replica>
            <replica>
                <host>clickhouse-02</host>
                <port>9000</port>
            </replica>
        </shard>
    </cluster_1S_2R>
</remote_servers>

각 서버에 대해 다음 매개변수가 지정돼요:

매개변수 설명 기본 값
host 원격 서버의 주소. 도메인 또는 IPv4·IPv6 주소를 사용할 수 있어요. 도메인을 지정하면 서버가 시작할 때 DNS 요청을 하고 그 결과를 서버가 실행되는 동안 저장해요. DNS 요청이 실패하면 서버가 시작하지 않아요. DNS 레코드를 변경하면 서버를 재시작해야 해요. -
port 메신저 활동을 위한 TCP 포트(구성의 tcp_port, 보통 9000으로 설정). http_port와 혼동하지 마세요. -
Keeper 구성

<ZooKeeper> 섹션은 ClickHouse Keeper(또는 ZooKeeper)가 어디서 실행되는지 ClickHouse에 알려줘요. ClickHouse Keeper 클러스터를 사용하므로 클러스터의 각 <node><host><port> 태그를 사용해 각각 호스트 이름과 포트 번호와 함께 지정해야 해요. ClickHouse Keeper 설정은 튜토리얼의 다음 단계에서 설명돼요.

<zookeeper>
    <node>
        <host>clickhouse-keeper-01</host>
        <port>9181</port>
    </node>
    <node>
        <host>clickhouse-keeper-02</host>
        <port>9181</port>
    </node>
    <node>
        <host>clickhouse-keeper-03</host>
        <port>9181</port>
    </node>
</zookeeper>

ClickHouse Keeper를 ClickHouse Server와 같은 서버에서 실행하는 것도 가능하지만, 프로덕션 환경에서는 ClickHouse Keeper가 전용 호스트에서 실행될 것을 강력히 권장해요.

Macros 구성

추가로 <macros> 섹션은 복제 테이블의 매개변수 대체를 정의하는 데 사용돼요. 이들은 system.macros에 나열되며 쿼리에서 {shard}{replica} 같은 대체를 사용할 수 있게 해줘요.

<macros>
    <shard>01</shard>
    <replica>01</replica>
    <cluster>cluster_1S_2R</cluster>
</macros>

이것들은 클러스터의 레이아웃에 따라 고유하게 정의돼요.

사용자 구성 (User configuration)

이제 fs/volumes/clickhouse-{}/etc/clickhouse-server/users.d에 있는 각 빈 구성 파일 users.xml을 다음 내용으로 수정해요:

/users.d/users.xml

<?xml version="1.0"?>
<clickhouse replace="true">
    <profiles>
        <default>
            <max_memory_usage>10000000000</max_memory_usage>
            <use_uncompressed_cache>0</use_uncompressed_cache>
            <load_balancing>in_order</load_balancing>
            <log_queries>1</log_queries>
        </default>
    </profiles>
    <users>
        <default>
            <profile>default</profile>
            <networks>
                <ip>::/0</ip>
            </networks>
            <quota>default</quota>
            <access_management>1</access_management>
            <named_collection_control>1</named_collection_control>
            <show_named_collections_secrets>1</show_named_collections_secrets>
        </default>
    </users>
    <quotas>
        <default>
            <interval>
                <duration>3600</duration>
                <queries>0</queries>
                <errors>0</errors>
                <result_rows>0</result_rows>
                <read_rows>0</read_rows>
                <execution_time>0</execution_time>
            </interval>
        </default>
    </quotas>
</clickhouse>
디렉터리 파일
fs/volumes/clickhouse-01/etc/clickhouse-server/users.d users.xml
fs/volumes/clickhouse-02/etc/clickhouse-server/users.d users.xml

이 예제에서는 단순함을 위해 default 사용자가 비밀번호 없이 구성돼요. 실제로는 권장되지 않아요.

이 예제에서 각 users.xml 파일은 클러스터의 모든 노드에서 동일해요.

3. ClickHouse Keeper 구성

Keeper 설정 (Keeper setup)

복제가 동작하려면 ClickHouse Keeper 클러스터를 설정하고 구성해야 해요. ClickHouse Keeper는 데이터 복제를 위한 조정 시스템을 제공하며, 사용할 수도 있는 Zookeeper를 대체하는 역할을 해요. 하지만 ClickHouse Keeper는 ZooKeeper보다 더 나은 보장과 신뢰성을 제공하고 더 적은 리소스를 사용하므로 권장돼요. 고가용성과 쿼럼 유지를 위해 최소 세 개의 ClickHouse Keeper 노드를 실행할 것을 권장해요.

ClickHouse Keeper는 ClickHouse와 함께 클러스터의 어떤 노드에서도 실행할 수 있지만, 데이터베이스 클러스터와 독립적으로 ClickHouse Keeper 클러스터를 확장·관리할 수 있게 해주는 전용 노드에서 실행할 것을 권장해요.

example 폴더의 루트에서 다음 명령으로 각 ClickHouse Keeper 노드의 keeper_config.xml 파일을 만들어요:

for i in {01..03}; do
  touch fs/volumes/clickhouse-keeper-${i}/etc/clickhouse-keeper/keeper_config.xml
done

각 노드 디렉터리 fs/volumes/clickhouse-keeper-{}/etc/clickhouse-keeper에서 만든 빈 구성 파일을 수정해요. 아래 강조된 줄은 각 노드에 맞게 변경해야 해요:

/clickhouse-keeper/keeper_config.xml

<clickhouse replace="true">
    <logger>
        <level>information</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>1</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>information</raft_logs_level>
        </coordination_settings>
        <raft_configuration>
            <server>
                <id>1</id>
                <hostname>clickhouse-keeper-01</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>2</id>
                <hostname>clickhouse-keeper-02</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>3</id>
                <hostname>clickhouse-keeper-03</hostname>
                <port>9234</port>
            </server>
        </raft_configuration>
    </keeper_server>
</clickhouse>
디렉터리 파일
fs/volumes/clickhouse-keeper-01/etc/clickhouse-keeper keeper_config.xml
fs/volumes/clickhouse-keeper-02/etc/clickhouse-keeper keeper_config.xml
fs/volumes/clickhouse-keeper-03/etc/clickhouse-keeper keeper_config.xml

각 구성 파일에는 다음 고유 구성(아래 표시)이 포함돼요. 사용되는 server_id는 해당 ClickHouse Keeper 노드에 대해 클러스터에서 고유해야 하며 <raft_configuration> 섹션에 정의된 서버 <id>와 일치해야 해요. tcp_port는 ClickHouse Keeper의 클라이언트 가 사용하는 포트예요.

<tcp_port>9181</tcp_port>
<server_id>{id}</server_id>

다음 섹션은 raft 합의 알고리즘의 쿼럼에 참여하는 서버를 구성하는 데 사용돼요:

<raft_configuration>
    <server>
        <id>1</id>
        <hostname>clickhouse-keeper-01</hostname>
        <!-- TCP port used for communication between ClickHouse Keeper nodes -->
        <port>9234</port>
    </server>
    <server>
        <id>2</id>
        <hostname>clickhouse-keeper-02</hostname>
        <port>9234</port>
    </server>
    <server>
        <id>3</id>
        <hostname>clickhouse-keeper-03</hostname>
        <port>9234</port>
    </server>
</raft_configuration>

ClickHouse Cloud는 관리를 단순화합니다 ClickHouse Cloud는 샤드와 레플리카 관리와 관련된 운영 부담을 제거해요. 플랫폼이 고가용성, 복제, 확장 결정을 자동으로 처리해요. 컴퓨팅과 저장은 분리되어 수동 구성이나 지속적 유지보수 없이 수요에 따라 확장돼요. 더 알아보기

4. 설정 테스트

머신에서 docker가 실행 중인지 확인해요. cluster_1S_2R 디렉터리의 루트에서 docker-compose up 명령으로 클러스터를 시작해요:

docker-compose up -d

docker가 ClickHouse와 Keeper 이미지를 가져오기 시작한 다음 컨테이너를 시작하는 것을 볼 수 있어요:

[+] Running 6/6
 ✔ Network cluster_1s_2r_default   Created
 ✔ Container clickhouse-keeper-03  Started
 ✔ Container clickhouse-keeper-02  Started
 ✔ Container clickhouse-keeper-01  Started
 ✔ Container clickhouse-01         Started
 ✔ Container clickhouse-02         Started

클러스터가 실행 중인지 확인하려면 clickhouse-01 또는 clickhouse-02에 연결해 다음 쿼리를 실행해요. 첫 번째 노드에 연결하는 명령은 다음과 같아요:

# Connect to any node
docker exec -it clickhouse-01 clickhouse-client

성공하면 ClickHouse 클라이언트 프롬프트가 보여요:

cluster_1S_2R node 1 :)

어떤 호스트에 어떤 클러스터 토폴로지가 정의되어 있는지 확인하려면 다음 쿼리를 실행해요:

Query

SELECT 
    cluster,
    shard_num,
    replica_num,
    host_name,
    port
FROM system.clusters;

Response

   ┌─cluster───────┬─shard_num─┬─replica_num─┬─host_name─────┬─port─┐
1. │ cluster_1S_2R │         1 │           1 │ clickhouse-01 │ 9000 │
2. │ cluster_1S_2R │         1 │           2 │ clickhouse-02 │ 9000 │
3. │ default       │         1 │           1 │ localhost     │ 9000 │
   └───────────────┴───────────┴─────────────┴───────────────┴──────┘

ClickHouse Keeper 클러스터의 상태를 확인하려면 다음 쿼리를 실행해요:

Query

SELECT *
FROM system.zookeeper
WHERE path IN ('/', '/clickhouse')

Response

   ┌─name───────┬─value─┬─path────────┐
1. │ sessions   │       │ /clickhouse │
2. │ task_queue │       │ /clickhouse │
3. │ keeper     │       │ /           │
4. │ clickhouse │       │ /           │
   └────────────┴───────┴─────────────┘

mntr 명령도 ClickHouse Keeper가 실행 중인지 확인하고 세 Keeper 노드의 관계에 대한 상태 정보를 얻는 데 흔히 사용돼요. 이 예제에서 사용된 구성에는 함께 동작하는 세 개의 노드가 있어요. 노드들이 리더를 선출하고 나머지 노드들은 팔로워가 돼요. mntr 명령은 성능과 특정 노드가 팔로워인지 리더인지에 대한 정보를 줘요.

Keeper에 mntr 명령을 보내려면 netcat을 설치해야 할 수 있어요. 다운로드 정보는 nmap.org 페이지를 참고하세요.

clickhouse-keeper-01, clickhouse-keeper-02, clickhouse-keeper-03의 셸에서 아래 명령을 실행해 각 Keeper 노드의 상태를 확인해요. clickhouse-keeper-01의 명령은 아래와 같아요:

docker exec -it clickhouse-keeper-01  /bin/sh -c 'echo mntr | nc 127.0.0.1 9181'

아래 응답은 팔로워 노드의 예시 응답이에요:

Response

zk_version      v23.3.1.2823-testing-46e85357ce2da2a99f56ee83a079e892d7ec3726
zk_avg_latency  0
zk_max_latency  0
zk_min_latency  0
zk_packets_received     0
zk_packets_sent 0
zk_num_alive_connections        0
zk_outstanding_requests 0
zk_server_state follower
zk_znode_count  6
zk_watch_count  0
zk_ephemerals_count     0
zk_approximate_data_size        1271
zk_key_arena_size       4096
zk_latest_snapshot_size 0
zk_open_file_descriptor_count   46
zk_max_file_descriptor_count    18446744073709551615

아래 응답은 리더 노드의 예시 응답이에요:

Response

zk_version      v23.3.1.2823-testing-46e85357ce2da2a99f56ee83a079e892d7ec3726
zk_avg_latency  0
zk_max_latency  0
zk_min_latency  0
zk_packets_received     0
zk_packets_sent 0
zk_num_alive_connections        0
zk_outstanding_requests 0
zk_server_state leader
zk_znode_count  6
zk_watch_count  0
zk_ephemerals_count     0
zk_approximate_data_size        1271
zk_key_arena_size       4096
zk_latest_snapshot_size 0
zk_open_file_descriptor_count   48
zk_max_file_descriptor_count    18446744073709551615
zk_followers    2
zk_synced_followers     2

이것으로 단일 샤드와 두 레플리카를 가진 ClickHouse 클러스터를 성공적으로 설정했어요. 다음 단계에서는 클러스터에 테이블을 만들 거예요.

5. 데이터베이스 만들기

클러스터가 올바르게 설정되고 실행 중임을 확인했으니, UK property prices 예제 데이터셋 튜토리얼에서 사용한 것과 같은 테이블을 다시 만들 거예요. 이는 1995년 이후 잉글랜드와 웨일스의 부동산에 지불된 가격 약 3000만 행으로 구성돼요.

각 호스트의 클라이언트에 별도의 터미널 탭 또는 창에서 각각 다음 명령을 실행해 연결해요:

docker exec -it clickhouse-01 clickhouse-client
docker exec -it clickhouse-02 clickhouse-client

각 호스트의 clickhouse-client에서 아래 쿼리를 실행해 기본 데이터베이스 외에는 아직 만들어진 데이터베이스가 없는지 확인할 수 있어요:

Query

SHOW DATABASES;

Response

   ┌─name───────────────┐
1. │ INFORMATION_SCHEMA │
2. │ default            │
3. │ information_schema │
4. │ system             │
   └────────────────────┘

clickhouse-01 클라이언트에서 ON CLUSTER 절을 사용한 다음 분산 DDL 쿼리를 실행해 uk라는 새 데이터베이스를 만들어요:

CREATE DATABASE IF NOT EXISTS uk 
ON CLUSTER cluster_1S_2R;

쿼리를 clickhouse-01에서만 실행했는데도 데이터베이스가 클러스터 전체에 생성되었는지 확인하려면 각 호스트의 클라이언트에서 이전과 같은 쿼리를 다시 실행할 수 있어요:

SHOW DATABASES;
   ┌─name───────────────┐
1. │ INFORMATION_SCHEMA │
2. │ default            │
3. │ information_schema │
4. │ system             │
5. │ uk                 │
   └────────────────────┘

6. 클러스터에 테이블 만들기

데이터베이스가 만들어졌으니 클러스터에 테이블을 만들어요. 어느 호스트 클라이언트에서든 다음 쿼리를 실행해요:

CREATE TABLE IF NOT EXISTS uk.uk_price_paid_local
ON CLUSTER cluster_1S_2R
(
    price UInt32,
    date Date,
    postcode1 LowCardinality(String),
    postcode2 LowCardinality(String),
    type Enum8('terraced' = 1, 'semi-detached' = 2, 'detached' = 3, 'flat' = 4, 'other' = 0),
    is_new UInt8,
    duration Enum8('freehold' = 1, 'leasehold' = 2, 'unknown' = 0),
    addr1 String,
    addr2 String,
    street LowCardinality(String),
    locality LowCardinality(String),
    town LowCardinality(String),
    district LowCardinality(String),
    county LowCardinality(String)
)
ENGINE = ReplicatedMergeTree
ORDER BY (postcode1, postcode2, addr1, addr2);

이것은 ON CLUSTER 절과 ReplicatedMergeTree 엔진 사용을 제외하면 UK property prices 예제 데이터셋 튜토리얼의 원래 CREATE 문에서 사용한 쿼리와 동일해요. ON CLUSTER 절은 CREATE, DROP, ALTER, RENAME 같은 DDL(Data Definition Language) 쿼리의 분산 실행을 위해 설계되어, 이러한 스키마 변경이 클러스터의 모든 노드에 적용되도록 보장해요. ReplicatedMergeTree 엔진은 일반 MergeTree 테이블 엔진처럼 동작하지만 데이터도 복제해요.

clickhouse-01 또는 clickhouse-02 클라이언트에서 아래 쿼리를 실행해 테이블이 클러스터 전체에 생성되었는지 확인할 수 있어요:

Query

SHOW TABLES IN uk;

Response

   ┌─name────────────────┐
1. │ uk_price_paid.      │
   └─────────────────────┘

7. 데이터 삽입

데이터셋이 크고 완전히 수집하는 데 몇 분이 걸리므로 처음에는 작은 부분집합만 삽입할 거예요. clickhouse-01에서 아래 쿼리를 사용해 데이터의 더 작은 부분집합을 삽입해요:

INSERT INTO uk.uk_price_paid_local
SELECT
    toUInt32(price_string) AS price,
    parseDateTimeBestEffortUS(time) AS date,
    splitByChar(' ', postcode)[1] AS postcode1,
    splitByChar(' ', postcode)[2] AS postcode2,
    transform(a, ['T', 'S', 'D', 'F', 'O'], ['terraced', 'semi-detached', 'detached', 'flat', 'other']) AS type,
    b = 'Y' AS is_new,
    transform(c, ['F', 'L', 'U'], ['freehold', 'leasehold', 'unknown']) AS duration,
    addr1,
    addr2,
    street,
    locality,
    town,
    district,
    county
FROM url(
    'http://prod1.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-complete.csv',
    'CSV',
    'uuid_string String,
    price_string String,
    time String,
    postcode String,
    a String,
    b String,
    c String,
    addr1 String,
    addr2 String,
    street String,
    locality String,
    town String,
    district String,
    county String,
    d String,
    e String'
) LIMIT 10000
SETTINGS max_http_get_redirects=10;

데이터가 각 호스트에 완전히 복제되는 것을 확인해요:

-- clickhouse-01
SELECT count(*)
FROM uk.uk_price_paid_local

--   ┌─count()─┐
-- 1.│   10000 │
--   └─────────┘

-- clickhouse-02
SELECT count(*)
FROM uk.uk_price_paid_local

--   ┌─count()─┐
-- 1.│   10000 │
--   └─────────┘

호스트 중 하나가 실패할 때 어떤 일이 발생하는지 보여주기 위해 어느 호스트에서든 간단한 테스트 데이터베이스와 테스트 테이블을 만들어요:

CREATE DATABASE IF NOT EXISTS test ON CLUSTER cluster_1S_2R;
CREATE TABLE test.test_table ON CLUSTER cluster_1S_2R
(
    `id` UInt64,
    `name` String
)
ENGINE = ReplicatedMergeTree
ORDER BY id;

uk_price_paid 테이블과 마찬가지로 어느 호스트에서든 데이터를 삽입할 수 있어요:

INSERT INTO test.test_table (id, name) VALUES (1, 'Clicky McClickface');

하지만 호스트 중 하나가 다운되면 어떻게 될까요? 이를 시뮬레이션하려면 다음을 실행해 clickhouse-01을 중지해요:

docker stop clickhouse-01

호스트가 다운되었는지 확인하려면 다음을 실행해요:

docker-compose ps

Response

NAME                   IMAGE                                        COMMAND            SERVICE                CREATED          STATUS          PORTS
clickhouse-02          clickhouse/clickhouse-server:latest          "/entrypoint.sh"   clickhouse-02          X minutes ago    Up X minutes    127.0.0.1:8124->8123/tcp, 127.0.0.1:9001->9000/tcp
clickhouse-keeper-01   clickhouse/clickhouse-keeper:latest-alpine   "/entrypoint.sh"   clickhouse-keeper-01   X minutes ago    Up X minutes    127.0.0.1:9181->9181/tcp
clickhouse-keeper-02   clickhouse/clickhouse-keeper:latest-alpine   "/entrypoint.sh"   clickhouse-keeper-02   X minutes ago    Up X minutes    127.0.0.1:9182->9181/tcp
clickhouse-keeper-03   clickhouse/clickhouse-keeper:latest-alpine   "/entrypoint.sh"   clickhouse-keeper-03   X minutes ago    Up X minutes    127.0.0.1:9183->9181/tcp

clickhouse-01이 이제 다운되었으므로 테스트 테이블에 데이터를 한 행 더 삽입하고 테이블을 조회해요:

INSERT INTO test.test_table (id, name) VALUES (2, 'Alexey Milovidov');
SELECT * FROM test.test_table;

Response

   ┌─id─┬─name───────────────┐
1. │  1 │ Clicky McClickface │
2. │  2 │ Alexey Milovidov   │
   └────┴────────────────────┘

이제 다음 명령으로 clickhouse-01을 재시작해요(완료 후 docker-compose ps를 다시 실행해 확인할 수 있어요):

docker start clickhouse-01

docker exec -it clickhouse-01 clickhouse-client를 실행한 후 clickhouse-01에서 테스트 테이블을 다시 조회해요:

Query

SELECT * FROM test.test_table

Response

   ┌─id─┬─name───────────────┐
1. │  1 │ Clicky McClickface │
2. │  2 │ Alexey Milovidov   │
   └────┴────────────────────┘

이 시점에서 놀아보기 위해 전체 UK 부동산 가격 데이터셋을 수집하고 싶다면 다음 쿼리를 실행할 수 있어요:

TRUNCATE TABLE uk.uk_price_paid_local ON CLUSTER cluster_1S_2R;
INSERT INTO uk.uk_price_paid_local
SELECT
    toUInt32(price_string) AS price,
    parseDateTimeBestEffortUS(time) AS date,
    splitByChar(' ', postcode)[1] AS postcode1,
    splitByChar(' ', postcode)[2] AS postcode2,
    transform(a, ['T', 'S', 'D', 'F', 'O'], ['terraced', 'semi-detached', 'detached', 'flat', 'other']) AS type,
    b = 'Y' AS is_new,
    transform(c, ['F', 'L', 'U'], ['freehold', 'leasehold', 'unknown']) AS duration,
    addr1,
    addr2,
    street,
    locality,
    town,
    district,
    county
FROM url(
    'http://prod1.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-complete.csv',
    'CSV',
    'uuid_string String,
    price_string String,
    time String,
    postcode String,
    a String,
    b String,
    c String,
    addr1 String,
    addr2 String,
    street String,
    locality String,
    town String,
    district String,
    county String,
    d String,
    e String'
    ) SETTINGS max_http_get_redirects=10;

clickhouse-02 또는 clickhouse-01에서 테이블을 조회해요:

Query

SELECT count(*) FROM uk.uk_price_paid_local;

Response

   ┌──count()─┐
1. │ 30212555 │ -- 30.21 million
   └──────────┘

결론 (Conclusion)

이 클러스터 토폴로지의 장점은 두 레플리카로 데이터가 두 개의 별도 호스트에 존재한다는 것이에요. 한 호스트가 실패하면 다른 레플리카가 손실 없이 데이터를 계속 제공해요. 이는 저장 수준에서 단일 실패 지점을 제거해요. 호스트 하나가 다운되면 남은 레플리카는 여전히:

  • 중단 없이 읽기 쿼리를 처리할 수 있어요
  • 새 쓰기를 받아들일 수 있어요(일관성 설정에 따라 다름)
  • 애플리케이션의 서비스 가용성을 유지할 수 있어요

실패한 호스트가 다시 온라인 상태가 되면:

  • 정상 레플리카에서 누락된 데이터를 자동으로 동기화할 수 있어요
  • 수동 개입 없이 정상 작동을 재개할 수 있어요
  • 완전한 중복을 신속히 복원할 수 있어요

다음 예제에서는 두 샤드와 하나의 레플리카만 있는 클러스터를 설정하는 방법을 살펴볼게요.

더 알아보기 (Learn more)