📊 데이터공학

DuckDB

DuckDB

인프로세스 분석 데이터베이스. SQLite for OLAP.

📖 상세 설명

DuckDB는 "SQLite for OLAP"이라 불리는 인프로세스 분석 데이터베이스입니다. 별도의 서버 설치 없이 Python, R, Node.js 등에서 라이브러리로 임포트하여 바로 사용할 수 있으며, 로컬 환경에서 수십 GB의 데이터를 빠르게 분석할 수 있습니다.

DuckDB의 핵심 강점은 컬럼 지향 저장과 벡터화 쿼리 실행입니다. 분석 쿼리에 최적화된 컬럼 지향 방식으로 필요한 컬럼만 읽고, SIMD(Single Instruction Multiple Data) 명령어를 활용한 벡터화 실행으로 CPU를 효율적으로 사용합니다. 이로 인해 Pandas보다 수십 배 빠른 집계 연산이 가능합니다.

다양한 파일 포맷을 직접 쿼리할 수 있습니다. CSV, Parquet, JSON 파일을 메모리에 로드하지 않고도 SQL로 직접 쿼리할 수 있습니다. S3, GCS 같은 클라우드 스토리지의 파일도 지원합니다. 예를 들어 SELECT * FROM read_parquet('s3://bucket/data.parquet')처럼 사용합니다.

Pandas, Polars와의 통합도 뛰어납니다. DataFrame을 DuckDB 테이블로 등록하거나, DuckDB 쿼리 결과를 DataFrame으로 반환할 수 있습니다. Jupyter Notebook에서 데이터 탐색 시 SQL과 Python을 자유롭게 오가며 사용할 수 있어 생산성이 높습니다.

💻 코드 예제

import duckdb
import pandas as pd

# 1. 인메모리 데이터베이스 생성 (기본)
con = duckdb.connect()

# 2. CSV 파일 직접 쿼리 (로드 없이)
result = con.execute("""
    SELECT
        category,
        COUNT(*) as count,
        AVG(price) as avg_price,
        SUM(quantity) as total_qty
    FROM read_csv_auto('sales_data.csv')
    GROUP BY category
    ORDER BY total_qty DESC
    LIMIT 10
""").fetchdf()

print(result)

# 3. Parquet 파일 쿼리 (S3 직접 접근)
con.execute("INSTALL httpfs; LOAD httpfs;")
con.execute("SET s3_region='ap-northeast-2';")

remote_result = con.execute("""
    SELECT * FROM read_parquet('s3://my-bucket/data/*.parquet')
    WHERE event_date >= '2024-01-01'
""").fetchdf()

# 4. Pandas DataFrame과 통합
df = pd.DataFrame({
    'user_id': ['u1', 'u2', 'u3', 'u1', 'u2'],
    'product': ['A', 'B', 'A', 'C', 'A'],
    'amount': [100, 200, 150, 300, 250]
})

# DataFrame을 DuckDB에서 직접 쿼리
user_summary = con.execute("""
    SELECT
        user_id,
        COUNT(DISTINCT product) as product_count,
        SUM(amount) as total_amount
    FROM df
    GROUP BY user_id
""").fetchdf()

# 5. 영구 데이터베이스 파일 사용
con_persistent = duckdb.connect('analytics.duckdb')

con_persistent.execute("""
    CREATE TABLE IF NOT EXISTS events (
        event_id INTEGER,
        event_type VARCHAR,
        user_id VARCHAR,
        timestamp TIMESTAMP
    )
""")

# 6. 대용량 파일 효율적 처리 (파티션별 병렬 읽기)
con.execute("""
    COPY (
        SELECT * FROM read_parquet('data/year=*/month=*/*.parquet',
                                   hive_partitioning=true)
        WHERE year = 2024
    ) TO 'output.parquet' (FORMAT PARQUET)
""")

# 7. Window 함수 활용
con.execute("""
    SELECT
        user_id,
        event_date,
        daily_amount,
        SUM(daily_amount) OVER (
            PARTITION BY user_id
            ORDER BY event_date
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) as cumulative_amount
    FROM daily_summary
""")

print("DuckDB 분석 완료!")
con.close()

🗣️ 실무에서 이렇게 말해요

분석가: "20GB Parquet 파일을 분석해야 하는데 Pandas로 읽으니까 메모리 부족이에요."

데이터엔지니어: "DuckDB로 해보세요. read_parquet으로 직접 쿼리하면 필요한 컬럼만 읽어서 메모리를 적게 써요. 제 노트북에서 60GB 파일도 쿼리 가능했어요."

분석가: "SQL로 되니까 Pandas보다 집계도 편하겠네요. 바로 시도해볼게요."

면접관: "로컬 환경에서 대용량 데이터 분석 경험이 있으신가요?"

지원자: "DuckDB를 활용했습니다. 서버 없이 노트북에서 수십 GB Parquet 파일을 직접 쿼리했어요. Pandas로 하면 메모리 오류가 났는데, DuckDB는 컬럼 지향 읽기와 streaming 처리로 가능했습니다. 복잡한 탐색 분석 후 필요한 부분만 Spark 클러스터로 올렸어요."

리뷰어: "이 Pandas groupby 로직, DuckDB SQL로 바꾸면 더 빠르고 읽기 쉬울 것 같아요."

작성자: "맞아요, 특히 이 다중 집계 부분이 Pandas에서 복잡했는데 SQL로 깔끔해지겠네요. 변경하겠습니다."

⚠️ 주의사항

📚 더 배우기