302 lines
7.3 KiB
Markdown
302 lines
7.3 KiB
Markdown
---
|
|
id: db-serverless-edge
|
|
title: Serverless / Edge DB — Neon / Turso / D1 / Hyperdrive
|
|
category: Coding
|
|
status: draft
|
|
source_trust_level: B
|
|
verification_status: conceptual
|
|
created_at: 2026-05-09
|
|
updated_at: 2026-05-09
|
|
tags: [database, serverless, edge, vibe-coding]
|
|
tech_stack: { language: "TS", applicable_to: ["Backend"] }
|
|
applied_in: []
|
|
aliases: [Neon, Turso, Cloudflare D1, Hyperdrive, libSQL, edge SQLite, branching DB]
|
|
---
|
|
|
|
# Serverless / Edge DB
|
|
|
|
> Lambda / Edge function = connection pool 어려움. **Neon (Postgres HTTP), Turso (libSQL), Cloudflare D1, Hyperdrive (PG proxy)**. Branching, scale-to-zero, low latency.
|
|
|
|
## 📖 핵심 개념
|
|
- HTTP-based: connection 없음 — REST 같이.
|
|
- Branching: production data → dev branch.
|
|
- Scale-to-zero: 안 쓰면 stop.
|
|
- Edge: 사용자 가까이.
|
|
|
|
## 💻 코드 패턴
|
|
|
|
### Neon (Postgres serverless)
|
|
```ts
|
|
import { neon } from '@neondatabase/serverless';
|
|
|
|
const sql = neon(process.env.DATABASE_URL!);
|
|
|
|
// HTTP API — connection 없음
|
|
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
|
|
const user = users[0];
|
|
```
|
|
|
|
```ts
|
|
// 또는 Pool (Edge runtime)
|
|
import { Pool } from '@neondatabase/serverless';
|
|
|
|
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
const r = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
|
|
```
|
|
|
|
→ Standard Postgres + HTTP transport.
|
|
|
|
### Neon branching (development)
|
|
```bash
|
|
# Branch 생성
|
|
neon branches create --name dev-feature-x --parent main
|
|
|
|
# Schema migration test
|
|
DATABASE_URL=$(neon connection-string dev-feature-x) yarn migrate:up
|
|
|
|
# Production 영향 없음
|
|
# Done → branch delete
|
|
```
|
|
|
|
→ Git-like database.
|
|
|
|
### Turso (libSQL = SQLite fork)
|
|
```ts
|
|
import { createClient } from '@libsql/client';
|
|
|
|
const turso = createClient({
|
|
url: 'libsql://my-db.turso.io',
|
|
authToken: process.env.TURSO_TOKEN,
|
|
});
|
|
|
|
const r = await turso.execute({
|
|
sql: 'SELECT * FROM users WHERE id = ?',
|
|
args: [userId],
|
|
});
|
|
|
|
console.log(r.rows);
|
|
```
|
|
|
|
→ SQLite + replication + edge.
|
|
|
|
### Turso embedded replica (zero-latency read)
|
|
```ts
|
|
const turso = createClient({
|
|
url: 'file:local.db',
|
|
syncUrl: 'libsql://my-db.turso.io',
|
|
authToken,
|
|
syncInterval: 60, // 60s sync
|
|
});
|
|
|
|
await turso.sync();
|
|
|
|
// Read = local file (0 ms)
|
|
const r = await turso.execute('SELECT * FROM users');
|
|
```
|
|
|
|
→ Read = local, write = remote, 자동 sync.
|
|
|
|
### Cloudflare D1
|
|
```ts
|
|
// wrangler.toml
|
|
[[d1_databases]]
|
|
binding = "DB"
|
|
database_name = "my-app"
|
|
database_id = "..."
|
|
```
|
|
|
|
```ts
|
|
// Worker
|
|
export default {
|
|
async fetch(req: Request, env: Env) {
|
|
const r = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();
|
|
return Response.json(r);
|
|
},
|
|
};
|
|
```
|
|
|
|
→ SQLite + 글로벌 read replica.
|
|
|
|
### Hyperdrive (CF, Postgres / MySQL accelerator)
|
|
```ts
|
|
// wrangler.toml
|
|
[[hyperdrive]]
|
|
binding = "HYPERDRIVE"
|
|
id = "..." # PG / MySQL connection
|
|
```
|
|
|
|
```ts
|
|
import postgres from 'postgres';
|
|
|
|
export default {
|
|
async fetch(req: Request, env: Env) {
|
|
const sql = postgres(env.HYPERDRIVE.connectionString);
|
|
const r = await sql`SELECT * FROM users WHERE id = ${id}`;
|
|
return Response.json(r);
|
|
},
|
|
};
|
|
```
|
|
|
|
→ Hyperdrive 가 connection pool + cache. CF Worker 안 일반 PG client.
|
|
|
|
### PlanetScale (MySQL serverless)
|
|
```ts
|
|
import { Client } from '@planetscale/database';
|
|
|
|
const client = new Client({
|
|
url: process.env.DATABASE_URL,
|
|
});
|
|
const conn = client.connection();
|
|
|
|
const r = await conn.execute('SELECT * FROM users WHERE id = ?', [id]);
|
|
```
|
|
|
|
→ MySQL HTTP. Branching 같이.
|
|
|
|
### Branching workflow (Neon / PlanetScale)
|
|
```yaml
|
|
# .github/workflows/preview.yml
|
|
- name: Create branch for PR
|
|
run: neon branches create --name pr-${{ github.event.number }} --parent main
|
|
|
|
- name: Run migrations
|
|
run: DATABASE_URL=$BRANCH_URL yarn migrate:up
|
|
|
|
- name: Deploy preview
|
|
run: vercel deploy --env DATABASE_URL=$BRANCH_URL
|
|
|
|
- name: On PR close — delete
|
|
run: neon branches delete pr-${{ github.event.number }}
|
|
```
|
|
|
|
→ 매 PR = 자체 DB.
|
|
|
|
### Scale-to-zero
|
|
```
|
|
Neon: 안 쓰면 compute stop. 다음 query 가 cold start (~500ms).
|
|
Turso: 항상 활성 (작은 비용).
|
|
D1: 활성.
|
|
Hyperdrive: pool + cache.
|
|
```
|
|
|
|
→ Low-traffic 앱 = Neon 가 cheap.
|
|
|
|
### Cost (대략)
|
|
```
|
|
Neon: Free tier — 0.5GB / 1 project. Paid $19/month.
|
|
Turso: Free 9GB / 500 DBs. Paid scaled.
|
|
D1: Free 5GB / 25M reads/day. Paid pennies/M.
|
|
Hyperdrive: CF Workers paid.
|
|
PlanetScale: Free tier — but 2024 가격 변경.
|
|
```
|
|
|
|
### 사용자 관점 latency
|
|
```
|
|
일반 RDS (us-east-1) + Lambda (us-east-1): ~5-20ms query
|
|
Neon HTTP + Lambda: ~10-30ms
|
|
Turso embedded replica: ~0ms read
|
|
D1 (CF worker, edge): ~1-5ms (local region)
|
|
Hyperdrive (CF worker → cached): ~1-5ms cached
|
|
```
|
|
|
|
### Drizzle 통합
|
|
```ts
|
|
import { drizzle } from 'drizzle-orm/neon-http';
|
|
import { neon } from '@neondatabase/serverless';
|
|
|
|
const sql = neon(process.env.DATABASE_URL!);
|
|
const db = drizzle(sql);
|
|
|
|
const users = await db.select().from(usersTable).where(eq(usersTable.id, id));
|
|
```
|
|
|
|
```ts
|
|
// Turso
|
|
import { drizzle } from 'drizzle-orm/libsql';
|
|
import { createClient } from '@libsql/client';
|
|
|
|
const client = createClient({ url, authToken });
|
|
const db = drizzle(client);
|
|
```
|
|
|
|
### Prisma (구식 — Edge 어려움)
|
|
```ts
|
|
// Prisma 의 Data Proxy / Accelerate 필요
|
|
// 또는 driver adapter (Neon)
|
|
import { PrismaClient } from '@prisma/client';
|
|
import { PrismaNeon } from '@prisma/adapter-neon';
|
|
import { neon } from '@neondatabase/serverless';
|
|
|
|
const sql = neon(process.env.DATABASE_URL!);
|
|
const adapter = new PrismaNeon(sql);
|
|
const prisma = new PrismaClient({ adapter });
|
|
```
|
|
|
|
### Vector search (Neon pgvector / Turso SQLite vss)
|
|
```sql
|
|
-- Neon = pgvector 그대로
|
|
CREATE EXTENSION vector;
|
|
CREATE TABLE docs (... embedding VECTOR(1536));
|
|
|
|
-- Turso = sqlite-vss extension
|
|
```
|
|
|
|
### Migration tools
|
|
```bash
|
|
# Neon — branch 로
|
|
neon branches create --name migration-test
|
|
DATABASE_URL=$BRANCH yarn migrate:up
|
|
# Verify
|
|
neon branches delete migration-test
|
|
|
|
# 또는 atlas / drizzle-kit / prisma migrate
|
|
```
|
|
|
|
### 동시성 / write
|
|
```
|
|
Neon: 읽기 다중 (read replica) / write 단일.
|
|
Turso: read 다중 / write 한 곳 (primary).
|
|
D1: write 한 region / read 글로벌.
|
|
|
|
→ 분산 write = 다른 system 필요.
|
|
```
|
|
|
|
### 단점
|
|
```
|
|
Neon: PG 호환 — but 일부 extension 제약.
|
|
Turso: SQLite 가 OLTP 만. analytic X.
|
|
D1: SQLite 같음 + 일부 extension X.
|
|
Hyperdrive: 자체 DB X — 기존 PG 가까이.
|
|
PlanetScale: FK 제약 (online schema change 위해).
|
|
```
|
|
|
|
## 🤔 의사결정 기준
|
|
| 상황 | 추천 |
|
|
|---|---|
|
|
| Vercel + 새 프로젝트 | Neon |
|
|
| Cloudflare Workers | D1 / Hyperdrive |
|
|
| 글로벌 low-latency read | Turso embedded |
|
|
| Postgres 기존 | Hyperdrive |
|
|
| MySQL | PlanetScale |
|
|
| 큰 OLTP | RDS / Aurora (전통) |
|
|
| Analytic | DuckDB / ClickHouse |
|
|
|
|
## ❌ 안티패턴
|
|
- **Lambda + 일반 PG 직접**: connection 폭발. HTTP / Hyperdrive.
|
|
- **Turso 가 analytic 가정**: SQLite. DuckDB.
|
|
- **Branch 생성 + 자동 delete X**: 비용.
|
|
- **Cold start 무관 prod**: latency. Pool / always-on.
|
|
- **Edge + complex JOIN**: cross-region 비싸.
|
|
- **Prepared statement cache 무**: 매번 parse.
|
|
|
|
## 🤖 LLM 활용 힌트
|
|
- Vercel / Next = Neon 디폴트.
|
|
- CF Workers = D1 / Hyperdrive.
|
|
- Local-first = Turso embedded.
|
|
- Branching = git-like dev.
|
|
|
|
## 🔗 관련 문서
|
|
- [[Backend_Connection_Handling]]
|
|
- [[Backend_Geo_Replication]]
|
|
- [[DB_Distributed_SQL]]
|