확장
확장 (2 샤드 1 레플리카)
이 예제에서는 확장하는 간단한 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_2S_1R
cd cluster_2S_1R
# 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_2S_1R 디렉터리에 다음 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
networks:
cluster_2S_1R:
ipv4_address: 192.168.7.1
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
networks:
cluster_2S_1R:
ipv4_address: 192.168.7.2
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
networks:
cluster_2S_1R:
ipv4_address: 192.168.7.5
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
networks:
cluster_2S_1R:
ipv4_address: 192.168.7.6
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
networks:
cluster_2S_1R:
ipv4_address: 192.168.7.7
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"
networks:
cluster_2S_1R:
driver: bridge
ipam:
config:
- subnet: 192.168.7.0/24
gateway: 192.168.7.254
다음 하위 디렉터리와 파일을 만들어요:
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.xmlClickHouse 구성 파일과 결합돼요.users.d디렉터리는 사용자 구성 파일users.xml을 포함하며, 여기서 사용자에 대한 사용자 지정 구성을 정의해요. 이 구성은 모든 ClickHouse 설치에 포함된 기본 ClickHouseusers.xml구성 파일과 결합돼요.
사용자 지정 구성 디렉터리 자신의 구성을 작성할 때는
/etc/clickhouse-server/config.xml과etc/clickhouse-server/users.xml의 기본 구성을 직접 수정하기보다config.d와users.d디렉터리를 활용하는 것이 모범 사례예요.
<clickhouse replace="true">
이 줄은 config.d와 users.d 디렉터리에 정의된 구성 섹션이 기본 config.xml과 users.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_2S_1R 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_2S_1R>
<shard>
<replica>
<host>clickhouse-01</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse-02</host>
<port>9000</port>
</replica>
</shard>
</cluster_2S_1R>
</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>
</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_2S_1R이 정의돼요. <cluster_2S_1R></cluster_2S_1R> 블록은 <shard></shard>와 <replica></replica> 설정을 사용해 클러스터의 레이아웃을 정의하며, ON CLUSTER 절을 사용해 클러스터 전역에서 실행되는 쿼리(분산 DDL 쿼리)의 템플릿 역할을 해요. 기본적으로 분산 DDL 쿼리는 허용되지만 allow_distributed_ddl_queries 설정으로 끌 수도 있어요. 샤드당 레플리카가 하나뿐이므로 internal_replication은 기본적으로 false로 둬요.
<remote_servers>
<cluster_2S_1R>
<shard>
<replica>
<host>clickhouse-01</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse-02</host>
<port>9000</port>
</replica>
</shard>
</cluster_2S_1R>
</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>
</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_2S_1R 디렉터리의 루트에서 docker-compose up 명령으로 클러스터를 시작해요:
docker-compose up -d
docker가 ClickHouse와 Keeper 이미지를 가져오기 시작한 다음 컨테이너를 시작하는 것을 볼 수 있어요:
[+] Running 6/6
✔ Network cluster_2s_1r_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_2S_1R 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_2S_1R │ 1 │ 1 │ clickhouse-01 │ 9000 │
2. │ cluster_2S_1R │ 2 │ 1 │ 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. │ task_queue │ │ /clickhouse │
2. │ sessions │ │ /clickhouse │
3. │ clickhouse │ │ / │
4. │ keeper │ │ / │
└────────────┴───────┴─────────────┘
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_2S_1R;
쿼리를 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_2S_1R
(
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 = MergeTree
ORDER BY (postcode1, postcode2, addr1, addr2);
이것은 ON CLUSTER 절을 제외하면 UK property prices 예제 데이터셋 튜토리얼의 원래 CREATE 문에서 사용한 쿼리와 동일해요. ON CLUSTER 절은 CREATE, DROP, ALTER, RENAME 같은 DDL(Data Definition Language) 쿼리의 분산 실행을 위해 설계되어, 이러한 스키마 변경이 클러스터의 모든 노드에 적용되도록 보장해요.
각 호스트의 클라이언트에서 아래 쿼리를 실행해 테이블이 클러스터 전체에 생성되었는지 확인할 수 있어요:
Query
SHOW TABLES IN uk;
Response
┌─name────────────────┐
1. │ uk_price_paid_local │
└─────────────────────┘
UK 가격 데이터를 삽입하기 전에, 어느 호스트에서든 일반 테이블에 데이터를 삽입하면 어떤 일이 발생하는지 보는 빠른 실험을 해볼게요. 어느 호스트에서든 다음 쿼리로 테스트 데이터베이스와 테이블을 만들어요:
CREATE DATABASE IF NOT EXISTS test ON CLUSTER cluster_2S_1R;
CREATE TABLE test.test_table ON CLUSTER cluster_2S_1R
(
`id` UInt64,
`name` String
)
ENGINE = MergeTree()
ORDER BY id;
이제 clickhouse-01에서 다음 INSERT 쿼리를 실행해요:
INSERT INTO test.test_table (id, name) VALUES (1, 'Clicky McClickface');
clickhouse-02로 전환해 다음 INSERT 쿼리를 실행해요:
Query
INSERT INTO test.test_table (id, name) VALUES (1, 'Alexey Milovidov');
이제 clickhouse-01 또는 clickhouse-02에서 다음 쿼리를 실행해요:
-- from clickhouse-01
SELECT * FROM test.test_table;
-- ┌─id─┬─name───────────────┐
-- 1.│ 1 │ Clicky McClickface │
-- └────┴────────────────────┘
--from clickhouse-02
SELECT * FROM test.test_table;
-- ┌─id─┬─name───────────────┐
-- 1.│ 1 │ Alexey Milovidov │
-- └────┴────────────────────┘
ReplicatedMergeTree 테이블과 달리 해당 특정 호스트의 테이블에 삽입된 행만 반환되고 두 행 모두 반환되지 않는 것을 알 수 있어요. 두 샤드에 걸쳐 데이터를 읽으려면 모든 샤드에 걸쳐 쿼리를 처리할 수 있는 인터페이스가 필요해요. 이것은 우리가 select 쿼리를 실행할 때 두 샤드의 데이터를 결합하고, insert 쿼리를 실행할 때 두 샤드에 데이터를 삽입해요. ClickHouse에서 이 인터페이스를 분산 테이블(distributed table) 이라고 하며, Distributed 테이블 엔진으로 만듭니다. 어떻게 동작하는지 살펴볼게요.
7. 분산 테이블 만들기
다음 쿼리로 분산 테이블을 만들어요:
CREATE TABLE test.test_table_dist ON CLUSTER cluster_2S_1R AS test.test_table
ENGINE = Distributed('cluster_2S_1R', 'test', 'test_table', rand())
이 예제에서는 삽입이 샤드들에 무작위로 분산되도록 rand() 함수를 샤딩 키로 선택해요.
이제 어느 호스트에서든 분산 테이블을 조회하면, 이전 예제와 달리 두 호스트에 삽입된 두 행을 모두 얻게 돼요:
SELECT * FROM test.test_table_dist;
┌─id─┬─name───────────────┐
1. │ 1 │ Alexey Milovidov │
2. │ 1 │ Clicky McClickface │
└────┴────────────────────┘
UK 부동산 가격 데이터에도 같은 작업을 해볼게요. 어느 호스트 클라이언트에서든 다음 쿼리를 실행해 이전에 ON CLUSTER로 만든 기존 테이블을 사용하는 분산 테이블을 만들어요:
CREATE TABLE IF NOT EXISTS uk.uk_price_paid_distributed
ON CLUSTER cluster_2S_1R
ENGINE = Distributed('cluster_2S_1R', 'uk', 'uk_price_paid_local', rand());
8. 분산 테이블에 데이터 삽입
이제 어느 호스트에든 연결해 데이터를 삽입해요:
INSERT INTO uk.uk_price_paid_distributed
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;
데이터가 삽입되면 분산 테이블을 사용해 행 수를 확인할 수 있어요:
Query
SELECT count(*)
FROM uk.uk_price_paid_distributed
Response
┌──count()─┐
1. │ 30212555 │ -- 30.21 million
└──────────┘
어느 호스트에서든 다음 쿼리를 실행하면 데이터가 샤드들에 대략 고르게 분산되었음을 볼 수 있어요(어떤 샤드에 삽입할지 선택이 rand()로 설정되었으므로 결과가 다를 수 있다는 점 유의):
-- from clickhouse-01
SELECT count(*)
FROM uk.uk_price_paid_local
-- ┌──count()─┐
-- 1. │ 15107353 │ -- 15.11 million
-- └──────────┘
--from clickhouse-02
SELECT count(*)
FROM uk.uk_price_paid_local
-- ┌──count()─┐
-- 1. │ 15105202 │ -- 15.11 million
-- └──────────┘
호스트 중 하나가 실패하면 어떤 일이 발생할까요? 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-02에서 이전에 분산 테이블에서 실행했던 같은 select 쿼리를 실행해요:
SELECT count(*)
FROM uk.uk_price_paid_distributed
Response
Received exception from server (version 25.5.2):
Code: 279. DB::Exception: Received from localhost:9000. DB::Exception: All connection tries failed. Log:
Code: 32. DB::Exception: Attempt to read after eof. (ATTEMPT_TO_READ_AFTER_EOF) (version 25.5.2.47 (official build))
Code: 209. DB::NetException: Timeout: connect timed out: 192.168.7.1:9000 (clickhouse-01:9000, 192.168.7.1, local address: 192.168.7.2:37484, connection timeout 1000 ms). (SOCKET_TIMEOUT) (version 25.5.2.47 (official build))
Code: 198. DB::NetException: Not found address of host: clickhouse-01: (clickhouse-01:9000, 192.168.7.1, local address: 192.168.7.2:37484). (DNS_ERROR) (version 25.5.2.47 (official build))
: While executing Remote. (ALL_CONNECTION_TRIES_FAILED)
안타깝게도 우리 클러스터는 장애 허용이 없어요. 호스트 중 하나가 실패하면 클러스터가 비정상으로 간주되고, 호스트 하나가 실패해도 데이터를 삽입할 수 있었던 이전 예제의 복제 테이블과 달리 쿼리가 실패해요.
결론 (Conclusion)
이 클러스터 토폴로지의 장점은 데이터가 별도의 호스트들에 분산되고 노드당 저장 공간의 절반을 사용한다는 것이에요. 더 중요하게는 쿼리가 두 샤드 모두에서 처리되어 메모리 활용 측면에서 더 효율적이고 호스트당 I/O를 줄여요. 이 클러스터 토폴로지의 주요 단점은 당연히 호스트 중 하나를 잃으면 쿼리를 제공할 수 없게 된다는 것이에요. 다음 예제에서는 확장성과 장애 허용을 모두 제공하는 두 샤드와 두 레플리카를 가진 클러스터를 설정하는 방법을 살펴볼게요.