본문으로 건너뛰기
clickhouse검색
운영과 트러블슈팅

복제 지연과 Keeper 문제 (readonly 테이블, 복제 큐 적체, 세션 만료)

복제 테이블이 readonly 로 떨어지거나 복제 큐가 쌓일 때 system.replicas 와 replication_queue 로 상태를 읽고, Keeper 세션과 interserver 포트, 뮤테이션 적체를 순서대로 푸는 방법을 정리합니다.

ReplicatedMergeTreeKeeperreadonlyreplication_queueSYSTEM RESTORE REPLICA2026-10 기준, 약 12분

증상

복제 쪽 문제는 메시지가 꽤 또렷합니다.

DB::Exception: Table is in readonly mode
(replica path: /clickhouse/tables/01/logs/events/replicas/ch1). (TABLE_IS_READ_ONLY)

DB::Exception: Session expired (Session expired). (KEEPER_EXCEPTION)

Coordination::Exception: Connection loss, path: /clickhouse/tables/... (KEEPER_EXCEPTION)

DB::Exception: Replica /clickhouse/tables/01/logs/events/replicas/ch1 already exists.
(REPLICA_ALREADY_EXISTS)

그 밖에 "복제본마다 결과가 다르다", "다른 노드에 INSERT 한 데이터가 몇 분 뒤에야 보인다", "SYSTEM SYNC REPLICA 가 끝나지 않는다" 도 같은 범주입니다. ReplicatedMergeTree 가 Keeper 를 어떻게 쓰는지는 복제와 샤딩에 있습니다. 짧게 말하면 INSERT 와 머지와 뮤테이션은 전부 Keeper 의 로그에 먼저 적히고, 각 복제본이 그 로그를 자기 큐로 가져와 순서대로 실행합니다. 문제는 거의 "Keeper 에 못 붙음", "큐가 막힘", "파트를 못 받아 옴" 셋 중 하나입니다.

확인 쿼리

복제 테이블 한눈에 보기입니다.

SELECT database, table, is_leader, is_readonly, is_session_expired,
       absolute_delay, queue_size, inserts_in_queue, merges_in_queue,
       log_pointer, log_max_index, active_replicas, total_replicas,
       zookeeper_exception
FROM system.replicas
WHERE is_readonly OR absolute_delay > 60 OR queue_size > 100
ORDER BY absolute_delay DESC;
열뜻정상
is_readonlyKeeper 와 끊겼거나 메타데이터가 없어 쓰기 불가0
is_session_expiredKeeper 세션이 만료됨0
absolute_delay이 복제본이 뒤처진 초수 초
queue_size처리 대기 작업 수수십 이하
log_pointer 와 log_max_index어디까지 읽었나, 로그 끝거의 같음
active_replicas살아 있는 복제본 수total_replicas 와 같음

큐에서 무엇이 막혀 있는지입니다.

SELECT database, table, type, create_time, num_tries, is_currently_executing,
       postpone_reason, last_exception, new_part_name
FROM system.replication_queue
WHERE num_tries > 10 OR postpone_reason != ''
ORDER BY create_time
LIMIT 20;

last_exception 과 postpone_reason 에 이유가 글자로 적혀 있습니다. "No active replica has part ... or covering part" 면 파트를 가진 복제본이 없고, "Not executing fetch because N fetches already executing" 은 단순 혼잡, "Cannot execute ... because another mutation is executing" 은 뮤테이션 적체입니다.

Keeper 쪽 상태입니다.

SELECT * FROM system.zookeeper_connection;   -- 어느 Keeper 에 붙어 있나, 세션 ID
SELECT name, value FROM system.zookeeper WHERE path = '/clickhouse/tables/01/logs/events';
# Keeper 노드에서 4 글자 명령 (기본 포트 9181)
echo mntr | nc localhost 9181   # zk_num_alive_connections, zk_outstanding_requests, zk_server_state
echo srvr | nc localhost 9181   # Mode: leader/follower, Latency
echo ruok | nc localhost 9181   # imok

