https://product.kyobobook.co.kr/detail/S000220221456
LUVIT EPL과 유튜브 데이터로 배우는 DuckDB | 이기준 - 교보문고
LUVIT EPL과 유튜브 데이터로 배우는 DuckDB | 복잡한 데이터 분석 흐름을 더 단순하게 만드는 DuckDB 최근 주목받고 있는 DuckDB를 활용해 SQL 기반 데이터 분석과 실전 프로젝트를 학습할 수 있도록 구
product.kyobobook.co.kr
PostgreSQL과 경쟁하기 위해 만들어진 데이터베이스는 아니었지만, 이제는 데이터 팀들이 분석 방식을 처음부터 다시 생각하게 만들고 있습니다.
실제 엔지니어링 관점의 트레이드오프를 통해 DuckDB와 PostgreSQL을 비교합니다. 분석 워크로드가 왜 DuckDB로 이동하고 있는지, 그리고 각각의 데이터베이스가 진정으로 강점을 발휘하는 영역이 어디인지 살펴봅니다.
가장 흥미로운 데이터베이스 이야기는 AI에 관한 것이 아닙니다.
수년 동안 데이터베이스에 대한 논의는 매우 예측 가능했습니다.
트랜잭션이 필요합니까? - PostgreSQL을 사용하면 됩니다.
분석이 필요합니까? - 데이터 웨어하우스를 구축하면 됩니다.
확장이 필요합니까? - 인프라를 더 추가하면 됩니다.
모두가 이 공식을 알고 있었습니다.
그런데 흥미로운 일이 벌어졌습니다.
엔지니어들이 노트북에서 분석 쿼리를 실행하기 시작했는데, 그 성능이 클러스터, 컨테이너, 오케스트레이션 계층, 그리고 작은 스타트업 하나를 운영할 만큼의 클라우드 비용을 들이는 시스템보다 더 뛰어난 경우가 나타난 것입니다.
그 주인공은 수십억 달러 규모의 플랫폼이 아니었습니다.
바로 DuckDB였습니다.
그리고 갑자기 데이터 엔지니어, 백엔드 개발자, 데이터 분석가, 심지어 머신러닝 팀까지 모두 같은 질문을 던지기 시작했습니다.
"쿼리 엔진 자체가 이렇게까지 빨라졌는데, 왜 우리는 여전히 데이터를 이리저리 옮기고 있는 걸까?"
이 질문에 대한 답이 현대 분석 아키텍처에서 가장 큰 변화 중 하나를 만들어내고 있습니다.
다만 아직 대부분의 사람들은 이를 눈치채지 못했을 뿐입니다.
PostgreSQL은 지난 시대의 승자였습니다
DuckDB가 부상하게 된 이유를 이해하려면 먼저 PostgreSQL이 왜 거의 모든 상황에서 기본 선택지가 되었는지 이해할 필요가 있습니다.
PostgreSQL은 지금까지 만들어진 가장 성공적인 오픈소스 소프트웨어 프로젝트 중 하나입니다.
다음과 같은 기능을 모두 안정적으로 처리합니다.
- OLTP 워크로드
- ACID 트랜잭션
- 복잡한 관계형 데이터 모델
- 동시 접속 사용자
- 복제(Replication)
- 고가용성(High Availability)
- 운영 환경의 API
- 인증 시스템
- 금융 거래
오늘날 수많은 백엔드 시스템이 조용히 PostgreSQL 위에서 동작하고 있습니다.
일반적인 운영 환경의 아키텍처는 다음과 같습니다.
┌──────────────┐
│ FastAPI │
└──────┬───────┘
│
┌──────▼───────┐
│ PostgreSQL │
└──────┬───────┘
│
┌─────────▼─────────┐
│ Redis / Kafka │
└───────────────────┘
문제는 PostgreSQL이 대규모 분석 작업을 위해 설계된 데이터베이스가 아니라는 점입니다.
PostgreSQL이 해결하도록 만들어진 질문은 다음과 같습니다.
"이 고객 한 명에게 무슨 일이 있었는가?"
반면 분석 시스템이 해결해야 하는 질문은 다음과 같습니다.
"지난 18개월 동안 5억 명의 고객에게 어떤 일이 일어났는가?"
이 둘은 완전히 다른 문제입니다.
PostgreSQL에서 분석을 수행할 때 발생하는 숨겨진 비용
많은 팀은 이 문제를 직접 겪으면서 깨닫게 됩니다.
초기에는 모든 것이 매우 만족스럽습니다.
그러다가 제품 관리자가 대시보드를 요청하기 시작합니다.
마케팅 팀은 퍼널 분석을 원합니다.
재무팀은 코호트 분석을 요구합니다.
경영진은 주간 추세를 보고 싶어 합니다.
데이터 사이언스 팀은 과거 데이터를 집계하려고 합니다.
그 순간부터 운영 트래픽을 처리하던 동일한 데이터베이스가 분석 작업까지 함께 수행하게 됩니다.
다음과 같은 쿼리가 곳곳에서 등장하기 시작합니다.
SELECT
DATE(created_at),
COUNT(*),
SUM(total_amount)
FROM orders
WHERE created_at >= NOW() - INTERVAL '365 days'
GROUP BY DATE(created_at)
ORDER BY DATE(created_at);
겉보기에는 단순해 보입니다.
하지만 실제로는 그렇지 않습니다.
대규모 데이터셋에서는 이 쿼리 하나가 수백만 개의 행을 스캔할 수 있습니다.
이 작업이 하루에도 수백 번 반복됩니다.
수십 명의 사용자가 동시에 실행합니다.
결국 트랜잭션 데이터베이스는 운영 업무와 분석 업무를 동시에 감당해야 하는 상황에 놓입니다.
그리고 결국 모두가 피해를 보게 됩니다.
전통적인 해결책은 지나치게 복잡해졌습니다
업계의 대응은 언제나 비슷했습니다.
인프라를 더 추가하는 것입니다.
그리고 아주 많이 추가하는 것입니다.
Application
│
▼
PostgreSQL
│
▼
CDC Pipeline
│
▼
Kafka
│
▼
Spark
│
▼
Data Lake
│
▼
Warehouse
│
▼
BI Tools
기술적으로는 올바른 접근입니다.
하지만 운영 측면에서는 매우 피곤한 구조입니다.
계층이 하나 추가될 때마다 다음과 같은 부담도 함께 늘어납니다.
- 더 많은 장애 발생 지점
- 더 긴 지연 시간
- 더 높은 비용
- 더 큰 운영 및 협업 부담
그리고 여기에는 불편하지만 인정해야 할 사실이 하나 있습니다.
많은 기업들이 실제로 그 정도의 규모가 필요하기도 전에 이러한 아키텍처를 도입했습니다.
그 결과, 실질적인 개발 생산성보다는 언젠가 필요할지도 모를 확장성을 위해 최적화된 시스템들이 대거 만들어지게 되었습니다.
DuckDB의 등장
DuckDB는 분석 문제를 완전히 다른 관점에서 접근했습니다.
기존에는 다음과 같은 질문을 던졌습니다.
"클러스터를 어떻게 더 크게 만들 수 있을까?"
하지만 DuckDB는 이렇게 질문했습니다.
"분석 실행 자체를 압도적으로 효율적으로 만들면 어떨까?"
DuckDB는 OLAP 워크로드를 위해 설계된 컬럼형(Columnar) 분석 데이터베이스입니다.
언뜻 들으면 평범한 이야기처럼 보입니다.
하지만 그 의미는 결코 평범하지 않습니다.
수억 개의 행을 담고 있는 Parquet 데이터셋을 생각해 보겠습니다.
DuckDB는 그 파일을 직접 조회할 수 있습니다.
데이터를 적재할 필요도 없습니다.
ETL도 필요 없습니다.
데이터 웨어하우스로 옮길 필요도 없습니다.
Spark 클러스터도 필요 없습니다.
Kubernetes를 배포할 필요도 없습니다.
import duckdb
result = duckdb.sql("""
SELECT
country,
SUM(revenue) as revenue
FROM 'sales.parquet'
GROUP BY country
ORDER BY revenue DESC
""").df()
이것이면 끝입니다.
쿼리 하나만 실행하면 됩니다.
복잡한 인프라 구축 과정은 필요하지 않습니다.
왜 이렇게 빠르게 느껴질까?
이 속도는 마법이 아닙니다.
아키텍처의 차이입니다.
PostgreSQL은 데이터를 행(Row) 단위로 저장합니다.
DuckDB는 데이터를 열(Column) 단위로 저장합니다.
다음과 같은 테이블이 있다고 가정해 보겠습니다.
id | country | revenue | created_at
만약 필요한 데이터가 country와 revenue뿐이라면,
PostgreSQL은 일반적으로 전체 행을 읽습니다.
반면 DuckDB는 필요한 열만 읽습니다.
분석 워크로드에서는 이것이 엄청난 장점이 됩니다.
특히 데이터가 수십억 건에 이르는 경우에는 더욱 그렇습니다.
그 결과는 명확합니다.
디스크 I/O가 줄어듭니다.
메모리 사용량이 감소합니다.
불필요한 작업이 사라집니다.
그리고 현대 CPU는 이러한 방식의 처리를 매우 잘 수행합니다.
아무도 이야기하지 않는 개발자 생산성의 변화
DuckDB가 가져온 가장 큰 변화는 성능이 아닙니다.
바로 생산성입니다.
오랫동안 데이터 분석은 전문적인 인프라를 다룰 수 있는 사람들의 영역이었습니다.
하지만 이제는 개별 엔지니어가 직접 원하는 질문에 답을 찾을 수 있습니다.
백엔드 개발자는 운영 데이터의 내보내기 파일을 자신의 PC에서 바로 분석할 수 있습니다.
데이터 사이언티스트는 데이터 웨어하우스 접근 권한을 요청하지 않아도 Parquet 파일을 직접 조회할 수 있습니다.
프로덕트 엔지니어는 몇 분 만에 사용자 행동 패턴을 분석할 수 있습니다.
호기심에서 인사이트까지 이어지는 장벽이 빠르게 사라지고 있습니다.
이것이야말로 진정한 혁명입니다.
실제 백엔드 환경의 예
전자상거래 플랫폼을 운영한다고 가정해 보겠습니다.
운영 데이터베이스는 PostgreSQL을 사용합니다.
주문은 FastAPI 서비스를 통해 들어옵니다.
운영 환경의 쓰기 경로(Write Path)
@router.post("/orders")
async def create_order(
payload: OrderCreate,
db: AsyncSession
):
order = Order(
customer_id=payload.customer_id,
total=payload.total
)
db.add(order)
await db.commit()
return {"order_id": order.id}
이것은 PostgreSQL이 가장 잘하는 작업입니다.
트랜잭션이 중요합니다.
일관성이 중요합니다.
동시성 처리가 중요합니다.
이런 시스템을 DuckDB로 대체하려고 해서는 안 됩니다.
바로 이 점을 많은 사람들이 오해하고 있습니다.
잘못된 아키텍처
일부 팀은 DuckDB의 벤치마크 결과를 보고 곧바로 이렇게 생각합니다.
DuckDB
│
Everything
하지만 이런 접근은 대부분 좋은 결과로 이어지지 않습니다.
DuckDB는 트랜잭션 데이터베이스를 대체하도록 설계된 시스템이 아닙니다.
이를 주력 쓰기(Write) 데이터베이스로 사용하려고 하면 여러 문제가 발생하고, 다음과 같은 장점을 잃게 됩니다.
- 성숙한 트랜잭션 보장
- 다중 사용자 쓰기 처리
- 운영 도구(Operational Tooling)
- 풍부한 운영 환경 생태계 지원
도구를 잘못 선택한 것입니다.
그리고 해결하려는 문제도 잘못 이해한 것입니다.
실용적인 아키텍처
현명한 팀들은 점점 더 트랜잭션과 분석의 역할을 분리하고 있습니다.
┌──────────────┐
│ FastAPI API │
└──────┬───────┘
│
┌──────▼───────┐
│ PostgreSQL │
└──────┬───────┘
│
Outbox Pattern
│
┌──────▼───────┐
│ Kafka │
└──────┬───────┘
│
┌──────▼───────┐
│ Parquet │
└──────┬───────┘
│
┌──────▼───────┐
│ DuckDB │
└──────────────┘
이 구조에서는 각 시스템이 자신이 가장 잘하는 역할만 담당합니다.
Outbox 패턴이 모든 것을 바꿉니다
확장 가능한 백엔드 시스템에서 가장 흔한 실수 중 하나는 쓰기 작업과 이후 분석 파이프라인을 강하게 연결(Coupling)하는 것입니다.
좋지 않은 구현은 다음과 같습니다.
async def create_order(order):
await save_order(order)
await kafka.publish(
"orders",
order.dict()
)
만약 Kafka가 장애를 일으키면 어떻게 될까요?
데이터 일관성을 유지하는 일이 매우 복잡해집니다.
더 나은 구현은 다음과 같습니다.
async def create_order(order, db):
async with db.begin():
db.add(Order(**order))
db.add(
OutboxEvent(
event_type="order_created",
payload=order
)
)
그리고 별도의 백그라운드 작업자가 이벤트를 전송합니다.
while True:
events = fetch_pending_events()
for event in events:
kafka.publish(
event.event_type,
event.payload
)
mark_processed(event.id)
이 패턴은 시스템의 신뢰성을 크게 향상시킵니다.
그리고 이렇게 생성된 동일한 이벤트는 이후 분석용 데이터셋을 만드는 데에도 활용될 수 있으며, DuckDB가 이를 효율적으로 분석하게 됩니다.
데이터베이스보다 중요한 것은 멱등성(Idempotency)
확장 과정에서 발생하는 많은 장애는 데이터베이스 자체의 문제가 아닙니다.
재시도(Retry) 처리의 문제입니다.
좋지 않은 구현은 다음과 같습니다.
@app.post("/payments")
async def process_payment(request):
charge_card()
save_payment()
return {"success": True}
이 요청이 재시도되면 어떻게 될까요?
카드가 두 번 결제됩니다.
축하합니다.
금융 사고가 발생했습니다.
더 나은 구현은 다음과 같습니다.
@app.post("/payments")
async def process_payment(
request,
idempotency_key: str
):
existing = await redis.get(
idempotency_key
)
if existing:
return existing
result = charge_card()
await redis.set(
idempotency_key,
result
)
return result
좋은 시스템 설계가 되어 있지 않다면, 어떤 데이터베이스를 선택하더라도 그 문제를 해결해 주지는 못합니다.
관측 가능성(Observability)은 인프라를 추가하는 것보다 더 중요합니다
많은 팀은 성능 문제가 발생하면 가장 먼저 서비스를 더 추가하려고 합니다.
하지만 실제로 필요한 것은 시스템이 무엇을 하고 있는지 명확하게 볼 수 있는 가시성입니다.
구조화된 로그(Structured Logging)의 예는 다음과 같습니다.
logger.info(
"query_executed",
query_type="analytics",
duration_ms=duration,
rows_scanned=rows
)
메트릭(Metrics)은 다음과 같이 수집할 수 있습니다.
analytics_query_duration.observe(
execution_time
)
분산 추적(Tracing)은 다음과 같습니다.
with tracer.start_as_current_span(
"analytics_query"
):
run_query()
대부분의 확장성 문제는 CPU 문제처럼 보이지만, 실제로는 시스템 구성 요소 간의 조율(Co-ordination) 문제인 경우가 많습니다.
우리는 왜 이렇게 복잡한 시스템을 만들게 되었을까?
그 이유는 무능해서가 아닙니다. 역사 때문입니다.
10년 전만 해도 스토리지는 비쌌습니다.
CPU 성능은 지금보다 훨씬 낮았습니다.
로컬 환경에서 대규모 분석을 수행하는 것은 어려웠습니다.
클라우드 데이터 웨어하우스는 실제 문제를 해결하는 훌륭한 선택이었습니다.
당시 엔지니어들은 자신들이 가진 제약 조건에 맞춰 시스템을 최적화했습니다.
하지만 지금은 그 제약이 달라졌습니다.
도구도 달라졌습니다.
그럼에도 아키텍처를 설계하는 습관은 쉽게 바뀌지 않습니다.
많은 조직은 원래의 문제가 사라진 뒤에도 복잡성을 유지하는 비용을 계속 지불하고 있습니다.
마이크로서비스와 모놀리식 아키텍처도 같은 교훈을 줍니다
DuckDB의 이야기는 또 다른 흐름과도 닮아 있습니다.
한동안 많은 팀은 시스템을 수십 개의 서비스로 분리했습니다.
- User Service
- Order Service
- Inventory Service
- Billing Service
- Notification Service
- Analytics Service
그러다 결국 다음과 같은 사실을 깨달았습니다.
대부분의 통신은 내부 시스템끼리 이루어지고 있었습니다.
대부분의 복잡성은 스스로 만들어낸 것이었습니다.
그래서 많은 조직이 다시 모듈형 모놀리식(Modular Monolith) 아키텍처로 돌아가고 있습니다.
┌─────────────────────────┐
│ Modular Monolith │
├─────────────────────────┤
│ Orders Module │
│ Billing Module │
│ Inventory Module │
│ Analytics Module │
└─────────────────────────┘
목표는 서비스를 줄이는 것이 아닙니다.
불필요한 의사결정을 줄이는 것입니다.
개발자의 생산성은 인프라보다 훨씬 빠르게 확장되는 경우가 많습니다.
백그라운드 작업은 여전히 중요합니다
분석 작업은 사용자 요청을 직접 지연시키는 방식으로 실행되어서는 안 됩니다.
좋지 않은 구현은 다음과 같습니다.
@app.post("/generate-report")
async def generate_report():
run_heavy_report()
return {"done": True}
더 나은 방법은 작업을 큐에 등록하는 것입니다.
@app.post("/generate-report")
async def generate_report():
task_id = queue.enqueue(
"generate_report"
)
return {"task_id": task_id}
그리고 워커가 이를 처리합니다.
@consumer.task
async def generate_report():
data = duckdb.sql("""
SELECT *
FROM reports.parquet
""").df()
export_report(data)
비동기 워크플로는 시스템을 확장하는 가장 쉽고 효과적인 방법 가운데 하나입니다.
속도 최적화보다 Rate Limiting이 더 많은 시스템을 살립니다
놀랍게도 많은 데이터베이스 성능 문제는 규모 때문이 아니라 과도한 요청 때문에 발생합니다.
limiter = Limiter(
key_func=get_remote_address
)
@app.get("/analytics")
@limiter.limit("50/minute")
async def analytics():
...
가장 뛰어난 성능 최적화는 잘못된 트래픽이 시스템에 들어오지 못하도록 막는 것일 때도 있습니다.
언제 이 조언이 맞지 않을까?
DuckDB는 모든 문제를 해결하는 만능 도구가 아닙니다.
다음과 같은 경우에는 PostgreSQL을 DuckDB로 대체해서는 안 됩니다.
- 동시성이 높은 쓰기 작업
- 금융 거래
- 운영(Operational) 워크로드
- 사용자가 직접 사용하는 애플리케이션
- 복잡한 트랜잭션 보장
- 반대로 PostgreSQL은 다음과 같은 작업에서 점점 부담이 커집니다.
- 수십억 건의 레코드 스캔
- 대규모 집계 분석
- 대용량 Parquet 데이터셋 조회
- 탐색적 데이터 분석(EDA)
서로 다른 워크로드에는 서로 다른 도구가 필요합니다.
좋은 엔지니어링은 그 차이를 이해하는 것에서 시작됩니다.
오늘날 뛰어난 팀들은 실제로 어떻게 구성하고 있을까?
점점 더 많은 조직이 다음과 같은 구조를 채택하고 있습니다.
운영 계층(Operational Layer)
FastAPI
PostgreSQL
Redis
RabbitMQ 또는 Kafka
분석 계층(Analytical Layer)
Parquet
DuckDB
Python
데이터 노트북(Jupyter 등)
관측 가능성(Observability)
OpenTelemetry
Prometheus
Grafana
배포(Deployment)
Docker
Kubernetes(정말 필요한 경우에만)
이 구성이 선택되는 이유는 유행하기 때문이 아닙니다.
마찰(Friction)을 최소화하기 때문입니다.
그리고 작은 마찰은 시간이 지날수록 점점 더 큰 비용으로 쌓이게 됩니다.
진짜 교훈
DuckDB와 PostgreSQL의 논쟁은 잘못된 방식으로 이해되고 있습니다.
이 둘은 경쟁 관계가 아닙니다.
역할을 분리하는 관계입니다.
PostgreSQL은 지금도 최고의 트랜잭션 데이터베이스 가운데 하나입니다.
DuckDB는 분석 시스템이 어디까지 가능할 수 있는지를 새롭게 정의하고 있습니다.
정말 놀라운 사실은 DuckDB가 빠르다는 것이 아닙니다.
과거에는 분석 실행이 느렸기 때문에 필요했던 수많은 인프라가 이제는 더 이상 필요하지 않게 되었다는 점입니다.
그 전제가 더 이상 사실이 아닙니다.
그리고 전제가 바뀌면 아키텍처도 바뀝니다.
차세대 데이터 플랫폼은 더 큰 클러스터로 정의되지 않을지도 모릅니다.
오히려 훨씬 적은 수의 클러스터만으로 충분한 플랫폼이 될지도 모릅니다.
바로 이것이, 아무도 예상하지 못했던 분석 혁명입니다.
'EPL과 유튜브 데이터로 배우는 DuckDB' 카테고리의 다른 글
| Spark 클러스트를 대체하는 DuckDB: 비용 70%를 절감하다 (0) | 2026.06.28 |
|---|---|
| DuckDB : 왜 모든 데이터 엔지니어가 갑자기 DuckDB를 이야기하는가? (0) | 2026.06.25 |
| Pandas, Polars, DuckDB로 테스트 데이터 생성하기 (0) | 2026.06.22 |
| DuckDB: 2026년 데이터 분석가를 위한 가장 과소 평가된 도구 (0) | 2026.06.21 |
| DuckDB + Python: SQL로 CSV 파일 다루기 part 1 (0) | 2026.06.20 |
댓글