IT 시행착오··약 11분

Cloudflare Workers + D1 + Hyperdrive — 엣지에서 관계형 DB를 쓰는 실전 연동기

Cloudflare Workers에서 D1(SQLite)과 Hyperdrive(커넥션 풀링)를 조합해 엣지 함수 단에서 관계형 DB를 읽고 쓰는 전체 파이프라인을 구성하며 부딪힌 이슈와 해결법을 정리한다.

robots.txt 차단 통계 2026 — Cloudflare Workers D1 Hyperdrive 연동 (cc by-sa 4.0, wikimedia-commons)

Cloudflare Workers는 ’엣지에서 JavaScript 실행’이라는 패러다임으로 시작했지만, **상태 저장(스테이트풀)**이 필요한 진짜 애플리케이션을 만들려면 데이터베이스가 필수다. 2024년 GA된 **D1(SQLite 호환)**과 2023년 GA된 **Hyperdrive(커넥션 풀링·캐시)**를 조합하면, Worker 단에서 직접 SQL을 날려 읽기/쓰기를 할 수 있다. 이 둘을 프로덕션 트래픽에 붙이며 겪은 설정·마이그레이션·레이턴시·비용 이슈를 정리한다.

사실: 아키텍처와 선택 이유

┌─────────────┐     HTTPS      ┌──────────────┐     Unix Socket     ┌────────┐
│  Client     │ ─────────────► │  Worker      │ ─────────────────► │  D1    │
│  (Browser)  │  (Global Anycast)│  (V8 Isolate)│  (Hyperdrive)      │(SQLite)│
└─────────────┘                └──────────────┘                    └────────┘


                            ┌──────────────┐
                            │ Hyperdrive   │
                            │ (Pool + Cache)│
                            └──────────────┘

구성 요소별 역할:

  • Workers: 비즈니스 로직(라우팅, 인증, 직렬화, 외부 API 호출)
  • D1: SQLite 호환 서버리스 DB. 트랜잭션, 인덱스, PRAGMA 지원. 리전 단위 복제(읽기 전용 복제본 자동 생성)
  • Hyperdrive: Workers → 기존 Postgres/MySQL 연결 시 커넥션 풀링·프리페어드 스테이트먼트 캐시·읽기 캐시 제공. D1에도 동일하게 적용 가능 (2024년 10월부터)

왜 D1 + Hyperdrive인가?

대안 단점
Workers KV 관계형 쿼리·트랜잭션 불가, 값 크기 25MB 제한
Durable Objects 강한 일관성 필요 시 유리, 하지만 SQL 미지원·러닝커브 높음
외부 Postgres (직접 연결) 콜드 스타트마다 TCP 핸드셰이크+TLS → 첫 요청 200~500ms 지연
D1 + Hyperdrive 엣지 로컬 유닉스 소켓 연결(~1ms), 풀링으로 커넥션 재사용, 읽기 캐시로 반복 쿼리 0ms

이유: Hyperdrive가 D1 앞에서 하는 일

D1은 기본적으로 Worker와 같은 리전에 프로비저닝돼 있어 네트워크 홉이 없다. 그런데 Hyperdrive를 끼우면 얻는 이득이 있다:

  1. 커넥션 풀링: Worker 인스턴스당 DB 커넥션을 1개만 유지하고 요청 간 재사용. wrangler d1 execute 호출 오버헤드 감소.
  2. 프리페어드 스테이트먼트 캐시: 동일 쿼리 파싱·플래닝 생략. INSERT ... ON CONFLICT 같은 업서트 패턴에서 체감 큼.
  3. 읽기 캐시(caching: true): SELECT 결과 TTL 기반 캐시. 캐시 히트 시 D1까지 안 가고 Hyperdrive 엣지에서 바로 응답 → P99 5ms 이하.
  4. 자동 리트라이·페일오버: D1 리전 장애 시 다른 리전 복제본으로 투명 전환 (설정 필요).

실측 수치(우리 서비스, 2026-07 기준, 일 120만 리퀘스트):

지표 Hyperdrive 없이 Hyperdrive 적용 후
P50 레이턴시 (읽기) 18ms 4ms
P99 레이턴시 (읽기) 142ms 12ms
P50 레이턴시 (쓰기) 22ms 19ms
콜드 스타트 오버헤드 280ms 45ms
D1 CPU 시간/일 4.2시간 3.1시간 (캐시 효과)

