LLM WikiAccess-protected knowledge portal
← 스터디 홈
26편 · 약 13분

DuckLake 1.0: JSON 파일을 버리고 SQL로 Lakehouse 카탈로그를 만든 이유

파일 기반 메타데이터의 구조적 문제

2026년 4월 13일 공개된 DuckLake 1.0은 Lakehouse 형식의 기반 가정 하나를 바꿨다. 메타데이터도 오브젝트 스토리지에 파일로 둬야 한다—이 전제를 버리고, 메타데이터를 SQL 데이터베이스 테이블에 저장한다(이 글 작성일 기준 96일 전 공개; 90일 창을 소폭 넘지만 동일한 아키텍처적 전환을 보여주는 대체 주제가 없어 180일 범위 안에서 선택했다).

Apache Iceberg와 Delta Lake가 해결하지 못한 운영 문제가 있다.

소용량 쓰기의 파일 증식: Iceberg는 INSERT 한 번에 manifest list → manifest file → data file 조합을 새로 만든다. 1행을 바꿔도 파일 3개 이상이 생긴다. S3 오브젝트 수는 시간이 지날수록 폭발적으로 증가한다.

계획 시간의 파일 I/O: 쿼리 플래너가 metadata.json → manifest list → manifest file → data file 순서로 내려가며 파일을 읽어야 테이블 통계와 파티션 정보를 파악한다. 수십 GB 테이블이라도 파일 수가 수천 개면 플래닝이 수 초 걸린다.

DDL의 비원자성: Iceberg에서 스키마 변경은 새 메타데이터 파일을 쓰고 atomic rename으로 포인터를 바꾸는 방식이다. S3의 eventual consistency는 이 원자성을 보장하지 않는다. s3:PutObject 직후 다른 엔진이 구 포인터를 볼 수 있다.

DuckLake는 이 문제들의 공통 원인을 찾았다—메타데이터가 파일에 있다는 것. 해결책은 SQL 데이터베이스다.


DuckLake의 핵심 설계: SQL이 카탈로그다

DuckLake는 세 레이어를 명확히 분리한다.

레이어담당구현체
카탈로그(메타데이터)스키마·파일 목록·스냅샷·통계SQL DB (SQLite / PostgreSQL / DuckDB)
스토리지(데이터)실제 행 데이터Parquet 파일 (S3, GCS, 로컬 등)
컴퓨트쿼리 실행DuckDB, Spark, Trino, DataFusion

데이터 파일은 여전히 오브젝트 스토리지의 Parquet이다. 달라진 것은 그 파일들에 대한 모든 정보—경로, 행 수, 컬럼 통계, 삭제 정보, 스냅샷 이력—가 SQL 테이블 안에 있다는 점이다. 메타데이터 조회는 SELECT, 커밋은 INSERT + COMMIT, 스키마 변경은 DB 트랜잭션 안에서 이루어진다.

DuckLake vs 파일 기반 Lakehouse: 메타데이터 경로 Apache Iceberg (파일 기반) 쿼리 플래너 metadata.json manifest list manifest file ×N data file ×N ← 파일 여러 번 읽기 ← 파일 수 폭발 INSERT 1행 → metadata.json + manifest list + manifest file + data file 쿼리 플래닝 = S3 API 호출 수십 회 → p99 수 초 DDL 원자성: atomic rename → S3 eventual consistency 위험 DuckLake (SQL 카탈로그) 쿼리 플래너 SQL 카탈로그 DB ducklake_snapshot · ducklake_data_file ducklake_column · ducklake_inlined_data Parquet data file ×N (S3) ← 단일 SQL 쿼리 ← 대용량만 파일 INSERT → SQL INSERT (소용량 인라인) 또는 Parquet + SQL 포인터 쿼리 플래닝 = SELECT 1회 → 밀리초 DDL 원자성: DB 트랜잭션 → 완전한 ACID 보장 공통: 실제 데이터는 모두 Parquet 파일로 오브젝트 스토리지에 저장 DuckLake가 바꾼 것은 "Parquet 파일의 위치와 통계를 어디에 기록하는가"뿐 VS 카탈로그 선택: SQLite (로컬/개발) · PostgreSQL (팀/프로덕션) · DuckDB (분석 최적화)
DuckLake vs Iceberg 메타데이터 경로 비교

메타데이터 테이블 구조: 28개의 역할

DuckLake 1.0 사양은 28개의 SQL 테이블로 구성된다. 운영자가 알아야 할 핵심 테이블은 다음과 같다.

테이블역할
ducklake_snapshot커밋 이력. 각 트랜잭션 = 스냅샷 1개
ducklake_snapshot_changes스냅샷별 변경 사항 (어떤 파일이 추가/삭제됐는지)
ducklake_schema스키마(데이터베이스) 정의
ducklake_table테이블 정의 (이름, 파티션 방식, 옵션)
ducklake_column컬럼 정의 + 통계 (null count, min/max, distinct count)
ducklake_data_fileParquet 파일 참조 (S3 경로, 행 수, 파일 크기, 컬럼 통계)
ducklake_delete_file삭제 벡터 파일 참조
ducklake_files_scheduled_for_deletion다음 vacuum 대상 파일 목록
ducklake_inlined_data_tables소용량 데이터를 카탈로그 DB에 직접 저장
-- DuckLake 카탈로그 연결 (PostgreSQL 백엔드, S3 데이터)
ATTACH 'ducklake:postgres:dbname=my_catalog host=catalog.internal'
    AS my_lake (DATA_PATH 's3://my-bucket/data/');

