Chapter 09

Edge 运行时与 Serverless

Drizzle 最独特的能力——跑在 Cloudflare Workers、Vercel Edge、Lambda 等无持久连接的环境。

9.1 Edge 场景的特殊性

传统 Node.js 长进程:创建一个数据库连接池(e.g. 10 连接),全进程复用——创建一次、使用千万次。

Edge / Serverless 函数:每个请求可能启动全新实例,请求结束即销毁。如果每次都建 TCP 连接+TLS 握手+鉴权,延迟要 100-300ms,还把数据库的最大连接数打爆。

解决方案有两类:

HTTP driver
把 SQL 通过 HTTP 请求发给专门的网关(Neon HTTP、Planetscale HTTP、Turso HTTP),不维持长连接——冷启动零开销。
连接池中间件
PgBouncer、Supabase Supavisor、Neon pooler——应用侧维持短连接,网关端维持长连接池。

9.2 Neon Serverless(Postgres over WebSocket/HTTP)

$ npm i @neondatabase/serverless
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);

Neon HTTP driver 把每个 SQL 发成一条 POST 请求,适合 Edge 函数、Cloudflare Workers、Vercel Edge Runtime。

若需要事务(HTTP 不能持 session),切到 WebSocket:

import { drizzle } from "drizzle-orm/neon-serverless";
import { Pool } from "@neondatabase/serverless";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool);

// 能用 db.transaction

9.3 Turso / libsql(分布式 SQLite)

Turso 是 LibSQL 的托管方案——分布式 SQLite 副本部署在全球边缘,HTTP + WebSocket 协议访问。

$ npm i @libsql/client
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";

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

本地开发也可以用 libsql(url: "file:local.db")——开发和生产共用一套 API。

9.4 Planetscale(HTTP MySQL)

$ npm i @planetscale/database
import { drizzle } from "drizzle-orm/planetscale-serverless";
import { Client } from "@planetscale/database";

const client = new Client({ url: process.env.DATABASE_URL });
export const db = drizzle(client);
Planetscale 无外键

Planetscale(Vitess 底层)不支持外键约束——schema 里 .references() 仍可以写(Drizzle 仅把它用于关系定义),但 migration 不生成 FOREIGN KEY 约束。业务层保证引用完整性。

9.5 Cloudflare D1

D1 是 Cloudflare Workers 原生的 SQLite——通过 env 绑定:

import { drizzle } from "drizzle-orm/d1";

type Env = { DB: D1Database };

export default {
  async fetch(req: Request, env: Env) {
    const db = drizzle(env.DB);
    const users = await db.select().from(schema.users);
    return Response.json(users);
  },
};

wrangler.toml:

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxxxx"
migrations_dir = "drizzle"

用 drizzle-kit 产出迁移,然后 wrangler d1 migrations apply DB 把它们推到 D1。

9.6 Vercel Postgres / Xata / Supabase

都提供自家 serverless driver:

9.7 冷启动优化三要点

  1. Drizzle 本身 7KB——不占启动预算。
  2. driver 选 HTTP 优先——无 WebSocket/TCP 握手。
  3. schema 模块尽量小——导入整个 schema/* 有成百列定义也不慢,但循环引用或大量 TypeScript 类型推断会拖编译。

9.8 环境变量与多环境

// .env
DATABASE_URL="postgresql://..."
NEON_ENV=dev

// drizzle.config.ts
export default defineConfig({
  dialect: "postgresql",
  schema: "./src/schema/*",
  out: "./drizzle",
  dbCredentials: { url: process.env.DATABASE_URL! },
});

9.9 本地开发 vs 生产

常见策略:本地跑 Postgres Docker 容器(和生产同方言),生产用 Neon/Supabase:

# docker-compose.yml
services:
  db:
    image: postgres:17
    ports: ["5432:5432"]
    environment:
      POSTGRES_PASSWORD: pw
      POSTGRES_DB: app

之后 DATABASE_URL="postgresql://postgres:pw@localhost:5432/app"——同一份 schema、同一份 migrate 代码。

9.10 小结