Ken
들어가며
ClickHouse의 Materialized View(MView)는 실시간 데이터 파이프라인 구축의 핵심 구성 요소입니다. 특히 외부 시스템(MySQL, PostgreSQL 등)과 연동할 때, 데이터 전송 효율성이 전체 시스템 성능을 좌우합니다. 이 글에서는 MView에서 block size 관련 설정을 제어할 수 있는지, 그리고 실제로 어떻게 동작하는지를 실증 테스트를 통해 검증해보았습니다.
- 들어가며
- 1. 해결하려는 문제 상황
- 1.1 전형적인 시나리오: 실시간 데이터 파이프라인
- 1.2 발생하는 성능 문제들
- 문제 1: 과도한 네트워크 I/O
- 문제 2: MySQL 트랜잭션 부하
- 문제 3: ClickHouse 읽기 성능 저하
- 1.3 핵심 질문
- 2. Block Size 관련 주요 파라미터
- 2.1 파라미터 상세 설명
- 2.2 파라미터별 동작 방식
- max_block_size: 읽기 블록 크기
- min_insert_block_size_rows: 쓰기 블록 크기
- 2.3 실제 사용 예시
- 3. Materialized View에서의 적용 검증
- 3.1 테스트 환경 구성
- 3.2 MView 생성: 올바른 문법
- ✅ 정답: TO 방식 (SETTINGS 지원)
- ❌ 오답: ENGINE 중복 선언
- 3.3 설정 저장 확인
- 3.4 실행 시 적용 확인
- 3.5 실제 Batch INSERT 동작 확인
- 3.6 Batch INSERT 증거 확인
- 4. 다른 Table Engine에서의 적용 여부
- 4.1 지원하는 Engine들
- 4.2 MySQL Table Engine 특성
- 4.3 Kafka Engine 제약사항
- 4.4 S3 / HDFS Engine
- 5. 실전 권장 설정 및 베스트 프랙티스
- 5.1 시나리오별 권장 설정
- 시나리오 1: MySQL로 실시간 집계 전송
- 시나리오 2: 고빈도 데이터, 낮은 지연 요구사항
- 시나리오 3: 대용량 배치 처리, 네트워크 대역폭 제한
- 시나리오 4: S3/HDFS 파일 출력
- 5.2 주의사항 및 함정
- 함정 1: GROUP BY와 min_insert_block_size_rows
- 함정 2: Duplicate Key 에러
- 함정 3: 메모리 부족
- 5.3 모니터링 쿼리
- 1. MView 설정 확인
- 2. MView 실행 통계
- 3. query_log에서 batch 크기 확인
- 4. MySQL 연결 상태
- 5.4 프로덕션 체크리스트
- 6. 결론
- 6.1 핵심 요약
- 6.2 권장 기본 설정
- 6.3 최종 조언
- 6.4 추가 학습 자료
1. 해결하려는 문제 상황
1.1 전형적인 시나리오: 실시간 데이터 파이프라인
많은 기업들이 다음과 같은 아키텍처를 사용합니다:
1.2 발생하는 성능 문제들
문제 1: 과도한 네트워크 I/O
MView가 소량의 데이터를 매번 MySQL로 전송하면 네트워크 왕복 횟수가 급증합니다.
❌ 비효율적인 시나리오:
100만 건 이벤트 → 1건씩 INSERT → 100만 번의 네트워크 요청
각 요청당 평균 5ms 지연 → 총 5,000초 (약 83분!)
✅ 효율적인 시나리오:
100만 건 이벤트 → 10,000건씩 batch INSERT → 100번의 네트워크 요청
각 요청당 평균 50ms 지연 → 총 5초
문제 2: MySQL 트랜잭션 부하
MySQL은 각 INSERT를 트랜잭션으로 처리하므로 작은 batch로 자주 쓰면 트랜잭션 오버헤드가 증가합니다.
-- 이런 패턴이 수천~수만 번 반복되면?
START TRANSACTION;
INSERT INTO table VALUES (1, 'data');
COMMIT;
START TRANSACTION;
INSERT INTO table VALUES (2, 'data');
COMMIT;
영향:
- Connection pool 고갈
- Binlog 파일 크기 급증
- Replication lag 증가
- Lock contention 심화
문제 3: ClickHouse 읽기 성능 저하
소스 테이블에서 데이터를 읽을 때 block size가 너무 작으면 I/O 효율이 떨어집니다.
Block Size가 작을 때 (1,000):
읽기 요청 1,000번 → 각각 오버헤드 발생 → 느림
Block Size가 적절할 때 (65,536):
읽기 요청 15번 → 오버헤드 최소화 → 빠름
1.3 핵심 질문
"Materialized View 단에서 block size를 제어하여 batch INSERT 크기를 조절할 수 있는가?"
일반적인 SELECT 쿼리에서는 다음과 같이 SETTINGS 절로 block size를 제어할 수 있습니다:
SELECT * FROM large_table
SETTINGS max_block_size = 10000;
하지만 Materialized View의 경우 CREATE 문에서 이런 설정이 가능한지, 그리고 실제로 적용되는지가 불명확했습니다.
2. Block Size 관련 주요 파라미터
2.1 파라미터 상세 설명
파라미터 | 기본값 | 역할 | 영향 범위 |
max_block_size | 65,536 | 소스 테이블에서 읽을 때 한 번에 처리하는 행 수 | SELECT 성능 |
min_insert_block_size_rows | 1,048,576 | 타겟 테이블에 쓸 때 최소 누적 행 수 | INSERT batch 크기 |
min_insert_block_size_bytes | 268,435,456 (256MB) | 타겟 테이블에 쓸 때 최소 누적 바이트 수 | INSERT batch 크기 |
max_insert_threads | 0 (자동) | INSERT 작업에 사용할 최대 스레드 수 | 병렬 처리 |
2.2 파라미터별 동작 방식
max_block_size: 읽기 블록 크기
최적화 포인트:
- 너무 크면: 메모리 사용량 증가
- 너무 작으면: I/O 요청 횟수 증가, CPU 오버헤드
min_insert_block_size_rows: 쓰기 블록 크기
중요: min_insert_block_size_rows는 "최소 누적 행 수"입니다. 이 값에 도달해야 실제 INSERT가 발생합니다.
2.3 실제 사용 예시
-- 일반 SELECT 쿼리
SELECT * FROM large_table
SETTINGS max_block_size = 10000;
-- 그렇다면 Materialized View에서는?
CREATE MATERIALIZED VIEW my_mv
TO target_table
AS
SELECT ... FROM source_table
SETTINGS
max_block_size = ???,
min_insert_block_size_rows = ???; -- 이게 가능한가?
3. Materialized View에서의 적용 검증
3.1 테스트 환경 구성
실제 프로덕션과 유사한 환경을 Docker로 구축했습니다:
서비스:
- Kafka 7.5.0 (메시지 브로커)
- ClickHouse 25.11.2 (데이터 처리)
- MySQL 8.0 (타겟 DB)
데이터 플로우:
3.2 MView 생성: 올바른 문법
✅ 정답: TO 방식 (SETTINGS 지원)
❌ 오답: ENGINE 중복 선언
-- TO와 ENGINE을 동시에 사용하면 에러!
CREATE MATERIALIZED VIEW my_mv
TO target_table
ENGINE = MergeTree() -- ❌ Error: Can't declare both TO and ENGINE
SETTINGS ...
에러 메시지:
DB::Exception: Syntax error: Both 'TO' and 'ENGINE' cannot be specified
3.3 설정 저장 확인
MView를 생성한 후, 설정이 실제로 저장되었는지 확인합니다.
SELECT
name,
create_table_query
FROM system.tables
WHERE engine = 'MaterializedView'
AND name = 'buffer_to_mysql_mv'
FORMAT Vertical;
결과:
✅ 검증 완료: SETTINGS가 MView 정의에 영구 저장됩니다!
3.4 실행 시 적용 확인
query_log를 통해 MView 생성 시 설정이 기록되는지 확인합니다.
SELECT
event_time,
query_kind,
Settings['max_block_size'] AS max_block_size,
Settings['min_insert_block_size_rows'] AS min_insert_block_size_rows
FROM system.query_log
WHERE query LIKE '%CREATE MATERIALIZED VIEW%buffer_to_mysql_mv%'
AND type = 'QueryFinish'
FORMAT Vertical;
결과:
event_time: 2025-12-13 10:30:45
query_kind: Create
max_block_size: 1000 ✅
min_insert_block_size_rows: 5000 ✅
3.5 실제 Batch INSERT 동작 확인
50,000개의 이벤트를 Kafka로 전송하여 실제로 batch INSERT가 발생하는지 확인했습니다.
# Kafka에 50,000개 이벤트 전송
python3 kafka_producer.py --num-events 50000
# 30초 대기 후 확인
ClickHouse Buffer Table:
SELECT count() FROM events_buffer;
-- 결과: 50,000 rows
MySQL 집계 결과:
SELECT * FROM mysql_aggregated_events;
event_date | query_kind | query_count | total_duration_ms |
2025-12-13 | SELECT | 8,279 | 20,720,606 |
2025-12-13 | INSERT | 8,360 | 20,908,893 |
2025-12-13 | UPDATE | 8,252 | 20,605,393 |
2025-12-13 | DELETE | 8,412 | 21,023,176 |
2025-12-13 | ALTER | 8,320 | 20,842,037 |
2025-12-13 | CREATE | 8,377 | 21,135,949 |
총 6개 행 (event_date + query_kind로 GROUP BY)
3.6 Batch INSERT 증거 확인
MView가 MySQL로 데이터를 전송할 때 실제로 batch로 묶어서 보내는지 확인하기 위해, 일부러 duplicate key 에러를 유발했습니다.
에러 메시지 (의도된 에러):
✅ 증명 완료: MView가 4개의 행을 한 번의 batch INSERT로 MySQL에 전송했습니다!
4. 다른 Table Engine에서의 적용 여부
4.1 지원하는 Engine들
Table Engine | SETTINGS 지원 | 비고 |
MySQL | ✅ 지원 | 네트워크 I/O 고려 필수 |
PostgreSQL | ✅ 지원 | MySQL과 동일 |
JDBC | ✅ 지원 | 범용 JDBC 드라이버 |
ODBC | ✅ 지원 | 범용 ODBC 드라이버 |
S3 | ✅ 지원 | 파일 크기 최적화 |
HDFS | ✅ 지원 | 블록 크기 조절 가능 |
Kafka | ⚠️ 제한적 | DEFAULT 표현식 불가 |
MergeTree | ✅ 지원 | 일반적인 경우 |
4.2 MySQL Table Engine 특성
MySQL Table Engine은 ClickHouse에서 외부 MySQL 데이터베이스로 직접 쓰는 특수한 엔진입니다.
주의사항:
4.3 Kafka Engine 제약사항
Kafka Engine은 특별한 제약이 있습니다.
-- ❌ 이렇게 하면 에러!
CREATE TABLE kafka_events (
event_time DateTime DEFAULT now(), -- DEFAULT 불가!
query_kind String,
query_duration_ms UInt32
) ENGINE = Kafka(...);
에러 메시지:
DB::Exception: KafkaEngine doesn't support DEFAULT/MATERIALIZED/EPHEMERAL
expressions for columns.
올바른 방법:
-- ✅ DEFAULT 제거
CREATE TABLE kafka_events (
event_time DateTime, -- DEFAULT 없이
query_kind String,
query_duration_ms UInt32
) ENGINE = Kafka(...);
4.4 S3 / HDFS Engine
S3나 HDFS 같은 파일 기반 엔진에서는 block size가 파일 크기에 영향을 줍니다.
CREATE MATERIALIZED VIEW s3_export_mv
TO s3_table
AS
SELECT * FROM source_table
SETTINGS
max_block_size = 1000000, -- 큰 블록으로 파일 크기 증가
min_insert_block_size_rows = 1000000;
효과:
- 작은 파일 여러 개 → 메타데이터 오버헤드
- 큰 파일 몇 개 → 읽기 효율 증가
5. 실전 권장 설정 및 베스트 프랙티스
5.1 시나리오별 권장 설정
시나리오 1: MySQL로 실시간 집계 전송
이유:
max_block_size=65536: 소스 읽기는 기본값으로 충분min_insert_block_size_rows=50000: MySQL 네트워크 I/O와 트랜잭션 부하의 균형min_insert_block_size_bytes=10MB: 메모리 사용량 제한
시나리오 2: 고빈도 데이터, 낮은 지연 요구사항
SETTINGS
max_block_size = 10000, -- 작은 블록으로 빠른 처리
min_insert_block_size_rows = 5000, -- 작은 batch
min_insert_block_size_bytes = 1048576, -- 1MB
max_insert_threads = 4; -- 병렬 처리
사용 예: 실시간 대시보드, 알림 시스템
시나리오 3: 대용량 배치 처리, 네트워크 대역폭 제한
SETTINGS
max_block_size = 100000, -- 큰 블록으로 I/O 최소화
min_insert_block_size_rows = 500000, -- 큰 batch
min_insert_block_size_bytes = 268435456, -- 256MB
max_insert_threads = 1; -- 순차 처리로 부하 분산
사용 예: ETL 작업, 일일 집계
시나리오 4: S3/HDFS 파일 출력
SETTINGS
max_block_size = 1000000, -- 매우 큰 블록
min_insert_block_size_rows = 10000000, -- 1천만 행씩
min_insert_block_size_bytes = 1073741824; -- 1GB
효과: 적은 수의 큰 파일 생성 → 메타데이터 오버헤드 감소
5.2 주의사항 및 함정
함정 1: GROUP BY와 min_insert_block_size_rows
-- 설정: min_insert_block_size_rows = 100000
-- 소스 데이터: 1,000,000 rows
SELECT date, country, count(*)
FROM events
GROUP BY date, country -- 결과가 50개만 나옴
문제: GROUP BY 결과가 50개만 나오면, 설정이 100,000이어도 50개만 INSERT됩니다.
해결: min_insert_block_size_rows는 "소스에서 누적된 행"이 아니라 "최종 결과 행" 기준입니다.
함정 2: Duplicate Key 에러
MySQL 테이블에 Primary Key가 있고, 중복 데이터가 들어오면 에러가 발생합니다.
-- MySQL 테이블
CREATE TABLE aggregated_events (
event_date DATE,
query_kind VARCHAR(50),
query_count BIGINT,
PRIMARY KEY (event_date, query_kind)
);
문제: 같은 날짜/종류의 데이터가 다시 들어오면 Duplicate key 에러
해결 방법 1: MySQL에서 ON DUPLICATE KEY UPDATE 사용
-- ClickHouse에서 직접 지원하지 않으므로
-- MySQL 테이블을 다음과 같이 수정
CREATE TABLE aggregated_events (
...
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
);
해결 방법 2: ClickHouse에서 ReplacingMergeTree 사용
-- MySQL Table Engine 대신 ClickHouse 테이블 사용
CREATE TABLE aggregated_events (
event_date Date,
query_kind String,
query_count UInt64,
total_duration_ms UInt64
) ENGINE = ReplacingMergeTree()
ORDER BY (event_date, query_kind);
-- 주기적으로 MySQL에 UPSERT
함정 3: 메모리 부족
min_insert_block_size_bytes를 너무 크게 설정하면 메모리 부족이 발생할 수 있습니다.
-- ❌ 위험!
SETTINGS min_insert_block_size_bytes = 10737418240; -- 10GB
권장: 서버 메모리의 10% 이하로 설정
5.3 모니터링 쿼리
1. MView 설정 확인
SELECT
name,
database,
engine,
substring(create_table_query,
position(create_table_query, 'SETTINGS'),
200) AS settings_part
FROM system.tables
WHERE engine = 'MaterializedView'
AND name LIKE '%_mv'
FORMAT Vertical;
2. MView 실행 통계
SELECT
view,
status,
last_refresh_time,
last_refresh_result,
exception
FROM system.view_refreshes
WHERE database = 'default'
ORDER BY last_refresh_time DESC
LIMIT 10;
3. query_log에서 batch 크기 확인
SELECT
event_time,
query_duration_ms,
read_rows,
written_rows,
Settings['max_block_size'] AS max_block_size,
Settings['min_insert_block_size_rows'] AS min_insert_block_size_rows
FROM system.query_log
WHERE query LIKE '%INSERT INTO%mysql_aggregated_events%'
AND type = 'QueryFinish'
AND event_time >= now() - INTERVAL 1 HOUR
ORDER BY event_time DESC
LIMIT 20;
4. MySQL 연결 상태
SELECT
host,
port,
database,
user,
default_kind,
pool_size
FROM system.mysql_connections
FORMAT Vertical;
5.4 프로덕션 체크리스트
배포 전 다음 항목들을 확인하세요:
설정 검증:
max_block_size: 소스 테이블 크기에 적합한가?min_insert_block_size_rows: 타겟 시스템 부하를 고려했는가?min_insert_block_size_bytes: 메모리 사용량이 적절한가?max_insert_threads: 병렬 처리가 필요한가?테이블 설계:
모니터링:
백업 및 복구:
6. 결론
6.1 핵심 요약
질문 | 답변 | 상세 |
MView에서 block size 제어 가능? | ✅ 가능 | SETTINGS 절을 SELECT 뒤에 추가 |
설정이 영구 저장되는가? | ✅ 저장됨 | create_table_query에 포함 |
실제로 batch INSERT되는가? | ✅ 동작함 | 에러 로그로 확인 완료 |
모든 Table Engine에서 동작? | ⚠️ 대부분 | Kafka Engine은 일부 제약 있음 |
6.2 권장 기본 설정
대부분의 경우 다음 설정으로 시작하고, 모니터링 후 조정하세요:
CREATE MATERIALIZED VIEW my_mv
TO target_table
AS
SELECT ... FROM source_table
GROUP BY ...
SETTINGS
max_block_size = 65536, -- 기본값 유지
min_insert_block_size_rows = 100000, -- 10만 행
min_insert_block_size_bytes = 10485760; -- 10MB
6.3 최종 조언
DO (해야 할 것):
- 네트워크 I/O가 있는 경우 반드시 block size 조정
- 프로덕션 배포 전 부하 테스트 수행
query_log로 실제 batch 크기 모니터링- MySQL/PostgreSQL의 트랜잭션 특성 이해
DON'T (하지 말아야 할 것):
- 기본값을 맹목적으로 신뢰
- GROUP BY 결과 수를 무시하고 설정
- 메모리 제한 없이 큰 block size 설정
- TO와 ENGINE을 동시에 사용
6.4 추가 학습 자료
- ClickHouse Official Documentation: Materialized Views
- ClickHouse Settings Reference
- MySQL Table Engine
- Kafka Engine
작성일: 2025-12-13
테스트 환경: ClickHouse 25.11.2, MySQL 8.0, Kafka 7.5.0
GitHub: 실습 코드 저장소
이 문서의 모든 테스트는 실제 Docker 환경에서 재현 가능하며, 테스트 스크립트와 설정 파일은 저장소에서 확인할 수 있습니다.