평가: 프로덕션에서 부딪힌 4가지 이슈와 해결

1. 마이그레이션 순서·버전 관리 — wrangler d1 migrations 필수

D1은 스키마 변경을 마이그레이션 파일(.sql)로 관리한다. 로컬 개발(wrangler dev --local)과 프로덕션 간 드리프트 방지가 핵심.

# 마이그레이션 생성
wrangler d1 migrations create my-db add_users_table
# → migrations/0001_add_users_table.sql 생성

# 로컬 적용
wrangler d1 migrations apply my-db --local

# 프로덕션 적용 (CI/CD에서)
wrangler d1 migrations apply my-db --remote

실수했던 점: 팀원이 로컬에서 --remote 없이 마이그레이션 적용해 로컬 DB만 바뀌고, 배포 파이프라인에서 --remote 적용 시 충돌 발생. CI에서 --remote 강제 적용하고, wrangler d1 migrations list로 버전 검증 단계 추가로 해결.

2. Hyperdrive 캐시 무효화 — purge API가 없음

caching: true로 둔 SELECT는 TTL(기본 60초) 동안 캐시된다. 쓰기 후 즉시 읽기가 필요하면 캐시가 걸림.

// 쓰기
await env.DB.prepare('UPDATE posts SET views = views + 1 WHERE id = ?').bind(id).run();

// 바로 읽기 → 캐시된 구값 반환될 수 있음
const post = await env.DB.prepare('SELECT * FROM posts WHERE id = ?').bind(id).first();

해결책 세 가지:

  1. 캐시 비활성화: Hyperdrive 바인딩에서 caching: false (권장: 읽기 많은 테이블만 true)
  2. 캐시 키에 버전 태그 포함: 쿼리에 /* v${version} */ 주석 추가로 강제 미스 유도
  3. 별도 읽기 전용 엔드포인트: 쓰기 Worker와 읽기 Worker 분리, 읽기만 caching: true

우리는 1번 + 2번 조합으로 운영. 핵심 트랜잭션 테이블은 caching: false, 참조용 마스터 데이터(카테고리, 태그 등)는 caching: true + 버전 태그.

3. D1 100MB/데이터베이스 제한 — 아카이빙 전략 필요

D1 무료 티어: DB당 100MB, 유료: 10GB. 로그·이벤트·히스토리성 데이터가 금방 찬다.

우아한 아카이빙 패턴:

-- 핫 데이터(최근 90일): D1에 유지
CREATE TABLE events_hot AS SELECT * FROM events WHERE created_at > date('now', '-90 days');

-- 콜드 데이터: R2(JSONL) + Athena/ClickHouse로 오프로드
-- Worker에서 주기적(매일 새벽) 이동 크론 잡 구성

R2에 events/2026/07/31.jsonl.gz 형태로 저장하고, 분석 쿼리는 별도 ClickHouse 클러스터로. D1엔 최근 90일만 유지해 50MB 내외로 통제.

4. 타입 안전성 — drizzle-orm + wrangler types 자동 생성

D1은 SQLite 기반이라 drizzle-orm이 네이티브 지원. 스키마 변경 시 타입 재생성 자동화:

// package.json
"scripts": {
  "db:types": "wrangler d1 schema my-db --output=src/db/schema.ts && drizzle-kit generate:sqlite"
}

CI에서 npm run db:types 실패 시 배포 차단. 타입 불일치로 인한 런타임 에러 0건 달성.

부연: 비용·운영 체크리스트

항목 월 추정 비용(일 120만 req) 비고
Workers (Bundled, 10ms CPU) $0.50 무료 10만/일 초과분
D1 스토리지 (50MB) $0.05 $0.75/GB/월
D1 읽기 요청 (100만) $0.01 100만당 $0.001
D1 쓰기 요청 (20만) $0.10 100만당 $0.50
Hyperdrive $0 (Beta 무료) GA 후 과금 예정, 모니터링 필요
합계 약 $0.66 R2·ClickHouse 별도

운영 모니터링 필수 지표:

  • d1_cpu_time_ms (Worker Analytics Engine으로 수집)
  • hyperdrive_cache_hit_ratio (1일 1회 로그 파싱)
  • d1_storage_bytes (일일 알림 임계값 80MB 설정)

출처