리옵트 데이터 문서
플랫폼과 개발

분석 백엔드 선택

ClickHouse와 MotherDuck 중 분석 백엔드를 선택하고, 로컬/운영 환경변수를 설정합니다.

리옵트 데이터의 분석 라우터는 AnalyticsBackend 추상화 뒤에서 동작합니다. 운영 환경에 맞게 ANALYTICS_BACKEND를 설정하면 ClickHouse 또는 MotherDuck 중 하나를 사용할 수 있어요.

설명
motherduck (기본값)MotherDuck cloud 또는 로컬 DuckDB
clickhouse셀프 호스팅 ClickHouse를 사용하는 선택형 백엔드

먼저 선택하세요

ClickHouse를 선택하면 기존 운영 환경과 ClickHouse 전용 SQL을 그대로 유지하기 쉽습니다. MotherDuck을 선택하면 Vercel 중심 배포에서 분석 백엔드 운영 부담을 줄일 수 있습니다.

상황추천
이미 ClickHouse를 운영하고 있고 저장 쿼리가 ClickHouse 방언에 맞춰져 있음clickhouse
대량 트래픽과 ClickHouse 전용 함수, Materialized View를 적극 사용함clickhouse
초기 트래픽이 적고 관리형 분석 백엔드가 필요함motherduck
Vercel 단독 배포에서 외부 관리형 저장소를 선호함motherduck

ClickHouse를 선택하는 경우

  • 기존 운영 환경 그대로 두고 싶다 (이전 컬럼/스키마/MV가 그대로 동작)
  • 대량 트래픽이 예상되며 windowFunnel, argMax, AggregatingMergeTree 같은 ClickHouse-specific 기능을 활용한 SQL을 작성 중이다
  • SQL 탐색기(query.execute)에 저장한 SQL이 ClickHouse 방언 위주 (FINAL, dateDiff, JSONExtractString, argMax 등) — MotherDuck도 탐색기를 지원하지만 DuckDB 방언으로 작성해야 하므로 마이그레이션 부담
  • 셀프 호스트 인프라(Docker, K8s 등)를 운영할 수 있다

MotherDuck를 선택하는 경우

  • Vercel 단독 배포에서 분석 백엔드까지 외부 관리형으로 두고 싶다
  • 초기 트래픽이 적고 MotherDuck Lite 무료 한도(10GB / 10h 컴퓨트)로 충분하다
  • ClickHouse 인프라 운영 부담을 피하고 싶다

환경변수

# 공통
ANALYTICS_BACKEND=motherduck       # 기본값, 필요하면 "clickhouse"로 변경

# MotherDuck 클라우드 사용 시
MOTHERDUCK_TOKEN=<MotherDuck Service Token>
MOTHERDUCK_DATABASE=<database name>

# 또는: 로컬 DuckDB 파일/메모리 모드 (offline)
MOTHERDUCK_LOCAL_PATH=./docker/data/analytics.duckdb   # 파일 경로
# MOTHERDUCK_LOCAL_PATH=:memory:                       # in-memory

# DuckDB 리소스 제한 (로컬 네이티브 모드에서만 적용)
DUCKDB_MEMORY_LIMIT=512MB          # 기본값 512MB
DUCKDB_THREADS=4                   # 기본값 4

# ClickHouse 사용 시 (기존)
CLICKHOUSE_URL=https://...
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=...

MOTHERDUCK_LOCAL_PATH가 설정되어 있으면 cloud token보다 우선합니다. 이 모드는 로컬 개발/검증용이며 production 환경 검증에서는 허용하지 않습니다. 개발 환경에서 cloud credential을 제거하지 않고도 로컬 파일로 고정할 수 있어요. 상대 경로는 워크스페이스 루트 기준으로 해석됩니다.

Vercel에 배포할 때는 두 백엔드 변수 모두 Production / Preview / Development 환경에 동일하게 주입해야 합니다 (Edge functions 포함). 로컬 파일 모드는 배포에는 사용하지 않습니다.

로컬 DuckDB 모드 (offline)

MOTHERDUCK_LOCAL_PATH를 사용하면 MotherDuck cloud 계정 없이도 어댑터 SQL을 검증할 수 있습니다. parity 테스트나 CI에서 클라우드 컴퓨트 비용 없이 두 백엔드 동등성을 확인할 때 유용합니다.

# 1. 분석 백엔드를 motherduck로 지정 + 로컬 파일 경로
export ANALYTICS_BACKEND=motherduck
export MOTHERDUCK_LOCAL_PATH=./docker/data/analytics.duckdb

# 2. 스키마 부트스트랩 (한 번)
pnpm --filter @reopt/db db:motherduck:init

# 3. 평소처럼 dev (PG + Redis만 필요, ClickHouse 컨테이너 안 띄워도 됨)
docker compose up -d reopt-db reopt-redis
pnpm --filter @reopt/db db:push
pnpm dev

한계

  • 한 시점에 한 프로세스만 DuckDB 파일에 write 가능 — DuckDB는 file-lock multi-writer를 지원하지 않습니다. 분리 서비스 모드(dev:full)는 모든 서비스가 같은 파일에 쓰려고 해서 충돌할 수 있으니, 로컬 DuckDB 모드는 주로 pnpm dev 단일 프로세스 시나리오 또는 테스트용으로 사용하세요.
  • :memory: 모드는 프로세스 종료 시 데이터가 사라집니다.

