연결·스토리지 모델
in-memory, file-backed database, ATTACH, read-only 연결, WAL, checkpoint를 운영 관점으로 정리합니다.
핵심 요약
- in-memory, file-backed, read-only, ATTACH, direct file scan은 각각 적합한 작업이 다르므로 "이 쿼리가 상태를 남기는가"를 먼저 구분합니다.
- read-write 모드는 단일 프로세스가 읽고 쓰며, 여러 프로세스가 공유해야 하면 access_mode='READ_ONLY'로 제한하고 Python은 thread별 .cursor()를 분리합니다.
- crash 복구는 WAL replay에 기대지 말고 staging .duckdb와 publish Parquet를 분리해 성공 후에만 final prefix로 노출합니다.
- ATTACH는 파일 DB를 namespace로 나누기 좋지만 권한 경계가 아니므로 민감 데이터는 파일 경로와 OS 권한으로 분리합니다.
- in-memory 결과를 보존하려면 EXPORT DATABASE나 COPY로 명시적으로 파일에 남깁니다.
DuckDB에서는 연결 방식이 곧 운영 모델입니다. 같은 SQL이라도 in-memory 연결, 파일 DB 연결, read-only 연결, ATTACH 조합에 따라 실패 모드가 달라집니다.
DBA는 "이 쿼리가 상태를 남기는가"부터 구분합니다.
연결 모드
| 모드 | 예시 | 적합한 작업 |
|---|---|---|
| in-memory | duckdb.connect() | notebook, 임시 검증, one-shot 변환 |
| file-backed | duckdb.connect('analytics.duckdb') | 반복 분석, 통계 유지, local mart |
| read-only | access_mode = 'READ_ONLY' | 여러 프로세스가 같은 파일을 조회 |
| attached database | ATTACH 'raw.duckdb' AS raw | DB 파일 간 이동, stage/main 분리 |
| direct file scan | FROM 's3://bucket/*.parquet' | 데이터 레이크 탐색 |
단일 writer와 다중 reader
DuckDB는 in-process 엔진입니다. read-write 모드에서는 한 프로세스가 읽고 쓰며, 그 프로세스 안에서는 여러 writer thread가 동작합니다. 여러 프로세스가 같은 DB 파일에 동시에 write하는 구조는 설계 기준이 아닙니다. 여러 프로세스가 공유해야 한다면 read-only 연결로 제한합니다.
-- read-only 연결은 client별 connection option으로 설정한다.
-- SQL 안에서는 현재 연결의 쓰기 가능성을 운영 문서로 명시한다.
SELECT current_database();Python에서는 thread별 cursor를 분리합니다.
import duckdb
from threading import Thread
con = duckdb.connect("analytics.duckdb")
def run_query():
local_con = con.cursor()
return local_con.execute("SELECT count(*) FROM events").fetchone()DB 파일과 WAL
file-backed DuckDB는 main database file과 write-ahead log를 씁니다. crash 이후에는 새 세션이 WAL을 replay해 복구합니다. 하지만 분석 batch를 안전하게 운영하려면 WAL 복구에 기대기보다 staging 파일과 publish 파일을 분리합니다.
| 대상 | 권장 |
|---|---|
| 원본 파일 | 읽기 전용 prefix로 유지 |
| staging DB | batch run id가 들어간 임시 .duckdb |
| publish output | 성공 후에만 final Parquet prefix로 노출 |
| 실패 처리 | staging DB, temp directory, partial output 삭제 |
ATTACH 패턴
ATTACH 'raw.duckdb' AS raw;
ATTACH 'mart.duckdb' AS mart;
CREATE OR REPLACE TABLE mart.daily_revenue AS
SELECT date_trunc('day', paid_at) AS day, sum(amount) AS revenue
FROM raw.payments
GROUP BY ALL;ATTACH는 파일 DB를 namespace로 나누기 좋지만, 복잡한 권한 경계가 아닙니다. 민감 데이터와 공유 mart는 파일 경로와 OS 권한으로 분리합니다.
in-memory를 파일로 남기기
임시 분석으로 시작했다가 결과를 보존해야 하면 EXPORT DATABASE나 COPY로 명시적으로 남깁니다.
COPY (
SELECT customer_id, sum(amount) AS lifetime_value
FROM payments
GROUP BY customer_id
) TO 'out/customer_ltv.parquet' (FORMAT parquet, COMPRESSION zstd);