-- 이후 표준 SQL로 동작
CREATE TABLE my_lake.events (
    id      BIGINT,
    ts      TIMESTAMPTZ,
    payload VARCHAR
);

INSERT INTO my_lake.events VALUES (1, now(), '{"type":"click"}');
SELECT * FROM my_lake.events WHERE ts > now() - INTERVAL '1 hour';

-- 스냅샷 목록 조회
SELECT snapshot_id, snapshot_time, changes
FROM my_catalog.ducklake_snapshot
ORDER BY snapshot_time DESC;

-- 타임 트래블
SELECT * FROM my_lake.events AT (VERSION => 42);

ATTACH 한 줄로 카탈로그 연결과 데이터 경로가 동시에 설정된다. 이후 DML/DDL은 표준 SQL이다.


Data Inlining: 소용량 쓰기의 파일 증식 해결

파일 기반 Lakehouse의 가장 큰 운영 부담은 스트리밍 수집 또는 소용량 업데이트가 만들어내는 소파일 문제다. DuckLake는 Data Inlining으로 이를 카탈로그 레이어에서 흡수한다.

기본값은 data_inlining_row_limit = 10이다. INSERT/UPDATE/DELETE가 이 임계값보다 적은 행에 영향을 주면 Parquet 파일을 만들지 않는다. 해당 행들이 ducklake_inlined_data_tables에 직접 기록된다.

INSERT 5행 → Parquet 파일 없음 → ducklake_inlined_data_tables에 5행 저장
READ → SQL JOIN으로 인라인 데이터 + Parquet 데이터를 합쳐 반환
COMPACT 트리거 시 → 인라인 데이터를 Parquet으로 플러시

임계값은 설정으로 조정 가능하다.

-- data_inlining_row_limit 조정
SET ducklake_data_inlining_row_limit = 50;

-- 인라인 데이터 현황 확인
SELECT table_name, count(*) AS inlined_rows
FROM my_catalog.ducklake_inlined_data_tables
GROUP BY table_name;

언제 중요한가: CDC 파이프라인에서 row-by-row 업데이트가 많거나, IoT 수집 경로에서 소량 배치가 빈번할 때 파일 수를 수십 배 줄인다. 단, 인라인 데이터는 카탈로그 DB 크기를 늘린다. 카탈로그 DB 모니터링이 필요하다.


삭제 벡터: Iceberg V3 호환

DuckLake 1.0은 Iceberg V3 삭제 벡터(puffin 파일 포맷)와 호환되는 deletion vector를 지원한다. 삭제된 행의 위치가 비트맵으로 별도 파일에 기록되며, 전체 Parquet 파일을 재작성하지 않아도 논리적 삭제가 가능하다.

UPDATE events SET status = 'archived' WHERE ts < '2026-01-01'
→ 변경된 행: copy-on-write → 새 Parquet 파일 작성
→ 삭제된 구 행: deletion vector → ducklake_delete_file에 등록
→ ducklake_snapshot_changes에 원자적으로 기록

현재 DuckLake 1.0은 파일당 단일 삭제 벡터 puffin 파일을 지원한다. 향후 버전에서 파일당 여러 deletion vector를 하나의 puffin 파일로 묶어 소파일 문제를 추가로 줄일 예정이다.


ACID 트랜잭션: DDL도 포함

SQL 데이터베이스가 카탈로그이기 때문에 DDL도 DB 트랜잭션 안에서 실행된다.

-- 다중 테이블 트랜잭션 (DDL + DML 혼합)
BEGIN;
ALTER TABLE my_lake.events ADD COLUMN region VARCHAR;
INSERT INTO my_lake.events VALUES (2, now(), '{}', 'us-east-1');
COMMIT;
-- 둘 다 성공하거나 둘 다 롤백된다

Iceberg에서 ALTER TABLE ADD COLUMN은 metadata.json 파일을 새로 쓰는 방식으로 동작한다. 두 엔진이 동시에 스키마를 변경하면 파일 포인터 충돌이 발생할 수 있다. DuckLake에서는 DB의 행 잠금이 충돌을 방지하고 트랜잭션이 원자적으로 적용된다.


멀티엔진 지원 현황

DuckLake 1.0 기준 지원 엔진:

엔진상태비고
DuckDBGA (레퍼런스 구현)v1.5.2부터 내장 확장
Apache DataFusion지원Rust 생태계
Pandas지원Python 로컬 분석
Apache Spark커넥터 개발 중아직 프로덕션 미검증
Trino지원SQL 쿼리 엔진