팁 absolute_delay 가 모든 복제본에서 똑같이 크면 Keeper 쪽, 한 복제본만 크면 그 서버(디스크, 네트워크, interserver 포트) 쪽입니다. 이 구분만으로 절반은 좁혀집니다.

원인과 해결

1. readonly: Keeper 세션이 끊김

가장 흔합니다. Keeper 가 재시작됐거나, 네트워크가 잠깐 끊겼거나, ClickHouse 가 Keeper 응답을 session_timeout_ms(기본 30초) 안에 못 받으면 세션이 만료되고 테이블이 readonly 로 떨어집니다. 보통은 ClickHouse 가 스스로 다시 붙습니다. 몇 분이 지나도 readonly 면 손으로 되살립니다.

SYSTEM RESTART REPLICA logs.events;        -- 복제본을 다시 초기화해 Keeper 에 재접속
SYSTEM RESTART REPLICAS;                   -- 서버 전체

Keeper 가 느려서 세션이 자주 끊기면 Keeper 를 봐야 합니다. Keeper 는 쓰기마다 디스크에 fsync 하므로 로그 디렉터리를 전용 SSD 에 두고, ClickHouse 와 같은 머신에서 디스크를 나눠 쓰지 않게 합니다. mntr 의 zk_avg_latency 가 수십 ms 를 넘으면 느린 것입니다. 노드 수는 3 개(또는 5 개), 짝수는 의미가 없습니다.

2. readonly: Keeper 에 메타데이터가 없음

Keeper 데이터를 잃었거나(디스크 교체, 잘못된 복구) 다른 Keeper 클러스터를 가리키게 됐을 때입니다. 로그에 "No metadata in ZooKeeper for ..." 가 나옵니다. 로컬 디스크에 파트는 그대로 있으니 Keeper 쪽만 다시 만들어 줍니다.

SYSTEM RESTORE REPLICA logs.events;

이 명령은 로컬 파트를 기준으로 Keeper 에 복제본 메타데이터를 다시 적습니다. 모든 복제본에서 한 번씩 돌립니다. 그 전에 Keeper 설정(<zookeeper> 노드 목록)이 맞는지, macros 의 {shard} 와 {replica} 가 바뀌지 않았는지 확인합니다.

3. 큐 적체: 뮤테이션이 길을 막음

ALTER TABLE ... UPDATE/DELETE 는 복제 큐를 통해 모든 복제본에서 파트를 다시 씁니다. 큰 뮤테이션 하나가 수 시간 걸리면 그 뒤의 머지가 기다립니다.

SELECT database, table, mutation_id, command, create_time, parts_to_do, is_done,
       latest_fail_reason
FROM system.mutations WHERE NOT is_done;

KILL MUTATION WHERE database = 'logs' AND table = 'events' AND mutation_id = '0000000042';

뮤테이션을 줄이는 설계(경량 삭제, ReplacingMergeTree, 파티션 삭제)는 삭제와 갱신에 있습니다.

4. 큐 적체: 파트를 못 받아 옴 (fetch 실패)

머지 결과나 INSERT 된 파트는 한 복제본이 만들고 다른 복제본은 interserver_http_port(기본 9009)로 받아 옵니다. 이 포트가 방화벽에 막혔거나 interserver_http_host 가 다른 노드에서 못 푸는 호스트 이름이면 last_exception 에 연결 오류가 적힙니다.

# 다른 복제본에서
curl -s http://ch1:9009/ -o /dev/null -w '%{http_code}\n'   # 200 이 아니면 포트 문제
  • interserver_http_host 를 다른 노드에서 풀 수 있는 이름이나 IP 로 둡니다.
  • 복제본 수십 개가 동시에 받으면 max_replicated_fetches_network_bandwidth 로 대역을 제한할 수 있습니다.
  • 파트를 가진 복제본이 하나도 없으면("No active replica has part") 그 복제본이 돌아올 때까지 기다리거나, 영영 안 돌아오면 아래 5 번처럼 떼어 냅니다.

