연결·스토리지 모델
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()를 분리합니다.
- DB crash 복구는 WAL replay를 사용하며, 파일 publish는 별도로 staging .duckdb와 publish Parquet를 분리해 성공 후에만 final prefix로 노출합니다.
- ATTACH는 파일 DB를 namespace로 나누기 좋지만 권한 경계가 아니므로 민감 데이터는 파일 경로와 OS 권한으로 분리합니다.
- in-memory 결과를 보존하려면 EXPORT DATABASE나 COPY로 명시적으로 파일에 남깁니다.
2.0에서 구분할 동시성 경로
| 경로 | 쓰기 조정 위치 | 운영 의미 |
|---|---|---|
| embedded 파일 직접 접근 | 한 writer 프로세스 내부 | 여러 프로세스가 같은 파일을 직접 쓰는 설계를 피함 |
| Quack remote protocol | DuckDB 서버 프로세스 | 여러 클라이언트 프로세스가 서버를 통해 native DB 접근 |
| DuckLake + PostgreSQL catalog | 중앙 catalog | 레이크의 동시 read/write 조정은 별도 배포·운영 계약 |
아래 단일 프로세스 설명은 embedded 직접 파일 접근에 대한 것입니다. 2.0은 Quack을 통한 서버 경로를 지원합니다. 기본 localhost binding을 유지하고 원격 연결은 TLS reverse proxy·인증·네트워크 정책을 함께 설계합니다. 테스트용 직접 외부 노출 옵션을 운영 기본값으로 삼지 않습니다.
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' | 데이터 레이크 탐색 |
Embedded 파일의 단일 writer와 다중 reader
DuckDB는 in-process 엔진입니다. read-write 모드에서는 한 프로세스가 읽고 쓰며, 그 프로세스 안에서는 여러 writer thread가 동작합니다. 여러 프로세스가 같은 DB 파일에 동시에 write하는 구조는 설계 기준이 아닙니다. embedded 직접 파일 접근을 여러 프로세스에서 공유할 때는 모든 연결을 read-only로 제한합니다. 서버 기반 공유는 위 Quack 경로를 검토합니다.
-- 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해 복구합니다. WAL은 DB 트랜잭션 복구를 담당합니다. 별도로 분석 batch의 파일 publish에는 staging 파일과 publish 파일을 분리합니다. 복구가 필요한 main DB와 WAL은 함께 보존합니다.
| 대상 | 권장 |
|---|---|
| 원본 파일 | 읽기 전용 prefix로 유지 |
| staging DB | batch run id가 들어간 임시 .duckdb |
| publish output | 성공 후에만 final Parquet prefix로 노출 |
| 실패 처리 | 소유 프로세스 종료와 복구 필요 여부 확인 후 job 전용 임시 산출물 정리 |
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);CONNECT와 ATTACH 구분
ATTACH는 catalog를 붙이고, CONNECT는 지원되는 remote catalog로 쿼리 실행 대상을 전환합니다. Quack뿐 아니라 지원되는 PostgreSQL/MySQL 연결에도 적용되므로 원격 엔진의 SQL·함수와 트랜잭션 의미를 확인합니다. 로컬 파일 공유 규칙을 그대로 서버 세션에 적용하거나, 원격 엔진의 모든 식이 DuckDB 문법으로 실행된다고 가정하지 않습니다. CONNECT.