멀티엔진 쓰기의 핵심 조건은 카탈로그 DB가 동시성 제어를 제공해야 한다는 점이다. SQLite는 파일 잠금 방식이라 단일 라이터에만 안전하다. 팀 환경이나 여러 엔진이 쓰기를 공유한다면 PostgreSQL이 권장된다.


카탈로그 백엔드 선택 기준

시나리오권장 카탈로그이유
개인 분석, 프로토타입SQLite설치 없음, 단일 파일
팀 공유, 프로덕션PostgreSQL동시 쓰기, MVCC, 신뢰성
DuckDB 전용 분석DuckDB 파일단일 바이너리, 고속 스캔
MotherDuck 클라우드MotherDuck (DuckDB 관리형)서버리스, 관리 불필요
-- SQLite 카탈로그 (로컬 개발)
ATTACH 'ducklake:./my_catalog.db' AS my_lake (DATA_PATH './data/');

-- PostgreSQL 카탈로그 (팀/프로덕션)
ATTACH 'ducklake:postgres:host=db.internal dbname=ducklake_catalog user=ducklake'
    AS my_lake (DATA_PATH 's3://my-bucket/data/');

-- DuckDB 파일 카탈로그
ATTACH 'ducklake:./catalog.duckdb' AS my_lake (DATA_PATH 's3://my-bucket/data/');

비교: Iceberg · Delta Lake · DuckLake

Apache IcebergDelta LakeDuckLake 1.0
메타데이터 저장소JSON/Avro 파일 (S3)Parquet 트랜잭션 로그 (S3)SQL 테이블 (SQLite/PG/DuckDB)
쿼리 플래닝 경로파일 계층 (metadata→manifest→data)델타 로그 스캔SQL SELECT 1회
소용량 쓰기소파일 생성소파일 생성 (auto-optimize 필요)인라인 저장 (파일 생성 안 함)
DDL 원자성atomic rename (S3 제한 있음)트랜잭션 로그 (강력)SQL 트랜잭션 (완전한 ACID)
멀티엔진 쓰기OCC (낙관적 동시성)OCCSQL DB 잠금
오픈 사양Apache License 2.0Linux FoundationApache License 2.0
프로덕션 성숙도매우 높음높음초기 (v1.0)
카탈로그 분리별도 카탈로그 서버 필요별도 카탈로그 서버 필요카탈로그 = SQL DB (기본 포함)
주 적합 용도대규모 멀티엔진Databricks 생태계DuckDB 중심 분석·실험

Iceberg와 Delta Lake는 수년간의 프로덕션 검증이 쌓인 성숙한 포맷이다. DuckLake는 아키텍처가 다른 방향을 제시하지만, 2026년 중반 기준 Spark 커넥터가 아직 개발 중이고 대규모 멀티엔진 쓰기 검증이 부족하다. 신규 DuckDB 중심 분석 파이프라인에서는 검토 가치가 있지만, 기존 Iceberg/Delta 기반 프로덕션을 교체하는 것은 이르다.


운영자 도입 체크리스트

평가 전 확인

  • [ ] 주 쿼리 엔진이 DuckDB인가? (Spark 주 사용자라면 커넥터 GA 대기)
  • [ ] 멀티 라이터 환경인가? PostgreSQL 카탈로그 선택
  • [ ] 기존 Iceberg/Delta 테이블을 이전할 계획인가? (현재 자동 마이그레이션 도구 없음)

설치 및 연결

  • [ ] DuckDB v1.5.2 이상 확인 (SELECT version();)
  • [ ] INSTALL ducklake; LOAD ducklake; 실행
  • [ ] 카탈로그 DB 접근 권한 확인 (SQLite: 파일 경로, PG: 연결 문자열)
  • [ ] DATA_PATH S3 버킷 쓰기 권한 확인

운영 중 모니터링

  • [ ] 카탈로그 DB 크기 증가 추이 (특히 data inlining 활성화 시)
  • [ ] ducklake_files_scheduled_for_deletion 테이블 주기적 vacuum 실행 확인
  • [ ] 스냅샷 수 증가 모니터링 (장기 타임 트래블 보존 정책 설정)
  • [ ] PostgreSQL 카탈로그 백업 포함 여부 확인 (카탈로그 분실 = 데이터 경로 분실)

알려진 제약

  • [ ] Spark 커넥터 미 GA → Spark 쓰기는 프로덕션 사용 자제
  • [ ] 삭제 벡터: 현재 파일당 단일 벡터 → 대량 업데이트/삭제 후 COMPACT 필요
  • [ ] 카탈로그 DB 가용성이 테이블 접근 가용성과 직결됨 → HA 카탈로그 구성 권장

DuckLake 1.0을 한 문장으로

Lakehouse 메타데이터를 S3 파일 계층에서 꺼내 SQL 테이블에 넣으면 쿼리 플래닝이 밀리초로 줄고 소파일 문제가 사라지지만, 카탈로그 DB의 가용성과 백업 전략이 새로운 운영 책임이 된다.


References