5. 죽은 복제본 정리

디스크가 날아간 노드를 빼고 새 노드로 바꿀 때, Keeper 에는 옛 복제본이 남아 쿼럼과 머지 결정에 끼어듭니다.

-- 살아 있는 노드에서 죽은 복제본 'ch3' 의 Keeper 항목 삭제
SYSTEM DROP REPLICA 'ch3' FROM TABLE logs.events;
SYSTEM DROP REPLICA 'ch3' FROM DATABASE logs;      -- DB 전체

새 노드는 같은 테이블 DDL 을 같은 Keeper 경로와 자기 {replica} 이름으로 만들면 데이터를 알아서 받아 옵니다(초기 복제). 테이블이 수 TB 면 그 시간 동안 네트워크를 많이 씁니다.

6. 두 복제본의 데이터가 다름

SELECT hostName(), count(), sum(cityHash64(*)) FROM clusterAllReplicas('my_cluster', logs.events)
GROUP BY hostName();

absolute_delay 가 0 인데도 수가 다르면 과거에 파트가 깨졌거나(system.detached_parts 에 broken_ 접두어) 복제 바깥에서 데이터를 건드린 경우입니다. SYSTEM SYNC REPLICA logs.events 로 큐를 끝까지 비운 뒤 다시 비교하고, 그래도 다르면 detached_parts 를 확인합니다. 깨진 파트가 다른 복제본에 있으면 ALTER TABLE ... FETCH PARTITION 으로 받아 올 수 있습니다.

7. 분산 쿼리가 뒤처진 복제본을 읽음

읽기 쿼리는 Distributed 테이블이 복제본 중 하나를 고릅니다. 뒤처진 복제본에서 읽으면 최신 데이터가 빠집니다. max_replica_delay_for_distributed_queries(기본 300초)보다 뒤처진 복제본은 피하고, 모두 뒤처졌을 때 실패시키려면 fallback_to_stale_replicas_for_distributed_queries = 0 으로 둡니다. INSERT 직후 반드시 보여야 한다면 insert_quorum 과 select_sequential_consistency 조합을 씁니다. 비용이 있으니 꼭 필요한 테이블에만 씁니다.

Keeper 운영 메모

  • ClickHouse Keeper 를 쓰고, 새로 ZooKeeper 를 깔지 않습니다. 설정 포맷만 다르고 프로토콜은 호환됩니다.
  • Keeper 전용 노드 3 대가 기본입니다. 작은 클러스터에서는 ClickHouse 와 같은 머신에 띄울 수 있지만 디스크는 따로 둡니다.
  • Keeper 안의 znode 수는 파트 수에 비례합니다. 파트가 수십만 개면 Keeper 메모리도 수 GB 가 됩니다. Too many parts는 Keeper 문제이기도 합니다.
  • system.zookeeper_log 를 켜면 Keeper 요청 하나하나가 남아 느린 요청을 찾을 수 있습니다. 평소에는 꺼 둡니다.
  • Cloud 는 Keeper 를 ClickHouse 가 운영하므로 이 문서의 대부분이 해당하지 않습니다. SharedMergeTree 는 복제 큐 자체가 없습니다 (ClickHouse Cloud 개요).

재발 방지

  1. ReplicasMaxAbsoluteDelay, ReplicasMaxQueueSize(asynchronous_metrics)와 ReadonlyReplica(metrics)에 경보를 둡니다. 모니터링 참고.
  2. Keeper zk_avg_latency, zk_outstanding_requests 도 같은 대시보드에 올립니다.
  3. interserver 포트는 방화벽 규칙 문서에 명시하고, 노드 추가 체크리스트에 넣습니다.
  4. 큰 뮤테이션은 업무 시간 밖에, 파티션 단위로 나눠 돌립니다.

공식 문서

이 문서에서