백엔드별 동작 차이

대부분의 라우터는 두 백엔드에서 동일한 결과를 반환합니다. 두 가지 미세한 차이가 있습니다.

1. 즉석 SQL 실행 (query.execute) — 백엔드별 방언

query.execute는 사용자가 입력한 SQL을 직접 실행합니다. 라우터가 backend.kind를 보고 두 방언 중 하나로 스코핑합니다:

실행 전에는 읽기 전용 쿼리인지 검증하고, 현재 프로젝트 데이터만 조회하도록 project_id 조건을 강제합니다. INSERT, UPDATE, DELETE, DDL은 실행할 수 없습니다.

  • ClickHouse 백엔드: 기존 그대로 {projectId:String} named placeholder
    • FINAL 유지. dateDiff, JSONExtractString, argMax 등 ClickHouse 함수를 사용할 수 있습니다.
  • MotherDuck 백엔드: 입력 SQL에서 FINAL은 자동 제거, project_id는 adapter가 escape된 literal로 치환. DuckDB 표준 SQL을 작성해야 합니다 (date_diff, json_extract_string, arg_max 등).

즉, 같은 입력 SQL이 두 백엔드에서 동일하게 동작하지 않습니다. 백엔드를 바꾸면 SQL 탐색기에 저장된 쿼리도 함께 점검해야 합니다. 라우터 응답 shape(columns/rows/rowCount/elapsed)은 동일합니다.

두 백엔드 공통으로 결과는 최대 10,000행으로 제한되며, MotherDuck 백엔드에서는 30초 타임아웃이 적용됩니다.

2. funnel.funnelTrend의 bucket key (edge case)

funnelTrend는 device의 first_event 시각으로 기간 버킷을 결정합니다. 두 백엔드 모두 "funnel에 포함된 step 이벤트들 중 가장 이른 시각"을 바스킷 키로 씁니다. 따라서 device가 step2를 step1보다 먼저 발생시킨 edge case에서 양쪽 모두 step2의 시각으로 버킷팅합니다(funnel level은 여전히 step1→stepN 순서를 요구함).

MotherDuck로 전환하기

기존 ClickHouse 운영 환경에서 MotherDuck로 옮기려면:

  1. MotherDuck 스키마 생성

    MOTHERDUCK_TOKEN=... MOTHERDUCK_DATABASE=... \
      pnpm --filter @reopt/db db:motherduck:init
  2. 데이터 백필 (프로젝트 단위)

    MOTHERDUCK_TOKEN=... MOTHERDUCK_DATABASE=... \
      CLICKHOUSE_URL=... CLICKHOUSE_USER=... CLICKHOUSE_PASSWORD=... \
      pnpm --filter @reopt/db db:motherduck:backfill-from-clickhouse \
        --project-id <projectId> [--since 2026-01-01]

    이벤트 / 프로필 / 세션 / device_first_seen / daily_active_devices를 모두 옮깁니다. --since로 증분 백필도 가능합니다.

  3. 트래픽 전환

    Vercel 환경변수에서 ANALYTICS_BACKEND=motherduck로 바꾸고 재배포합니다. ingest 파이프라인이 즉시 MotherDuck에 쓰기 시작합니다.

  4. 벤치마크로 응답 시간 비교 (권장)

    ANALYTICS_BACKEND=clickhouse pnpm --filter @reopt/trpc bench:retention
    ANALYTICS_BACKEND=motherduck MOTHERDUCK_TOKEN=... MOTHERDUCK_DATABASE=... \
      pnpm --filter @reopt/trpc bench:retention

연결과 성능

MotherDuck 어댑터는 환경에 따라 네이티브 @duckdb/node-api(로컬) 또는 pg.Pool(MotherDuck Postgres wire endpoint, 프로덕션/Vercel)을 자동으로 선택합니다. 두 모드 모두 같은 쿼리 코드가 동작하며, 연결이 실패하면 자동으로 재시도합니다.

  • 벌크 삽입: insertEvents()는 500행 단위 multi-row VALUES INSERT로 배칭합니다.
  • 리소스 제한: 네이티브 모드에서 DUCKDB_MEMORY_LIMIT(기본 512MB)과 DUCKDB_THREADS(기본 4)를 적용합니다. 잘못된 값은 무시되고 DuckDB 기본값을 사용합니다.
  • 스키마 관리: DDL은 packages/db/src/analytics/duck-schema.ts에 한 번만 정의되어 있고, CLI 부트스트랩(db:motherduck:init)과 런타임 첫 연결 시 모두 이 파일을 참조합니다.

주의할 점

  • MotherDuck Lite 플랜은 10GB storage / 10시간 컴퓨트/월 한도가 있습니다.
  • 운영 트래픽이 한도를 초과하면 분석 라우터가 실패할 수 있습니다.
  • 저장된 SQL은 백엔드를 바꿀 때 함께 마이그레이션해야 합니다.
  • MOTHERDUCK_LOCAL_PATH=:memory:는 프로세스 종료 시 데이터가 사라집니다. production에서는 MOTHERDUCK_TOKENMOTHERDUCK_DATABASE를 사용하세요.
  • 로컬 DuckDB 파일은 한 번에 한 프로세스만 write할 수 있습니다.

다음 단계