본문 바로가기
WIKI 기술 지식 베이스

Vercel

원문 보기 위키 갱신

Vercel 서버리스와 엣지 함수에서 Turso를 사용하는 방법이에요. HTTP로 SQL을 날리는 방식과 함수 안에 SQLite를 넣는 방식, 두 가지 접근이 있어요.

출처: 문서

본문

Turso는 Vercel 서버리스와 엣지 함수에서 데이터베이스에 접근하는 두 가지 방식을 제공해요:

  • HTTP를 통한 SQL(SQL over HTTP) — 모든 쿼리가 네트워크를 통해 데이터베이스로 전달돼요. 설정이 간단하고 파일시스템 접근 없이도 엣지 함수를 포함한 어떤 런타임에서든 동작해요. 다른 데이터베이스에서 이미 익숙한 모델이에요.

  • 함수 내 SQLite(In-Function SQLite) — 데이터베이스 페이지가 서버리스 함수 안으로 복제돼요. 읽기는 네트워크 왕복 없이 로컬에서 실행되고, 쓰기는 내구성을 위해 원격 데이터베이스로 바로 전달돼요. Turso의 임베디드 복제본이나 Cloudflare의 SQLite 탑재 Durable Objects를 써 본 적이 있다면 낯익은 모델일 거예요. 심층 분석은 Bringing SQLite to Vercel Functions를 참고하세요.

SQL over HTTP In-Function SQLite
Packages @tursodatabase/serverless, @libsql/client @tursodatabase/vercel-experimental
Best for 쓰기가 많은 워크로드, 파일시스템이 없는 엣지 런타임 읽기가 많은 워크로드, 테넌트별 데이터베이스
Read latency 쿼리마다 네트워크 왕복 프로세스 내 SQLite 읽기(네트워크 홉 0)
Write latency 네트워크 왕복 네트워크 왕복(동일)
Multi-tenant 공유 데이터베이스 자동 온디맨드 프로비저닝을 갖춘 테넌트별 데이터베이스
Runtime requirements fetch API만 필요 휘발성 파일시스템(/tmp)
ORM support Drizzle, Prisma 등(@libsql/client) 아직 미지원

SQL over HTTP

Vercel 서버리스와 엣지 환경에서 Turso 데이터베이스에 연결해요. Vercel 환경에 다음 변수들을 설정하세요:

  • TURSO_DATABASE_URL — Turso 데이터베이스 URL
  • TURSO_AUTH_TOKEN — Turso 인증 토큰
@tursodatabase/serverless @libsql/client
Use with Turso 데이터베이스 libSQL 데이터베이스
Dependencies fetch만 사용 — 네이티브 의존성 0 Node.js 또는 /web 서브패스 필요
Concurrent writes 지원 — Turso 엔진에 내장 미지원
ORM support 아직 미지원 Drizzle, Prisma 등

데이터베이스 엔진에 맞는 드라이버를 고르세요: Turso 데이터베이스에는 @tursodatabase/serverless, libSQL 데이터베이스와 ORM 연동(Drizzle, Prisma)에는 @libsql/client.

퀵스타트 (Quickstart)

@tursodatabase/serverless

@tursodatabase/serverless 패키지는 fetch API만으로 Turso에 연결해요 — 네이티브 의존성이 없어서 엣지와 서버리스 런타임에서 가장 가벼운 선택지예요.

npm install @tursodatabase/serverless
import { connect } from "@tursodatabase/serverless";

const conn = connect({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

const stmt = await conn.prepare("SELECT * FROM users WHERE id = ?");
const row = await stmt.get([123]);

libSQL 클라이언트 API와의 호환이 필요하면 compat 모듈을 사용하세요:

import { createClient } from "@tursodatabase/serverless/compat";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]);

@libsql/client

@libsql/client 패키지는 Turso 생태계 전반에서 쓰이는 프로덕션 준비 libSQL 드라이버예요. Vercel 서버리스와 엣지 런타임에서는 /web 임포트를 사용하세요:

npm install @libsql/client
import { createClient } from "@libsql/client/web";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
});

const result = await client.execute({
  sql: "SELECT * FROM users WHERE id = ?",
  args: [123],
});

Vercel 배포에서는 @libsql/client 대신 @libsql/client/web을 사용하세요. /web 임포트는 HTTP 프로토콜을 사용해서 엣지와 서버리스 런타임과 호환돼요.

함수 내 SQLite (In-Function SQLite)

@tursodatabase/vercel-experimental 패키지는 베타예요. 아직 버그와 예상 밖 동작이 있을 수 있어요. 프로덕션 데이터에 사용할 때는 주의하고 백업을 꼭 확보하세요.

@tursodatabase/vercel-experimental 패키지는 SQLite 데이터베이스 페이지를 서버리스 함수 자체에 복제해요. 읽기는 네트워크 왕복 없이 로컬 복사본으로 실행되고, 쓰기는 내구성과 일관성을 위해 원격 데이터베이스로 바로 전달돼요. 쿼리당 네트워크 지연이 지배적인 비용인 읽기 위주 워크로드에 이상적이에요.

  • 테넌트별 격리와 샤딩 — 각 에이전트, 워크스페이스, 세션이 독립적인 스토리지 한도를 가진 자기만의 데이터베이스를 가질 수 있어요
  • 컴퓨트와 데이터의 동거(colocation) — 비즈니스 로직이 데이터베이스 바로 옆에서 실행되어 홉 없는 읽기와 요청당 수많은 쿼리가 가능해요
  • 자동 온디맨드 프로비저닝 — createDb()가 호출되면 데이터베이스가 암묵적으로 생성돼요. 재배포나 바인딩 관리가 필요 없어요

