PostgreSQL, MySQL 연동과 CDC (테이블 엔진, PeerDB 기반 ClickPipes, MaterializedPostgreSQL)
운영 DB 의 데이터를 ClickHouse 로 가져오는 길을 조회 위임 엔진, 스냅샷 복사, 변경 데이터 캡처(CDC) 로 나누어 설명하고, CDC 결과를 ReplacingMergeTree 로 받아 최신 상태를 읽는 패턴과 Postgres, MySQL 쪽 준비 사항을 정리합니다.
세 가지 수준의 연동
| 수준 | 방법 | 데이터는 어디에 | 맞는 상황 |
|---|---|---|---|
| 조회 위임 | PostgreSQL, MySQL 테이블 엔진과 함수, 같은 이름의 데이터베이스 엔진 | 원본 DB 에 그대로. 쿼리마다 원본을 읽음 | 작은 코드 테이블, 가끔 보는 조회, 적재 전 탐색 |
| 스냅샷 복사 | INSERT INTO ... SELECT FROM postgresql(...), 딕셔너리 | ClickHouse 에 그 시점 사본 | 하루 한 번 갱신해도 되는 차원 테이블 |
| CDC(변경 데이터 캡처) | ClickPipes Postgres, MySQL 커넥터(PeerDB 기반), PeerDB 오픈소스, Debezium + Kafka, MaterializedPostgreSQL | ClickHouse 에 거의 실시간으로 반영되는 사본 | 운영 테이블을 분석에 바로 쓰고 싶을 때 |
운영 DB 에 분석 쿼리를 직접 날리지 않는 것이 목적이라면 결국 세 번째 수준으로 가게 됩니다. 앞의 두 수준은 그 전 단계이거나 보조 수단입니다.
조회 위임: PostgreSQL, MySQL 엔진
-- 테이블 하나
CREATE TABLE pg_customers (id UInt64, name String, tier String, updated_at DateTime)
ENGINE = PostgreSQL('pg.internal:5432', 'shop', 'customers', 'reader', 'secret', 'public');
-- 데이터베이스 통째로 (스키마의 테이블 목록을 그대로 보여 줌)
CREATE DATABASE pg_shop ENGINE = PostgreSQL('pg.internal:5432', 'shop', 'reader', 'secret', 'public');
SHOW TABLES FROM pg_shop;
-- 함수로 즉석 조회
SELECT * FROM postgresql('pg.internal:5432', 'shop', 'orders', 'reader', 'secret') WHERE id = 10;쿼리를 받으면 ClickHouse 가 원본 DB 에 SELECT 를 보내고 결과를 받아 나머지 연산(조인, 집계) 을 자기 쪽에서 합니다. 단순한 WHERE 조건은 원본으로 내려보내지만(푸시다운), 복잡한 조건이나 집계는 전부 가져와서 처리하므로 큰 테이블을 이 엔진으로 자주 읽으면 운영 DB 가 느려집니다. MySQL 도 MySQL 엔진과 mysql() 함수, MySQL 데이터베이스 엔진으로 똑같이 씁니다.
팁 조회 위임 엔진은 적재 파이프라인의 소스 로 쓰면 가장 유용합니다.
INSERT INTO dim_customers SELECT * FROM pg_customers를 하루 한 번 돌리는 식입니다. 수십만 행 이하 차원 테이블은 딕셔너리 로 올려 두면 조인 없이dictGet으로 조회할 수 있고, 딕셔너리 소스가 PostgreSQL 과 MySQL 을 지원하므로 갱신 주기만 정하면 됩니다.
CDC 의 기본 모양
CDC 는 원본 DB 의 변경 로그(Postgres 의 논리 복제 WAL, MySQL 의 binlog) 를 읽어 INSERT, UPDATE, DELETE 를 ClickHouse 에 흘려 넣습니다. ClickHouse 는 행을 제자리에서 고치는 데이터베이스가 아니므로, 변경을 새 행으로 추가 하고 읽을 때 최신 버전만 고르는 방식을 씁니다. 엔진은 ReplacingMergeTree 입니다.
CREATE TABLE orders (
id UInt64, customer_id UInt64, status LowCardinality(String), amount Decimal(18, 2),
updated_at DateTime64(6),
_version UInt64, -- 변경 순서 (LSN, binlog 위치, 또는 updated_at 기반)
_is_deleted UInt8 -- DELETE 는 1 로 표시한 행이 들어옴
) ENGINE = ReplacingMergeTree(_version, _is_deleted)
ORDER BY id;
-- 최신 상태만 읽기
SELECT * FROM orders FINAL WHERE status = 'paid';ORDER BY는 원본의 기본 키(또는 고유 키) 여야 합니다. 같은 키의 행 중_version이 큰 것이 남고,_is_deleted = 1인 최신 행은 머지 때 사라집니다.FINAL은 읽는 시점에 중복을 정리합니다. 머지가 끝나지 않은 파트가 있어도 최신 상태를 보장하지만 비용이 있습니다. 조회가 많은 테이블은 정렬 키 조건을 꼭 넣고, 24.x 이후FINAL최적화(파티션 단위 병렬,do_not_merge_across_partitions_select_final) 덕에 예전보다 훨씬 가볍습니다. 자세한 비교는 MergeTree 가족 과 삭제와 갱신 에 있습니다.- 집계 테이블은
FINAL결과를 기준으로 만들어야 하므로, 변경이 잦은 CDC 테이블 위에는 머티리얼라이즈드 뷰를 직접 걸지 말고 주기적 Refreshable MV 나INSERT SELECT ... FINAL로 만듭니다.
ClickPipes Postgres, MySQL CDC (ClickHouse Cloud)
ClickHouse 가 2024년에 인수한 PeerDB 가 ClickHouse Cloud 안에 ClickPipes 의 Postgres CDC 커넥터 로 들어왔고 2025년 6월 OpenHouse 에서 GA 가 발표됐습니다. MySQL CDC 커넥터와 MongoDB CDC 커넥터는 그 뒤에 추가됐습니다(단계는 콘솔과 문서에서 확인).
동작은 이렇습니다.
- 콘솔에서 원본 연결 정보와 복제할 테이블을 고르면 초기 스냅샷 을 병렬로 복사합니다. 테이블은 위와 같은
ReplacingMergeTree로 만들어지고_peerdb_version,_peerdb_is_deleted,_peerdb_synced_at컬럼이 붙습니다. - 그 뒤 논리 복제 슬롯(Postgres) 또는 binlog(MySQL) 를 읽어 변경을 배치로 밀어 넣습니다. 동기화 간격(기본 1분 안팎) 과 배치 크기를 조절할 수 있습니다.
- 컬럼 추가 같은 스키마 변경을 따라가고, 테이블을 나중에 추가할 수도 있습니다.
- 지연, 처리 행 수, 오류를 콘솔에서 보고, 슬롯 상태도 함께 보여 줍니다.
소스 측 준비(Postgres):
-- postgresql.conf: wal_level = logical, max_replication_slots, max_wal_senders 충분히
CREATE USER clickpipes_user PASSWORD '...';
GRANT USAGE ON SCHEMA public TO clickpipes_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO clickpipes_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO clickpipes_user;
ALTER ROLE clickpipes_user REPLICATION; -- RDS 는 rds_replication 역할
CREATE PUBLICATION clickpipes_pub FOR TABLE orders, customers;
-- UPDATE, DELETE 의 이전 값이 필요하면
ALTER TABLE orders REPLICA IDENTITY FULL;주의 복제 슬롯은 소비자가 멈추면 WAL 을 계속 붙잡습니다. 파이프를 지우거나 오래 멈출 때 슬롯을 정리하지 않으면 Postgres 디스크가 차서 운영 DB 가 멈출 수 있습니다.
pg_replication_slots의confirmed_flush_lsn과 WAL 크기를 모니터링하고, RDS 라면max_slot_wal_keep_size를 설정하세요.
MySQL 쪽은 binlog_format = ROW, binlog_row_image = FULL, GTID 모드를 켜 두고 복제 권한(REPLICATION SLAVE, REPLICATION CLIENT) 을 줍니다. binlog 보존 기간이 스냅샷 시간보다 짧으면 초기 동기화 뒤 이어 붙지 못하니 보존을 늘려 두세요.
셀프 호스팅에서는
- PeerDB 오픈소스 는 같은 엔진을 Docker 로 직접 돌리는 방식입니다. ClickHouse Cloud 가 아닌 곳에서 Postgres CDC 를 하려면 가장 가깝습니다.
- Debezium + Kafka 는 가장 널리 쓰이는 범용 CDC 입니다. Debezium 이 변경을 Kafka 토픽에
before,after,op,ts_ms봉투로 쓰고, ClickHouse 는 Kafka 엔진 이나 Kafka Connect 싱크로 받습니다. 봉투를ReplacingMergeTree행으로 바꾸는 머티리얼라이즈드 뷰가 핵심입니다.
CREATE MATERIALIZED VIEW orders_cdc_mv TO orders AS
SELECT
JSONExtractUInt(after, 'id') AS id,
JSONExtractUInt(after, 'customer_id') AS customer_id,
JSONExtractString(after, 'status') AS status,
toDecimal64(JSONExtractString(after, 'amount'), 2) AS amount,
fromUnixTimestamp64Micro(JSONExtractUInt(after, 'updated_at')) AS updated_at,
ts_ms AS _version,
if(op = 'd', 1, 0) AS _is_deleted
FROM orders_debezium_queue
WHERE op IN ('c', 'u', 'r', 'd');DELETE 이벤트는 after 가 null 이라 before 에서 키를 꺼내야 합니다(위 예시는 단순화한 것이고 실제로는 if(op = 'd', before, after) 로 분기합니다). Debezium 의 ExtractNewRecordState 변환으로 봉투를 평탄화해 보내면 뷰가 훨씬 단순해집니다.
MaterializedPostgreSQL데이터베이스 엔진은 ClickHouse 가 직접 논리 복제 슬롯을 읽는 내장 기능입니다. Exp 이고 제약이 많아(스키마 변경 제한, 일부 타입 미지원, 운영 안정성) 새 설계에서는 권하지 않습니다.MaterializedMySQL도 같은 처지입니다.
운영에서 자주 만나는 일
- 초기 스냅샷이 느리다 큰 테이블은 파티션 또는 키 범위로 나눠 병렬 복사합니다. ClickPipes 는 스냅샷 병렬도와 행 수 설정이 있고, 직접 할 때는
INSERT SELECT FROM postgresql(...) WHERE id BETWEEN ...을 여러 개 돌립니다. - DELETE 가 안 사라진다
ReplacingMergeTree(_version, _is_deleted)두 번째 인자를 빠뜨렸거나, 머지가 아직 안 됐는데FINAL없이 읽은 경우입니다. 삭제 표시 행은 머지 때 지워지므로 그 전까지는FINAL이 필요합니다. - 같은 키인데 두 행이 보인다
ORDER BY가 원본 기본 키와 다르거나, 파티션 키 때문에 같은 키가 다른 파티션에 들어간 경우입니다. CDC 테이블은 파티션을 두지 않거나 키의 일부로만 둡니다. - UPDATE 폭풍 원본에서 한 행이 초당 수백 번 바뀌면 그만큼 행이 쌓입니다. 집계 쿼리는
FINAL비용이 커지니 주기적OPTIMIZE TABLE ... FINAL대신 머지 설정(min_age_to_force_merge_seconds) 으로 머지를 재촉하거나, 최신 상태 스냅샷 테이블을 따로 만듭니다. - 타입 매핑 Postgres
numeric은Decimal,jsonb는String또는JSON,timestamptz는DateTime64(6, 'UTC'), 배열은Array로 옵니다. 열거형과 사용자 정의 타입은 문자열로 받는 것이 안전합니다.
시작 전 체크리스트
- 원본 테이블마다 기본 키(또는 고유 키) 가 있는지. 없으면 CDC 결과를 합칠 기준이 없습니다.
- 변경량(초당 행 수) 과 테이블 크기를 알고 있는지. 스냅샷 시간과 ClickHouse 쪽 파트 생성 속도를 가늠하는 재료입니다.
- 운영 DB 쪽 복제 설정 변경(wal_level, binlog) 에 재시작이 필요한지, 가능한 시간대는 언제인지.
- 슬롯과 WAL 모니터링 경보가 있는지. CDC 를 켠 뒤 가장 먼저 터지는 곳입니다.
- 조회 쿼리를
FINAL기준으로 작성할지, 스냅샷 테이블을 따로 둘지 정했는지.