본문으로 건너뛰기
clickhouse검색
수집과 통합

입출력 포맷 (JSONEachRow, Parquet, Native, CSV, Arrow, 스키마 추론)

ClickHouse 가 읽고 쓰는 70여 가지 포맷 중 현장에서 실제로 쓰는 것을 골라, 언제 어떤 포맷을 쓰고 어떤 설정으로 파싱 문제를 피하는지, 스키마 추론은 어디까지 믿어도 되는지 정리합니다.

포맷JSONEachRowParquetNative스키마 추론CSV2026-10 기준, 약 14분상태 표시 설명

포맷은 "어떻게 읽고 어떻게 내보낼지" 를 정하는 한 단어

ClickHouse 는 테이블 저장 형식과 입출력 형식을 완전히 분리합니다. 저장은 늘 MergeTree 의 열 지향 파트이고, 데이터가 들어오고 나가는 경계에서만 FORMAT 한 단어로 모양을 정합니다. INSERT 의 입력, SELECT 의 출력, s3() 나 file() 같은 테이블 함수가 읽는 파일, Kafka 메시지 본문까지 모두 같은 포맷 이름을 씁니다.

SELECT * FROM events LIMIT 3 FORMAT JSONEachRow;      -- 출력
INSERT INTO events FORMAT CSVWithNames                  -- 입력 (본문이 이어짐)
SELECT count() FROM s3('https://.../2026/*.parquet', Parquet);  -- 파일 읽기

HTTP 인터페이스에서는 default_format 설정이나 FORMAT 절, 또는 X-ClickHouse-Format 헤더로 정합니다. 쓰는 포맷 설정은 이름이 input_format_..., output_format_... 으로 시작해서 방향을 바로 알 수 있습니다.

현장에서 쓰는 포맷 열 가지

포맷종류한 줄 설명어디에
Native바이너리, 열 지향ClickHouse 내부 블록 형식 그대로. 파싱 비용이 거의 없음공식 클라이언트, 서버 간 복사, clickhouse-client 기본 전송
RowBinary, RowBinaryWithNamesAndTypes바이너리, 행행 단위 고정 인코딩. 직접 만들기 쉬움커스텀 수집기, 고속 INSERT
Parquet바이너리, 열 지향 파일레이크 표준. 압축과 통계 포함S3 적재, 내보내기, 레이크 교환
Arrow, ArrowStream바이너리, 열 지향메모리 형식 교환. pandas, Polars, Spark 와 복사 없이 가까움분석 도구 연동, Arrow Flight
JSONEachRow텍스트, 행한 줄에 객체 하나(NDJSON). 필드 순서 자유, 없는 필드 허용애플리케이션 이벤트, 로그
JSONCompactEachRow텍스트, 행객체 대신 배열 [v1, v2]. 키 반복이 없어 가벼움대량 JSON 전송
JSONAsObject, JSONAsString텍스트객체 전체를 JSON 타입 또는 문자열 한 컬럼으로스키마 모르는 JSON 원문 보관
CSVWithNames, TSVWithNames텍스트첫 줄이 헤더. 타입 정보 없음일회성 이동, 다른 시스템 내보내기
Values텍스트(1, 'a'), (2, 'b') INSERT 문 그대로작은 수동 INSERT
Pretty, PrettyCompact, Vertical텍스트, 출력 전용사람이 보는 표. clickhouse-client 기본터미널

고르는 기준은 단순합니다. 기계가 만들고 기계가 읽으면 Native 나 RowBinary, 사람이 디버깅할 가능성이 있으면 JSONEachRow, 파일로 남기거나 다른 엔진과 나누면 Parquet 입니다. CSV 는 타입을 매번 추측해야 해서 가장 느리고 가장 자주 깨집니다. 피할 수 있으면 피하세요.

팁 포맷 이름 뒤에 붙는 WithNames, WithNamesAndTypes 는 헤더 줄의 유무입니다. 입력에 헤더가 있으면 input_format_with_names_use_header = 1(기본) 덕분에 컬럼 순서가 테이블과 달라도 이름으로 맞춰 들어갑니다. 헤더 없는 CSV 는 순서가 곧 매핑이라 컬럼 하나만 밀려도 조용히 엉뚱한 값이 들어갑니다.

JSON 입력에서 자주 쓰는 설정

