DuckDB — 파일 위에서 바로 분석하는 열 지향 엔진 · 파일 위에서 바로 · 이론
서버 없이, 파일을 그대로 표로 읽는다
한 줄 요약
DuckDB 는 서버가 없는 분석용 데이터베이스다. 프로세스 안에서 돌고, 파일 경로를 FROM 에 적으면 그 파일이 표가 되며, 열 단위로 저장하고 읽어서 집계가 빠르다.
왜 이게 필요했나
월별 판매 로그 CSV 여섯 개를 받았다. "국가별 월 매출 좀 뽑아 주세요" 라는 요청이다. PostgreSQL 에 표를 만들고 적재하면 표 정의부터 써야 하고, 파이썬으로 읽으면 SQL 이 아니라 코드를 새로 짜는 일이 된다.
세 번째 길이 있다. select ... from 'sales/2026-01.csv.gz'. 표를 만들지 않는다. 파일이 그 자리에서 표가 된다. 이 코스의 픽스처(36만 행) 여섯 파일을 글롭으로 한 번에 세는 데 0.15초가 걸렸다.
어떻게 동작하나
DuckDB 는 라이브러리다. duckdb 명령 하나가 곧 데이터베이스이고, 파이썬에서는 import duckdb 가 전부다. 접속할 서버도, 계정도, 포트도 없다.
파일을 읽을 때는 스니퍼가 앞부분 표본을 보고 구분자·헤더·열 형을 추론한다. 대개 맞지만 표본으로 판단할 수 없는 것은 틀린다. 05 Jan 2026 같은 영문 월 날짜는 VARCHAR 로 남고, 숫자 열에 N/A 가 섞이면 그 열 전체가 VARCHAR 가 된다. 그래서 DESCRIBE 로 추론 결과를 먼저 보고, read_csv(..., dateformat = '%d %b %Y', nullstr = ['', 'N/A']) 처럼 모르는 것만 알려 준다. nullstr 는 기본값(빈 칸)을 대체하므로 빈 칸을 함께 적어야 한다.
저장 방식이 다르다. PostgreSQL 은 행을 이어서 쓰고, DuckDB 와 Parquet 는 열을 이어서 쓴다. sum(qty) 를 구할 때 행 저장은 모든 열을 읽어야 하지만 열 저장은 qty 열만 읽는다. Parquet 는 행 그룹마다 열의 최소·최대가 적혀 있어서 범위 밖 그룹은 열지도 않는다. 같은 36만 행으로 잰 값이다.
csv.gz 여섯 개 3,847,035 바이트 매장별 집계 137 msparquet(zstd) 2,920,050 바이트 매장별 집계 2 ms크기는 조금 줄었지만 시간은 60배 넘게 차이 난다. gzip 은 한 스레드로 처음부터 끝까지 풀어야 하고, 푼 뒤에도 텍스트를 숫자로 바꾸는 파싱이 남는다. Parquet 는 필요한 열을 여러 스레드가 나눠 읽고 이미 이진수라 파싱이 없다.
파티션은 디렉터리 이름으로 조건을 거는 방식이다. PARTITION_BY (month) 로 쓰면 month=2026-03/ 아래에 파일이 놓이고, where month = '2026-03' 은 디렉터리 이름만 보고 나머지 파일을 건너뛴다. 결과를 남기려면 duckdb /root/duck/sales.duckdb 처럼 경로를 준다. 경로 없이 열면 메모리 DB 라 종료하면 사라진다.
실무에서 진짜 중요한 것
첫째, 원본을 옮기지 말고 그 자리에서 읽는다. "일단 DB 에 넣고" 는 대개 하루를 먹는다. 파일에 먼저 물어보면 스키마와 지저분한 열을 몇 분 안에 안다.
둘째, CSV 는 받는 형식이지 보관하는 형식이 아니다. 한 번 읽어 Parquet 로 바꿔 두면 이후 모든 질의가 그 비용을 돌려받는다. 압축은 zstd 로 — 기본 snappy 는 gzip CSV 보다 커질 때가 있다.
셋째, DESCRIBE 를 먼저 친다. 추론이 틀린 열은 조용히 문자열로 남아 정렬과 비교가 어긋난다. 다음 실습에서 그 파일을 고쳐 본다.