Parquet 레이크 모델링
Parquet 직접 스캔, metadata 함수, row group, compression, materialization 기준을 정리합니다.
핵심 요약
- DuckDB는 Parquet를 column projection·filter pushdown으로 직접 읽습니다. 다만 반복 쿼리나 join이 많을 때는 매번 decompression을 다시 하고 통계도 부족해서 DuckDB 테이블에 적재하는 편이 낫습니다.
- 성능 문제는 SQL보다 파일 구조에서 비롯될 때가 많으니, parquet_schema·parquet_metadata로 파일 수, row group, compression, statistics를 먼저 봅니다.
- 필터 후 읽을 파일 전체의 row group 수가 worker를 충분히 활용하는지 봅니다. 너무 작으면 metadata overhead가 커지고, 너무 크면 selective query와 병렬성이 약해집니다.
- 자주 필터링하는 날짜·계정·tenant 컬럼으로 ORDER BY 정렬해 zonemap 효율을 높입니다.
- compression은 snappy·zstd를 대표 입력으로 비교하고, gzip은 기존 소비자 호환 요구를 확인합니다. 같은 파일을 반복해서 직접 스캔한다면 무거운 compression이 오히려 불리합니다.
DuckDB와 Parquet는 함께 쓸 때 가치가 큽니다. DuckDB는 Parquet를 직접 읽으면서 column projection과 filter pushdown을 활용하고, metadata 함수로 파일 구조를 들여다봅니다. 하지만 모든 Parquet를 직접 스캔하는 것이 정답은 아닙니다.
직접 스캔과 적재의 기준
| 기준 | Parquet 직접 스캔 | DuckDB 테이블 적재 |
|---|---|---|
| 1회성 탐색 | 적합 | 과함 |
| 반복 쿼리 | 가능하지만 매번 decompression | 유리 |
| 많은 join | 통계가 부족할 수 있음 | cardinality estimate가 유리 |
| 디스크 여유 | 원본만 필요 | DB 파일만큼 추가 공간 필요 |
| 데이터 공유 | 파일 lake와 잘 맞음 | 단일 DB 파일 배포에 유리 |
-- 직접 스캔
SELECT region, sum(amount) AS revenue
FROM 's3://lake/orders/year=2026/month=*/part-*.parquet'
WHERE order_date >= DATE '2026-01-01'
GROUP BY ALL;
-- 반복 분석용 적재
CREATE OR REPLACE TABLE orders AS
SELECT *
FROM read_parquet('s3://lake/orders/**/*.parquet', hive_partitioning = true);Metadata를 먼저 본다
SELECT *
FROM parquet_schema('curated/orders.parquet');
SELECT
file_name,
row_group_id,
row_group_num_rows,
compression
FROM parquet_metadata('curated/orders.parquet');Parquet 성능 문제는 SQL보다 파일 구조에서 비롯될 때가 많습니다. 파일 수, row group 수, compression, column type, min/max statistics가 필터 pushdown과 병렬성을 좌우합니다.
Row group 설계
DuckDB 문서는 Parquet row group size 기본값과 병렬성의 관계를 설명합니다. 단일 파일이면 여러 row group이 필요하고, 다중 파일이면 읽을 파일 전체의 row group 수를 함께 봅니다. row group이 너무 작으면 metadata overhead가 커지고, 너무 크면 selective query와 병렬성이 약해집니다.
COPY (
SELECT *
FROM orders
ORDER BY order_date, customer_id
) TO 'curated/orders.parquet'
(FORMAT parquet, COMPRESSION zstd, ROW_GROUP_SIZE 100000);정렬은 zonemap 효율을 높입니다. 날짜, 계정, tenant처럼 자주 필터링하는 컬럼이 있다면 output을 만들 때 정렬 순서를 의도적으로 잡아 둡니다.
Compression 선택
| 방식 | 특징 | 권장 |
|---|---|---|
| snappy | 빠른 압축/해제, 넓은 호환성 | 기본 운영 |
| zstd | 압축률과 속도의 균형 | 저장 비용이 중요할 때 |
| gzip | 해제 비용이 큼 | 기존 파일 호환 외에는 피함 |
DuckDB performance guide는 무거운 compression이 반복 직접 스캔에서 불리할 수 있다고 설명합니다. 같은 파일을 거듭 분석한다면 DuckDB 테이블로 적재하거나 가벼운 compression으로 다시 씁니다.
2.0 포맷 경계
비동기 I/O는 전송 대기를 줄이지만 row group 크기·필터 선택도·압축 해제 비용을 없애지는 않습니다. tiny file을 많이 만들기 전에 I/O 관측으로 네트워크와 CPU 병목을 분리합니다.
VARIANT를 Parquet에 저장한다면 다른 reader가 그 표현을 지원하는지 읽기 회귀를 추가합니다. 범용 BI용 파일에는 typed projection을 제공할 수 있습니다. native .duckdb의 storage version 69와 Parquet 파일 포맷은 서로 다른 호환성 계약입니다.
2.0 reader 옵션을 선택하는 기준
| 옵션 | 점검할 계약 |
|---|---|
file_row_group_number | 파일 내 0부터 시작하는 row group 추적; filename과 함께 식별 |
int96_as | legacy INT96 timestamp의 정밀도와 표현 범위 |
utf8_validation | 기본 strict 오류와 replace/ignore에 따른 데이터 변형을 구분 |
prefetch_strategy | 기본 auto와 whole_group의 원격 읽기량·메모리 비교 |
오염된 UTF-8을 무조건 제거하거나 timestamp 정밀도를 조용히 낮추면 원본 손상을 숨길 수 있습니다. 품질 검사와 reader 옵션 변경을 한 검토 단위로 다룹니다. Parquet 2.0.