설정기본뜻
input_format_skip_unknown_fields1테이블에 없는 키는 버림. 0 이면 오류
input_format_defaults_for_omitted_fields1빠진 컬럼은 DEFAULT 식으로 채움
input_format_null_as_default1Nullable 이 아닌 컬럼에 null 이 오면 기본값으로
input_format_json_read_numbers_as_strings1문자열 컬럼에 숫자가 와도 받아 줌
input_format_json_read_bools_as_numbers1숫자 컬럼에 true/false 가 오면 1/0
input_format_import_nested_json0중첩 객체를 Nested 컬럼(a.b) 으로 펼침
input_format_json_ignore_unknown_keys_in_named_tuple1Tuple 컬럼에 모르는 키가 있어도 통과
date_time_input_formatbasicbest_effort 로 바꾸면 ISO 8601 변형, 시간대 표기, 단위 다른 타임스탬프를 넉넉히 파싱
-- 애플리케이션 이벤트 수집용 사용자 프로필 예시
CREATE SETTINGS PROFILE ingest_json SETTINGS
  date_time_input_format = 'best_effort',
  input_format_skip_unknown_fields = 1,
  input_format_allow_errors_ratio = 0.001;

중첩이 깊고 키가 계속 바뀌는 JSON 은 컬럼으로 억지로 펼치는 대신 JSON 타입 컬럼에 넣는 것이 낫습니다. 25.3 에서 JSON 타입이 GA 가 되면서 서브컬럼이 자동으로 열 지향 저장되고 json.user.id 처럼 바로 쿼리할 수 있습니다. 어떤 키를 컬럼으로 빼고 어떤 키를 JSON 에 남길지는 배열, Map, JSON 다루기 와 데이터 타입 고르기 에서 다룹니다.

Parquet 를 읽을 때

Parquet 는 레이크와 ClickHouse 를 잇는 기본 포맷이라 읽기 경로가 꾸준히 좋아졌습니다.

  • 필터 푸시다운 input_format_parquet_filter_push_down(기본 1) 이 행 그룹의 min/max 통계로 안 읽어도 되는 덩어리를 건너뜁니다. input_format_parquet_bloom_filter_push_down 은 파일에 Bloom 필터가 있을 때 등호 조건을 걸러 줍니다.
  • 네이티브 리더 v3 25.8 에 Exp 로 들어온 새 리더(input_format_parquet_use_native_reader_v3) 는 Parquet 페이지를 ClickHouse 메모리 형식으로 바로 올려 페이지 단위 필터와 PREWHERE 를 지원합니다. 공식 벤치마크에서 평균 1.8배 빨라졌다고 밝혔습니다. 버전에 따라 기본값이 달라지니 system.settings 에서 확인하세요.
  • 스키마 차이 파일마다 컬럼이 다르면 input_format_parquet_allow_missing_columns(기본 1) 로 빠진 컬럼을 기본값으로 채우고, 대소문자가 다르면 input_format_parquet_case_insensitive_column_matching 을 켭니다.
  • 지리 타입 25.5 부터 GeoParquet 의 Point, LineString, Polygon 을 ClickHouse 지리 타입으로 읽고 씁니다.
-- 파일 안 메타데이터만 보기 (행 그룹, 컬럼, 압축)
SELECT * FROM s3('https://bucket.s3.amazonaws.com/events/2026-10-01.parquet', ParquetMetadata)
FORMAT Vertical;

Parquet 를 쓸 때

설정뜻
output_format_parquet_compression_methodzstd, lz4, snappy, gzip, brotli, none. 레이크 호환은 snappy 나 zstd 가 무난
output_format_parquet_row_group_size행 그룹 행 수(기본 100만). 소비자가 Spark, Trino 면 기본값으로 충분
output_format_parquet_string_as_stringString 을 BYTE_ARRAY 대신 UTF-8 STRING 논리 타입으로. 다른 엔진에서 문자열로 보이게 하려면 1
output_format_parquet_write_bloom_filter25.2 부터. 선택적 조건 쿼리를 빠르게 하는 대신 파일이 10% 안팎 커짐
output_format_parquet_version2.latest 기본. 오래된 리더가 있으면 1.0
-- 파티션 경로로 나눠 내보내기 (Hive 스타일 디렉터리)
INSERT INTO FUNCTION s3('https://bucket.s3.amazonaws.com/export/{_partition_id}/data.parquet', Parquet)
PARTITION BY toYYYYMM(ts)
SELECT * FROM events WHERE ts >= '2026-01-01'
SETTINGS output_format_parquet_compression_method = 'zstd';

스키마 추론: 편하지만 믿을 곳과 아닐 곳

