mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 06:11:27 -07:00
update skills
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# Hyperdrive
|
||||
|
||||
Accelerates database queries from Workers via connection pooling, edge setup, query caching.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Connection Pooling**: Persistent connections eliminate TCP/TLS/auth handshakes (~7 round-trips)
|
||||
- **Edge Setup**: Connection negotiation at edge, pooling near origin
|
||||
- **Query Caching**: Auto-cache non-mutating queries (default 60s TTL)
|
||||
- **Support**: PostgreSQL, MySQL + compatibles (CockroachDB, Timescale, PlanetScale, Neon, Supabase)
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Worker → Edge (setup) → Pool (near DB) → Origin
|
||||
↓ cached reads
|
||||
Cache
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Create config
|
||||
npx wrangler hyperdrive create my-db \
|
||||
--connection-string="postgres://user:pass@host:5432/db"
|
||||
|
||||
# wrangler.jsonc
|
||||
{
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
"hyperdrive": [{"binding": "HYPERDRIVE", "id": "<ID>"}]
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { Client } from "pg";
|
||||
|
||||
export default {
|
||||
async fetch(req: Request, env: Env): Promise<Response> {
|
||||
const client = new Client({
|
||||
connectionString: env.HYPERDRIVE.connectionString,
|
||||
});
|
||||
await client.connect();
|
||||
const result = await client.query("SELECT * FROM users WHERE id = $1", [123]);
|
||||
await client.end();
|
||||
return Response.json(result.rows);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
✅ Global access to single-region DBs, high read ratios, popular queries, connection-heavy loads
|
||||
❌ Write-heavy, real-time data (<1s), single-region apps close to DB
|
||||
|
||||
**💡 Pair with Smart Placement** for Workers making multiple queries - executes near DB to minimize latency.
|
||||
|
||||
## Driver Choice
|
||||
|
||||
| Driver | Use When | Notes |
|
||||
|--------|----------|-------|
|
||||
| **pg** (recommended) | General use, TypeScript, ecosystem compatibility | Stable, widely used, works with most ORMs |
|
||||
| **postgres.js** | Advanced features, template literals, streaming | Lighter than pg, `prepare: true` is default |
|
||||
| **mysql2** | MySQL/MariaDB/PlanetScale | MySQL only, less mature support |
|
||||
|
||||
## Reading Order
|
||||
|
||||
| New to Hyperdrive | Implementing | Troubleshooting |
|
||||
|-------------------|--------------|-----------------|
|
||||
| 1. README (this) | 1. [configuration.md](./configuration.md) | 1. [gotchas.md](./gotchas.md) |
|
||||
| 2. [configuration.md](./configuration.md) | 2. [api.md](./api.md) | 2. [patterns.md](./patterns.md) |
|
||||
| 3. [api.md](./api.md) | 3. [patterns.md](./patterns.md) | 3. [api.md](./api.md) |
|
||||
|
||||
## In This Reference
|
||||
- [configuration.md](./configuration.md) - Setup, wrangler config, Smart Placement
|
||||
- [api.md](./api.md) - Binding APIs, query patterns, driver usage
|
||||
- [patterns.md](./patterns.md) - Use cases, ORMs, multi-query optimization
|
||||
- [gotchas.md](./gotchas.md) - Limits, troubleshooting, connection management
|
||||
|
||||
## See Also
|
||||
- [smart-placement](../smart-placement/) - Optimize multi-query Workers near databases
|
||||
- [d1](../d1/) - Serverless SQLite alternative for edge-native apps
|
||||
- [workers](../workers/) - Worker runtime with database bindings
|
||||
143
.agents/skills/cloudflare-deploy/references/hyperdrive/api.md
Normal file
143
.agents/skills/cloudflare-deploy/references/hyperdrive/api.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# API Reference
|
||||
|
||||
See [README.md](./README.md) for overview, [configuration.md](./configuration.md) for setup.
|
||||
|
||||
## Binding Interface
|
||||
|
||||
```typescript
|
||||
interface Hyperdrive {
|
||||
connectionString: string; // PostgreSQL
|
||||
// MySQL properties:
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
password: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
interface Env {
|
||||
HYPERDRIVE: Hyperdrive;
|
||||
}
|
||||
```
|
||||
|
||||
**Generate types:** `npx wrangler types` (auto-creates worker-configuration.d.ts from wrangler.jsonc)
|
||||
|
||||
## PostgreSQL (node-postgres) - RECOMMENDED
|
||||
|
||||
```typescript
|
||||
import { Client } from "pg"; // pg@^8.17.2
|
||||
|
||||
export default {
|
||||
async fetch(req: Request, env: Env): Promise<Response> {
|
||||
const client = new Client({connectionString: env.HYPERDRIVE.connectionString});
|
||||
try {
|
||||
await client.connect();
|
||||
const result = await client.query("SELECT * FROM users WHERE id = $1", [123]);
|
||||
return Response.json(result.rows);
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**⚠️ Workers connection limit: 6 per Worker invocation** - use connection pooling wisely.
|
||||
|
||||
## PostgreSQL (postgres.js)
|
||||
|
||||
```typescript
|
||||
import postgres from "postgres"; // postgres@^3.4.8
|
||||
|
||||
const sql = postgres(env.HYPERDRIVE.connectionString, {
|
||||
max: 5, // Limit per Worker (Workers max: 6)
|
||||
prepare: true, // Enabled by default, required for caching
|
||||
fetch_types: false, // Reduce latency if not using arrays
|
||||
});
|
||||
|
||||
const users = await sql`SELECT * FROM users WHERE active = ${true} LIMIT 10`;
|
||||
```
|
||||
|
||||
**⚠️ `prepare: true` is enabled by default and required for Hyperdrive caching.** Setting to `false` disables prepared statements + cache.
|
||||
|
||||
## MySQL (mysql2)
|
||||
|
||||
```typescript
|
||||
import { createConnection } from "mysql2/promise"; // mysql2@^3.16.2
|
||||
|
||||
const conn = await createConnection({
|
||||
host: env.HYPERDRIVE.host,
|
||||
user: env.HYPERDRIVE.user,
|
||||
password: env.HYPERDRIVE.password,
|
||||
database: env.HYPERDRIVE.database,
|
||||
port: env.HYPERDRIVE.port,
|
||||
disableEval: true, // ⚠️ REQUIRED for Workers
|
||||
});
|
||||
|
||||
const [results] = await conn.query("SELECT * FROM users WHERE active = ? LIMIT ?", [true, 10]);
|
||||
ctx.waitUntil(conn.end());
|
||||
```
|
||||
|
||||
**⚠️ MySQL support is less mature than PostgreSQL** - expect fewer optimizations and potential edge cases.
|
||||
|
||||
## Query Caching
|
||||
|
||||
**Cacheable:**
|
||||
```sql
|
||||
SELECT * FROM posts WHERE published = true;
|
||||
SELECT COUNT(*) FROM users;
|
||||
```
|
||||
|
||||
**NOT cacheable:**
|
||||
```sql
|
||||
-- Writes
|
||||
INSERT/UPDATE/DELETE
|
||||
|
||||
-- Volatile functions
|
||||
SELECT NOW();
|
||||
SELECT random();
|
||||
SELECT LASTVAL(); -- PostgreSQL
|
||||
SELECT UUID(); -- MySQL
|
||||
```
|
||||
|
||||
**Cache config:**
|
||||
- Default: `max_age=60s`, `swr=15s`
|
||||
- Max `max_age`: 3600s
|
||||
- Disable: `--caching-disabled=true`
|
||||
|
||||
**Multiple configs pattern:**
|
||||
```typescript
|
||||
// Reads: cached
|
||||
const sqlCached = postgres(env.HYPERDRIVE_CACHED.connectionString);
|
||||
const posts = await sqlCached`SELECT * FROM posts ORDER BY views DESC LIMIT 10`;
|
||||
|
||||
// Writes/time-sensitive: no cache
|
||||
const sqlNoCache = postgres(env.HYPERDRIVE_NO_CACHE.connectionString);
|
||||
const orders = await sqlNoCache`SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 5 MINUTE`;
|
||||
```
|
||||
|
||||
## ORMs
|
||||
|
||||
**Drizzle:**
|
||||
```typescript
|
||||
import { drizzle } from "drizzle-orm/postgres-js"; // drizzle-orm@^0.45.1
|
||||
import postgres from "postgres";
|
||||
|
||||
const client = postgres(env.HYPERDRIVE.connectionString, {max: 5, prepare: true});
|
||||
const db = drizzle(client);
|
||||
const users = await db.select().from(users).where(eq(users.active, true)).limit(10);
|
||||
```
|
||||
|
||||
**Kysely:**
|
||||
```typescript
|
||||
import { Kysely, PostgresDialect } from "kysely"; // kysely@^0.27+
|
||||
import postgres from "postgres";
|
||||
|
||||
const db = new Kysely({
|
||||
dialect: new PostgresDialect({
|
||||
postgres: postgres(env.HYPERDRIVE.connectionString, {max: 5, prepare: true}),
|
||||
}),
|
||||
});
|
||||
const users = await db.selectFrom("users").selectAll().where("active", "=", true).execute();
|
||||
```
|
||||
|
||||
See [patterns.md](./patterns.md) for use cases, [gotchas.md](./gotchas.md) for limits.
|
||||
@@ -0,0 +1,159 @@
|
||||
# Configuration
|
||||
|
||||
See [README.md](./README.md) for overview.
|
||||
|
||||
## Create Config
|
||||
|
||||
**PostgreSQL:**
|
||||
```bash
|
||||
# Basic
|
||||
npx wrangler hyperdrive create my-db \
|
||||
--connection-string="postgres://user:pass@host:5432/db"
|
||||
|
||||
# Custom cache
|
||||
npx wrangler hyperdrive create my-db \
|
||||
--connection-string="postgres://..." \
|
||||
--max-age=120 --swr=30
|
||||
|
||||
# No cache
|
||||
npx wrangler hyperdrive create my-db \
|
||||
--connection-string="postgres://..." \
|
||||
--caching-disabled=true
|
||||
```
|
||||
|
||||
**MySQL:**
|
||||
```bash
|
||||
npx wrangler hyperdrive create my-db \
|
||||
--connection-string="mysql://user:pass@host:3306/db"
|
||||
```
|
||||
|
||||
## wrangler.jsonc
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"compatibility_date": "2025-01-01", // Use latest for new projects
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
"hyperdrive": [
|
||||
{
|
||||
"binding": "HYPERDRIVE",
|
||||
"id": "<HYPERDRIVE_ID>",
|
||||
"localConnectionString": "postgres://user:pass@localhost:5432/dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Generate TypeScript types:** Run `npx wrangler types` to auto-generate `worker-configuration.d.ts` from your wrangler.jsonc.
|
||||
|
||||
**Multiple configs:**
|
||||
```jsonc
|
||||
{
|
||||
"hyperdrive": [
|
||||
{"binding": "HYPERDRIVE_CACHED", "id": "<ID1>"},
|
||||
{"binding": "HYPERDRIVE_NO_CACHE", "id": "<ID2>"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Management
|
||||
|
||||
```bash
|
||||
npx wrangler hyperdrive list
|
||||
npx wrangler hyperdrive get <ID>
|
||||
npx wrangler hyperdrive update <ID> --max-age=180
|
||||
npx wrangler hyperdrive delete <ID>
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
Hyperdrive create/update CLI flags:
|
||||
|
||||
| Option | Default | Notes |
|
||||
|--------|---------|-------|
|
||||
| `--caching-disabled` | `false` | Disable caching |
|
||||
| `--max-age` | `60` | Cache TTL (max 3600s) |
|
||||
| `--swr` | `15` | Stale-while-revalidate |
|
||||
| `--origin-connection-limit` | 20/100 | Free/paid |
|
||||
| `--access-client-id` | - | Tunnel auth |
|
||||
| `--access-client-secret` | - | Tunnel auth |
|
||||
| `--sslmode` | `require` | PostgreSQL only |
|
||||
|
||||
## Smart Placement Integration
|
||||
|
||||
For Workers making **multiple queries** per request, enable Smart Placement to execute near your database:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"compatibility_date": "2025-01-01",
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
"placement": {
|
||||
"mode": "smart"
|
||||
},
|
||||
"hyperdrive": [
|
||||
{
|
||||
"binding": "HYPERDRIVE",
|
||||
"id": "<HYPERDRIVE_ID>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:** Multi-query Workers run closer to DB, reducing round-trip latency. See [patterns.md](./patterns.md) for examples.
|
||||
|
||||
## Private DB via Tunnel
|
||||
|
||||
```
|
||||
Worker → Hyperdrive → Access → Tunnel → Private Network → DB
|
||||
```
|
||||
|
||||
**Setup:**
|
||||
```bash
|
||||
# 1. Create tunnel
|
||||
cloudflared tunnel create my-db-tunnel
|
||||
|
||||
# 2. Configure hostname in Zero Trust dashboard
|
||||
# Domain: db-tunnel.example.com
|
||||
# Service: TCP -> localhost:5432
|
||||
|
||||
# 3. Create service token (Zero Trust > Service Auth)
|
||||
# Save Client ID/Secret
|
||||
|
||||
# 4. Create Access app (db-tunnel.example.com)
|
||||
# Policy: Service Auth token from step 3
|
||||
|
||||
# 5. Create Hyperdrive
|
||||
npx wrangler hyperdrive create my-private-db \
|
||||
--host=db-tunnel.example.com \
|
||||
--user=dbuser --password=dbpass --database=prod \
|
||||
--access-client-id=<ID> --access-client-secret=<SECRET>
|
||||
```
|
||||
|
||||
**⚠️ Don't specify `--port` with Tunnel** - port configured in tunnel service settings.
|
||||
|
||||
## Local Dev
|
||||
|
||||
**Option 1: Local (RECOMMENDED):**
|
||||
```bash
|
||||
# Env var (takes precedence)
|
||||
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgres://user:pass@localhost:5432/dev"
|
||||
npx wrangler dev
|
||||
|
||||
# wrangler.jsonc
|
||||
{"hyperdrive": [{"binding": "HYPERDRIVE", "localConnectionString": "postgres://..."}]}
|
||||
```
|
||||
|
||||
**Remote DB locally:**
|
||||
```bash
|
||||
# PostgreSQL
|
||||
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgres://user:pass@remote:5432/db?sslmode=require"
|
||||
|
||||
# MySQL
|
||||
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="mysql://user:pass@remote:3306/db?sslMode=REQUIRED"
|
||||
```
|
||||
|
||||
**Option 2: Remote execution:**
|
||||
```bash
|
||||
npx wrangler dev --remote # Uses deployed config, affects production
|
||||
```
|
||||
|
||||
See [api.md](./api.md), [patterns.md](./patterns.md), [gotchas.md](./gotchas.md).
|
||||
@@ -0,0 +1,77 @@
|
||||
# Gotchas
|
||||
|
||||
See [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [patterns.md](./patterns.md).
|
||||
|
||||
## Common Errors
|
||||
|
||||
### "Too many open connections" / "Connection limit exceeded"
|
||||
|
||||
**Cause:** Workers have a hard limit of **6 concurrent connections per invocation**
|
||||
**Solution:** Set `max: 5` in driver config, reuse connections, ensure proper cleanup with `client.end()` or `ctx.waitUntil(conn.end())`
|
||||
|
||||
### "Failed to acquire a connection (Pool exhausted)"
|
||||
|
||||
**Cause:** All connections in pool are in use, often due to long-running transactions
|
||||
**Solution:** Reduce transaction duration, avoid queries >60s, don't hold connections during external calls, or upgrade to paid plan for more connections
|
||||
|
||||
### "connection_refused"
|
||||
|
||||
**Cause:** Database refusing connections due to firewall, connection limits, or service down
|
||||
**Solution:** Check firewall allows Cloudflare IPs, verify DB listening on port, confirm service running, and validate credentials
|
||||
|
||||
### "Query timeout (deadline exceeded)"
|
||||
|
||||
**Cause:** Query execution exceeding 60s timeout limit
|
||||
**Solution:** Optimize with indexes, reduce dataset with LIMIT, break into smaller queries, or use async processing
|
||||
|
||||
### "password authentication failed"
|
||||
|
||||
**Cause:** Invalid credentials in Hyperdrive configuration
|
||||
**Solution:** Check username and password in Hyperdrive config match database credentials
|
||||
|
||||
### "SSL/TLS connection error"
|
||||
|
||||
**Cause:** SSL/TLS configuration mismatch between Hyperdrive and database
|
||||
**Solution:** Add `sslmode=require` (Postgres) or `sslMode=REQUIRED` (MySQL), upload CA cert if self-signed, verify DB has SSL enabled, and check cert expiry
|
||||
|
||||
### "Queries not being cached"
|
||||
|
||||
**Cause:** Query is mutating (INSERT/UPDATE/DELETE), contains volatile functions (NOW(), RANDOM()), or caching disabled
|
||||
**Solution:** Verify query is non-mutating SELECT, avoid volatile functions, confirm caching enabled, use `wrangler dev --remote` to test, and set `prepare=true` for postgres.js
|
||||
|
||||
### "Slow multi-query Workers despite Hyperdrive"
|
||||
|
||||
**Cause:** Worker executing at edge, each query round-trips to DB region
|
||||
**Solution:** Enable Smart Placement (`"placement": {"mode": "smart"}` in wrangler.jsonc) to execute Worker near DB. See [patterns.md](./patterns.md) Multi-Query pattern.
|
||||
|
||||
### "Local database connection failed"
|
||||
|
||||
**Cause:** `localConnectionString` incorrect or database not running
|
||||
**Solution:** Verify `localConnectionString` correct, check DB running, confirm env var name matches binding, and test with psql/mysql client
|
||||
|
||||
### "Environment variable not working"
|
||||
|
||||
**Cause:** Environment variable format incorrect or not exported
|
||||
**Solution:** Use format `CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>`, ensure binding matches wrangler.jsonc, export variable in shell, and restart wrangler dev
|
||||
|
||||
## Limits
|
||||
|
||||
| Limit | Free | Paid | Notes |
|
||||
|-------|------|------|-------|
|
||||
| Max configs | 10 | 25 | Hyperdrive configurations per account |
|
||||
| Worker connections | 6 | 6 | Max concurrent connections per Worker invocation |
|
||||
| Username/DB name | 63 bytes | 63 bytes | Maximum length |
|
||||
| Connection timeout | 15s | 15s | Time to establish connection |
|
||||
| Idle timeout | 10 min | 10 min | Connection idle timeout |
|
||||
| Max origin connections | ~20 | ~100 | Connections to origin database |
|
||||
| Query duration max | 60s | 60s | Queries >60s terminated |
|
||||
| Cached response max | 50 MB | 50 MB | Responses >50MB returned but not cached |
|
||||
|
||||
## Resources
|
||||
|
||||
- [Docs](https://developers.cloudflare.com/hyperdrive/)
|
||||
- [Getting Started](https://developers.cloudflare.com/hyperdrive/get-started/)
|
||||
- [Wrangler Reference](https://developers.cloudflare.com/hyperdrive/reference/wrangler-commands/)
|
||||
- [Supported DBs](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/)
|
||||
- [Discord #hyperdrive](https://discord.cloudflare.com)
|
||||
- [Limit Increase Form](https://forms.gle/ukpeZVLWLnKeixDu7)
|
||||
@@ -0,0 +1,190 @@
|
||||
# Patterns
|
||||
|
||||
See [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md).
|
||||
|
||||
## High-Traffic Read-Heavy
|
||||
|
||||
```typescript
|
||||
const sql = postgres(env.HYPERDRIVE.connectionString, {max: 5, prepare: true});
|
||||
|
||||
// Cacheable: popular content
|
||||
const posts = await sql`SELECT * FROM posts WHERE published = true ORDER BY views DESC LIMIT 20`;
|
||||
|
||||
// Cacheable: user profiles
|
||||
const [user] = await sql`SELECT id, username, bio FROM users WHERE id = ${userId}`;
|
||||
```
|
||||
|
||||
**Benefits:** Trending/profiles cached (60s), connection pooling handles spikes.
|
||||
|
||||
## Mixed Read/Write
|
||||
|
||||
```typescript
|
||||
interface Env {
|
||||
HYPERDRIVE_CACHED: Hyperdrive; // max_age=120
|
||||
HYPERDRIVE_REALTIME: Hyperdrive; // caching disabled
|
||||
}
|
||||
|
||||
// Reads: cached
|
||||
if (req.method === "GET") {
|
||||
const sql = postgres(env.HYPERDRIVE_CACHED.connectionString, {prepare: true});
|
||||
const products = await sql`SELECT * FROM products WHERE category = ${cat}`;
|
||||
}
|
||||
|
||||
// Writes: no cache (immediate consistency)
|
||||
if (req.method === "POST") {
|
||||
const sql = postgres(env.HYPERDRIVE_REALTIME.connectionString, {prepare: true});
|
||||
await sql`INSERT INTO orders ${sql(data)}`;
|
||||
}
|
||||
```
|
||||
|
||||
## Analytics Dashboard
|
||||
|
||||
```typescript
|
||||
const client = new Client({connectionString: env.HYPERDRIVE.connectionString});
|
||||
await client.connect();
|
||||
|
||||
// Aggregate queries cached (use fixed timestamps for caching)
|
||||
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const dailyStats = await client.query(`
|
||||
SELECT DATE(created_at) as date, COUNT(*) as orders, SUM(amount) as revenue
|
||||
FROM orders WHERE created_at >= $1
|
||||
GROUP BY DATE(created_at) ORDER BY date DESC
|
||||
`, [thirtyDaysAgo]);
|
||||
|
||||
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const topProducts = await client.query(`
|
||||
SELECT p.name, COUNT(oi.id) as count, SUM(oi.quantity * oi.price) as revenue
|
||||
FROM order_items oi JOIN products p ON oi.product_id = p.id
|
||||
WHERE oi.created_at >= $1
|
||||
GROUP BY p.id, p.name ORDER BY revenue DESC LIMIT 10
|
||||
`, [sevenDaysAgo]);
|
||||
```
|
||||
|
||||
**Benefits:** Expensive aggregations cached (avoid NOW() for cacheability), dashboard instant, reduced DB load.
|
||||
|
||||
## Multi-Tenant
|
||||
|
||||
```typescript
|
||||
const tenantId = req.headers.get("X-Tenant-ID");
|
||||
const sql = postgres(env.HYPERDRIVE.connectionString, {prepare: true});
|
||||
|
||||
// Tenant-scoped queries cached separately
|
||||
const docs = await sql`
|
||||
SELECT * FROM documents
|
||||
WHERE tenant_id = ${tenantId} AND deleted_at IS NULL
|
||||
ORDER BY updated_at DESC LIMIT 50
|
||||
`;
|
||||
```
|
||||
|
||||
**Benefits:** Per-tenant caching, shared connection pool, protects DB from multi-tenant load.
|
||||
|
||||
## Geographically Distributed
|
||||
|
||||
```typescript
|
||||
// Worker runs at edge nearest user
|
||||
// Connection setup at edge (fast), pooling near DB (efficient)
|
||||
const sql = postgres(env.HYPERDRIVE.connectionString, {prepare: true});
|
||||
const [user] = await sql`SELECT * FROM users WHERE id = ${userId}`;
|
||||
|
||||
return Response.json({
|
||||
user,
|
||||
serverRegion: req.cf?.colo, // Edge location
|
||||
});
|
||||
```
|
||||
|
||||
**Benefits:** Edge setup + DB pooling = global → single-region DB without replication.
|
||||
|
||||
## Multi-Query + Smart Placement
|
||||
|
||||
For Workers making **multiple queries** per request, enable Smart Placement to execute near DB:
|
||||
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"placement": {"mode": "smart"},
|
||||
"hyperdrive": [{"binding": "HYPERDRIVE", "id": "<ID>"}]
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
const sql = postgres(env.HYPERDRIVE.connectionString, {prepare: true});
|
||||
|
||||
// Multiple queries benefit from Smart Placement
|
||||
const [user] = await sql`SELECT * FROM users WHERE id = ${userId}`;
|
||||
const orders = await sql`SELECT * FROM orders WHERE user_id = ${userId} ORDER BY created_at DESC LIMIT 10`;
|
||||
const stats = await sql`SELECT COUNT(*) as total, SUM(amount) as spent FROM orders WHERE user_id = ${userId}`;
|
||||
|
||||
return Response.json({user, orders, stats});
|
||||
```
|
||||
|
||||
**Benefits:** Worker executes near DB → reduces latency for each query. Without Smart Placement, each query round-trips from edge.
|
||||
|
||||
## Connection Pooling
|
||||
|
||||
Operates in **transaction mode**: connection acquired per transaction, `RESET` on return.
|
||||
|
||||
**SET statements:**
|
||||
```typescript
|
||||
// ✅ Within transaction
|
||||
await client.query("BEGIN");
|
||||
await client.query("SET work_mem = '256MB'");
|
||||
await client.query("SELECT * FROM large_table"); // Uses SET
|
||||
await client.query("COMMIT"); // RESET after
|
||||
|
||||
// ✅ Single statement
|
||||
await client.query("SET work_mem = '256MB'; SELECT * FROM large_table");
|
||||
|
||||
// ❌ Across queries (may get different connection)
|
||||
await client.query("SET work_mem = '256MB'");
|
||||
await client.query("SELECT * FROM large_table"); // SET not applied
|
||||
```
|
||||
|
||||
**Best practices:**
|
||||
```typescript
|
||||
// ❌ Long transactions block pooling
|
||||
await client.query("BEGIN");
|
||||
await processThousands(); // Connection held entire time
|
||||
await client.query("COMMIT");
|
||||
|
||||
// ✅ Short transactions
|
||||
await client.query("BEGIN");
|
||||
await client.query("UPDATE users SET status = $1 WHERE id = $2", [status, id]);
|
||||
await client.query("COMMIT");
|
||||
|
||||
// ✅ SET LOCAL within transaction
|
||||
await client.query("BEGIN");
|
||||
await client.query("SET LOCAL work_mem = '256MB'");
|
||||
await client.query("SELECT * FROM large_table");
|
||||
await client.query("COMMIT");
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
**Enable prepared statements (required for caching):**
|
||||
```typescript
|
||||
const sql = postgres(connectionString, {prepare: true}); // Default, enables caching
|
||||
```
|
||||
|
||||
**Optimize connection settings:**
|
||||
```typescript
|
||||
const sql = postgres(connectionString, {
|
||||
max: 5, // Stay under Workers' 6 connection limit
|
||||
fetch_types: false, // Reduce latency if not using arrays
|
||||
idle_timeout: 60, // Match Worker lifetime
|
||||
});
|
||||
```
|
||||
|
||||
**Write cache-friendly queries:**
|
||||
```typescript
|
||||
// ✅ Cacheable (deterministic)
|
||||
await sql`SELECT * FROM products WHERE category = 'electronics' LIMIT 10`;
|
||||
|
||||
// ❌ Not cacheable (volatile NOW())
|
||||
await sql`SELECT * FROM logs WHERE created_at > NOW()`;
|
||||
|
||||
// ✅ Cacheable (parameterized timestamp)
|
||||
const ts = Date.now();
|
||||
await sql`SELECT * FROM logs WHERE created_at > ${ts}`;
|
||||
```
|
||||
|
||||
See [gotchas.md](./gotchas.md) for limits, troubleshooting.
|
||||
Reference in New Issue
Block a user