Turso의 임베디드 복제본이나 Cloudflare의 SQLite 탑재 Durable Objects를 써 본 적이 있다면 낯익은 모델일 거예요.

퀵스타트 (Quickstart)

패키지를 설치해요:

npm install @tursodatabase/vercel-experimental

Turso API 토큰과 조직 슬러그를 확인해요:

turso auth api-tokens mint my-vercel-token
turso org list

Vercel 프로젝트에 환경 변수를 추가해요:

TURSO_API_TOKEN=your-api-token
TURSO_ORG=your-org-slug
TURSO_GROUP=your-group-name
TURSO_DATABASE=your-database-name

TURSO_GROUP 환경 변수는 모든 데이터베이스 접근을 하나의 그룹으로 한정해요. API 토큰이 조직 전체 접근을 부여하더라도, 데이터베이스 이름을 장악한 공격자는 설정된 그룹 안의 데이터베이스에만 접근할 수 있어요. 그래도 데이터베이스 이름은 가능하면 시크릿 환경 변수로 저장하는 게 좋아요.

createDb()로 데이터베이스를 열고 쿼리를 시작해요:

import { createDb } from "@tursodatabase/vercel-experimental";

const db = await createDb(process.env.TURSO_DATABASE!);
try {
  // Create tables
  await db.execute(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE
    )
  `);

  // Insert data
  await db.execute(
    "INSERT INTO users (name, email) VALUES (?, ?)",
    ["Alice", "[email protected]"]
  );

  // Query data
  const result = await db.query("SELECT * FROM users");
  console.log(result.rows);
} finally {
  await db.close(); // Syncs and closes the connection
}

테넌트별 데이터베이스 (Per-tenant databases)

자동 프로비저닝 덕분에 데이터베이스-퍼-테넌트 패턴을 손쉽게 구현할 수 있어요. 모든 테넌트를 하나의 공유 데이터베이스로 라우팅하는 대신, 각 사용자나 세션에 격리된 SQLite 데이터베이스를 줄 수 있어요. 테넌트 식별자에서 데이터베이스 이름을 유도하기만 하면 돼요:

// Per-user database
const db = await createDb(`user-${userId}`);

// Per-agent-session database
const db = await createDb(`session-${sessionId}`);

드라이버가 필요할 때 데이터베이스를 만들어 주기 때문에, 프로비저닝 단계도, 실행할 마이그레이션도, 구성할 연결 라우팅도 없어요. 각 테넌트의 데이터는 SQLite 수준에서 완전히 격리되고, 모든 데이터베이스가 partial-sync 읽기의 혜택을 독립적으로 받아요.

API 레퍼런스 (API Reference)

createDb(name, options?)

데이터베이스 인스턴스를 만들거나 가져와요. 데이터베이스가 없으면 설정된 그룹(TURSO_GROUP) 안에서 자동으로 생성돼요. 단일 함수 호출 안에서 같은 이름으로 createDb()를 호출하면 기존 인스턴스를 반환해요.

const db = await createDb(process.env.TURSO_DATABASE!, {
  remoteWrites: false,     // Batch writes locally and push on close() (optional, default: true)
});

remoteWrites가 true(기본값)이면 쓰기 문(INSERT, UPDATE, DELETE)이 즉시 내구성을 위해 원격 Turso 데이터베이스로 바로 전달돼요. false로 설정하면 쓰기가 로컬 SQLite 복사본에 적용되고, db.push()나 db.close()가 호출될 때 한 배치로 밀어 올려져요. 최종적 일관(eventual visibility)으로 충분한 벌크 수집이나 백그라운드 작업에서 유용해요.

db.query(sql, params?)

SELECT 쿼리를 실행하고 결과를 반환해요.

const result = await db.query("SELECT * FROM users WHERE id = ?", [1]);

console.log(result.columns); // ["id", "name", "email"]
console.log(result.rows);    // [[1, "Alice", "[email protected]"]]

db.execute(sql, params?)

INSERT, UPDATE, DELETE, DDL 문을 실행해요. 기본적으로 쓰기는 원격 Turso 데이터베이스로 바로 전달돼요. remoteWrites: false로 설정하면 쓰기가 로컬에 적용되고 close() 시점에 밀어 올려져요.

await db.execute("UPDATE users SET name = ? WHERE id = ?", ["Bob", 1]);

db.close()

연결을 닫아요. remoteWrites: false로 설정했다면 아직 밀어 올리지 않은 로컬 쓰기도 원격 Turso 데이터베이스로 전송돼요. 항상 finally 블록에서 호출하세요. 안전망으로, 함수 완료 후 5초 안에 close()가 호출되지 않으면 대기 중인 변경을 밀어 올리도록 Vercel의 waitUntil() API에 콜백을 등록해요.

const db = await createDb(process.env.TURSO_DATABASE!);
try {
  await db.execute("INSERT INTO users (name) VALUES (?)", ["Charlie"]);
} finally {
  await db.close();
}

db.push()

연결을 닫지 않고 로컬 변경을 원격 Turso 데이터베이스로 수동 밀어 올려요. remoteWrites: false를 사용할 때만 해당돼요.

await db.execute("INSERT INTO users (name) VALUES (?)", ["Charlie"]);
await db.push(); // Sync without closing

db.pull()

원격 Turso 데이터베이스에서 최신 변경을 당겨 와요.

await db.pull();

더 알아보기 (Learn more)