s3(), file(), url() 에서 구조를 생략하면 ClickHouse 가 파일 앞부분을 읽어 컬럼과 타입을 추론합니다. Parquet, Arrow, ORC, Native 처럼 파일에 스키마가 들어 있는 포맷은 그대로 믿어도 됩니다. 추론은 메타데이터를 읽는 것뿐이니까요. 문제는 JSON, CSV 같은 텍스트 포맷입니다.

  • 기본으로 input_format_max_rows_to_read_for_schema_inference 25,000 행(또는 ..._max_bytes_... 32 MiB) 만 봅니다. 뒤쪽에만 나오는 키는 컬럼이 안 되고, 앞쪽에서 정수였던 값이 뒤에서 소수가 되면 INSERT 가 실패합니다.
  • 텍스트 포맷에서 추론한 컬럼은 schema_inference_make_columns_nullable 때문에 모두 Nullable 로 잡힐 수 있습니다. 최근 버전은 기본값이 auto(값에 null 이 있을 때만) 지만, 테이블을 추론 결과로 그대로 만들면 불필요한 Nullable 이 남아 압축과 속도를 깎습니다.
  • 날짜와 시간은 input_format_try_infer_dates, input_format_try_infer_datetimes, 정수는 input_format_try_infer_integers 로 켜고 끕니다. 날짜처럼 보이는 문자열이 섞여 있으면 꺼 두는 편이 안전합니다.
-- 추론 결과를 먼저 보고
DESCRIBE s3('https://bucket.s3.amazonaws.com/raw/*.json.gz', JSONEachRow)
SETTINGS schema_inference_hints = 'user_id UInt64, amount Decimal(18, 2)';

-- 추론을 캐시해 두면 같은 경로를 다시 읽을 때 빠름 (기본 켜짐)
SET schema_inference_use_cache_for_s3 = 1;

주의 운영 테이블은 추론으로 만들지 마세요. DESCRIBE 로 뼈대만 얻고, 정렬 키와 코덱, LowCardinality 를 손으로 정한 CREATE TABLE 을 따로 쓰는 것이 원칙입니다. 추론은 탐색과 일회성 적재용입니다. 어떤 컬럼을 어떤 타입으로 둘지는 데이터 타입 고르기 를 참고하세요.

압축 파일과 확장자

파일 함수는 확장자로 포맷과 압축을 함께 추론합니다. events.json.gz 는 JSONEachRow 에 gzip, data.parquet 는 Parquet, *.csv.zst 는 CSV 에 zstd 로 읽습니다. 명시하려면 포맷 다음 인자나 compression 인자에 gzip, zstd, lz4, bz2, xz, brotli, snappy, none 을 줍니다. 내보낼 때도 같은 규칙이라 INSERT INTO FUNCTION s3('.../out.jsonl.zst', JSONEachRow) 처럼 쓰면 압축까지 됩니다.

틀린 줄이 있을 때

텍스트 파일에는 깨진 줄이 늘 있습니다. 전체를 실패시키지 않으려면 허용치를 둡니다.

INSERT INTO events
SELECT * FROM s3('https://bucket.s3.amazonaws.com/raw/2026-10-*.json.gz', JSONEachRow)
SETTINGS input_format_allow_errors_num = 100, input_format_allow_errors_ratio = 0.0001;

허용치를 넘으면 그때까지의 블록 일부는 이미 들어갔을 수 있으니, 실패 시 재실행하려면 블록 중복 제거를 믿거나 파일 단위로 _file 가상 컬럼을 두고 멱등하게 설계합니다. 재시도 원칙은 INSERT 모범 사례 에 있습니다.

다른 시스템으로 내보낼 때 흔한 함정

  • 시간대 DateTime 은 내부적으로 UTC 유닉스 초입니다. 텍스트로 내보내면 세션 시간대로 찍히므로 SET session_timezone = 'UTC' 또는 toString(ts, 'UTC') 로 고정하세요. Parquet 로는 타임스탬프 논리 타입으로 나가므로 소비자가 시간대를 어떻게 해석하는지 확인해야 합니다.
  • UInt64 와 Int64 Parquet 에 UInt64 를 쓰면 일부 리더가 음수로 뒤집어 읽습니다. 교환용이면 Int64 로 캐스팅하거나 output_format_parquet_... 대신 문자열로 내보내는 편이 안전합니다.
  • Nullable 과 빈 문자열 CSV 에서 빈 칸은 기본으로 빈 문자열이지 NULL 이 아닙니다. format_csv_null_representation(기본 \N) 과 input_format_csv_empty_as_default 를 맞추세요.
  • 구분자 format_csv_delimiter 로 세미콜론이나 탭을 지정할 수 있습니다. 값 안에 줄바꿈이 있는 CSV 는 input_format_csv_allow_cr_end_of_line 과 따옴표 규칙을 확인하세요.

공식 문서

이 문서에서