api-database-upstash
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUpstash Patterns
Upstash 使用模式
Quick Guide: Upstash provides a REST/HTTP-based Redis client () designed for serverless and edge runtimes where TCP connections are unavailable. Unlike ioredis/node-redis, every command is an HTTP request -- no persistent connections, no connection pools, no teardown. The client automatically serializes/deserializes JSON (objects stored via@upstash/rediscome back as objects fromset), which is convenient but has gotchas with large numbers and cross-client compatibility. Usegetto batch commands into a single HTTP request,redis.pipeline()for atomic transactions, andredis.multi()for pre-built rate limiting algorithms. For background jobs, use@upstash/ratelimitwhich pushes messages to your API via HTTP webhooks.@upstash/qstash
<critical_requirements>
快速指南: Upstash 提供了一款基于REST/HTTP的Redis客户端(),专为无法维持TCP连接的无服务器(serverless)和边缘运行时(edge runtimes)设计。与ioredis/node-redis不同,每个命令都是一次HTTP请求——无需持久连接、连接池或销毁操作。该客户端会自动序列化/反序列化JSON(通过@upstash/redis存储的对象会以对象形式从set返回),这一特性虽然便捷,但在处理大数字和跨客户端兼容性时存在一些注意事项。使用get将命令批量处理为单个HTTP请求,使用redis.pipeline()执行原子事务,使用redis.multi()调用预构建的限流算法。对于后台任务,使用@upstash/ratelimit通过HTTP Webhook将消息推送到你的API。@upstash/qstash
<critical_requirements>
CRITICAL: Before Using This Skill
重要提示:使用此技能前须知
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST use for initialization in production code -- never hardcode or values)
Redis.fromEnv()UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN(You MUST handle the promise from responses in edge runtimes -- use on Vercel Edge/Cloudflare Workers or analytics data is lost)
pending@upstash/ratelimitcontext.waitUntil(pending)(You MUST use when issuing 3+ independent commands in a single handler -- each command is a separate HTTP round-trip without pipelining)
redis.pipeline()(You MUST NOT use Upstash for Pub/Sub, blocking commands (BRPOP, BLPOP, XREAD BLOCK), or Lua scripting -- REST API does not support these; use ioredis with a TCP connection instead)
</critical_requirements>
所有代码必须遵循CLAUDE.md中的项目规范(短横线命名法、具名导出、导入顺序、、具名常量)import type
(生产代码中必须使用进行初始化——绝对不要硬编码或的值)
Redis.fromEnv()UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN(在边缘运行时中,必须处理响应中的 Promise——在Vercel Edge/Cloudflare Workers上使用,否则分析数据会丢失)
@upstash/ratelimitpendingcontext.waitUntil(pending)(在单个处理器中执行3个及以上独立命令时,必须使用——不使用管道的话,每个命令都是一次独立的HTTP往返)
redis.pipeline()(绝对不要将Upstash用于Pub/Sub、阻塞命令(BRPOP、BLPOP、XREAD BLOCK)或Lua脚本——REST API不支持这些功能;请改用带TCP连接的ioredis)
</critical_requirements>
Examples
示例
- Core Patterns -- Client setup, commands, auto-serialization, pipeline, transactions
- Rate Limiting -- @upstash/ratelimit algorithms, middleware, analytics
- QStash -- Background jobs, scheduling, message publishing
Additional resources:
- reference.md -- Command cheat sheet, constructor options, environment variables, eviction policies
Auto-detection: Upstash, @upstash/redis, @upstash/ratelimit, @upstash/qstash, Redis.fromEnv, UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN, Ratelimit.slidingWindow, Ratelimit.fixedWindow, Ratelimit.tokenBucket, serverless Redis, edge Redis, REST Redis
When to use:
- Serverless functions (AWS Lambda, Vercel, Netlify) that cannot maintain TCP connections
- Edge runtimes (Cloudflare Workers, Vercel Edge, Fastly Compute) that only support HTTP
- Rate limiting API routes with pre-built algorithms (sliding window, fixed window, token bucket)
- Caching in serverless/edge where ioredis connection pooling is impractical
- Background job scheduling with QStash (push-based, no long-running consumers needed)
- Global read latency optimization via Upstash Global Database with read replicas
Key patterns covered:
- client setup with
@upstash/redisand constructor optionsRedis.fromEnv() - Automatic JSON serialization/deserialization behavior and gotchas
- Pipeline batching () and atomic transactions (
redis.pipeline())redis.multi() - algorithms: sliding window, fixed window, token bucket
@upstash/ratelimit - for serverless background jobs and scheduling
@upstash/qstash - Global Database architecture (primary + read regions, eventual consistency)
- Edge runtime compatibility and patterns
context.waitUntil()
When NOT to use:
- Long-running servers with persistent connections (use ioredis -- lower latency per command via TCP)
- Pub/Sub, blocking commands, or Lua scripting (REST API does not support these)
- Write-heavy workloads on Global Database (writes always go to primary region)
- Latency-critical paths where per-command HTTP overhead (~5-15ms) is unacceptable (use ioredis with TCP for <1ms per command)
- Large payloads (>1 MB) -- REST API has payload size limits
<philosophy>
- 核心模式——客户端设置、命令、自动序列化、管道、事务
- 限流——@upstash/ratelimit算法、中间件、分析
- QStash——后台任务、调度、消息发布
额外资源:
- reference.md——命令速查表、构造函数选项、环境变量、淘汰策略
自动检测项: Upstash、@upstash/redis、@upstash/ratelimit、@upstash/qstash、Redis.fromEnv、UPSTASH_REDIS_REST_URL、UPSTASH_REDIS_REST_TOKEN、Ratelimit.slidingWindow、Ratelimit.fixedWindow、Ratelimit.tokenBucket、serverless Redis、edge Redis、REST Redis
适用场景:
- 无法维持TCP连接的无服务器函数(AWS Lambda、Vercel、Netlify)
- 仅支持HTTP的边缘运行时(Cloudflare Workers、Vercel Edge、Fastly Compute)
- 使用预构建算法(滑动窗口、固定窗口、令牌桶)对API路由进行限流
- 在无服务器/边缘环境中进行缓存,而ioredis连接池不切实际的场景
- 使用QStash进行后台任务调度(基于推送,无需长期运行的消费者)
- 通过Upstash全局数据库和只读副本优化全局读取延迟
涵盖的核心模式:
- 使用和构造函数选项设置
Redis.fromEnv()客户端@upstash/redis - 自动JSON序列化/反序列化的行为及注意事项
- 管道批量处理()和原子事务(
redis.pipeline())redis.multi() - 算法:滑动窗口、固定窗口、令牌桶
@upstash/ratelimit - 使用实现无服务器后台任务和调度
@upstash/qstash - 全局数据库架构(主节点+只读区域、最终一致性)
- 边缘运行时兼容性及模式
context.waitUntil()
不适用场景:
- 带有持久连接的长期运行服务器(使用ioredis——通过TCP实现更低的单命令延迟)
- Pub/Sub、阻塞命令或Lua脚本(REST API不支持这些)
- 全局数据库上的写密集型工作负载(写入始终路由到主区域)
- 单命令HTTP开销(约5-15ms)无法接受的延迟敏感路径(使用带TCP的ioredis实现<1ms的单命令延迟)
- 大负载(>1 MB)——REST API有负载大小限制
<philosophy>
Philosophy
设计理念
Upstash exists because serverless and edge runtimes cannot maintain TCP connections. Traditional Redis clients (ioredis, node-redis) rely on persistent TCP sockets -- they fail in Cloudflare Workers, break in short-lived Lambda functions, and cannot run in browser/WebAssembly environments. Upstash replaces TCP with REST/HTTP, trading per-command latency (~5-15ms vs <1ms) for universal compatibility.
Core principles:
- Connectionless by design -- Every command is a stateless HTTP request. No connection pools, no teardown, no connection limits. This is a feature, not a limitation.
- Auto-serialization is default -- Objects go in, objects come out. No manual /
JSON.stringify. This simplifies 90% of use cases but surprises developers who expect raw string behavior.JSON.parse - Pipeline for performance -- Without pipelining, N commands = N HTTP requests. Always batch independent commands with to reduce round-trips.
redis.pipeline() - Rate limiting as a first-class citizen -- provides production-ready algorithms without writing Lua scripts. The library handles all the Redis plumbing internally.
@upstash/ratelimit - Push-based messaging -- QStash delivers messages TO your API via HTTP webhooks. No long-running consumer processes needed -- perfect for serverless.
<patterns>
Upstash的存在是因为无服务器和边缘运行时无法维持TCP连接。传统Redis客户端(ioredis、node-redis)依赖持久TCP套接字——它们在Cloudflare Workers中会失败,在短生命周期的Lambda函数中会中断,且无法在浏览器/WebAssembly环境中运行。Upstash用REST/HTTP替代TCP,以单命令延迟(约5-15ms vs <1ms)为代价换取了通用兼容性。
核心原则:
- 无连接设计——每个命令都是无状态的HTTP请求。无需连接池、销毁操作或连接限制。这是特性,而非局限。
- 自动序列化默认开启——对象存入,对象取出。无需手动调用/
JSON.stringify。这简化了90%的使用场景,但会让期望原始字符串行为的开发者感到意外。JSON.parse - 管道提升性能——不使用管道的话,N个命令=N次HTTP请求。始终使用批量处理独立命令以减少往返次数。
redis.pipeline() - 限流作为一等公民——提供生产就绪的算法,无需编写Lua脚本。该库内部处理所有Redis相关逻辑。
@upstash/ratelimit - 基于推送的消息传递——QStash通过HTTP Webhook将消息推送到你的API。无需长期运行的消费者进程——非常适合无服务器环境。
<patterns>
Core Patterns
核心模式
Pattern 1: Client Setup with Redis.fromEnv()
模式1:使用Redis.fromEnv()初始化客户端
Initialize using environment variables for zero-config deployment. See examples/core.md for full examples including constructor options and timeout configuration.
typescript
// Good Example
import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
// Reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN automatically
export { redis };Why good: Zero-config, environment variables injected by platform (Vercel, Fly.io), no secrets in code
typescript
// Bad Example
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: "https://us1-merry-cat-12345.upstash.io",
token: "AXXXAAIgcDE...",
});Why bad: Hardcoded credentials leak in version control, non-portable across environments
使用环境变量进行初始化,实现零配置部署。完整示例(包括构造函数选项和超时配置)请参见examples/core.md。
typescript
// 良好示例
import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
// 自动读取UPSTASH_REDIS_REST_URL和UPSTASH_REDIS_REST_TOKEN
export { redis };为什么良好: 零配置,环境变量由平台(Vercel、Fly.io)注入,代码中无密钥
typescript
// 不良示例
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: "https://us1-merry-cat-12345.upstash.io",
token: "AXXXAAIgcDE...",
});为什么不良: 硬编码的密钥会在版本控制中泄露,无法跨环境移植
Pattern 2: Automatic JSON Serialization
模式2:自动JSON序列化
Upstash auto-serializes objects with on write and on read. See examples/core.md for type-safe patterns and disabling auto-serialization.
JSON.stringifyJSON.parsetypescript
// Good Example -- objects round-trip automatically
interface UserProfile {
name: string;
email: string;
loginCount: number;
}
const CACHE_TTL_SECONDS = 3600;
await redis.set<UserProfile>(
"user:123",
{
name: "Alice",
email: "alice@example.com",
loginCount: 42,
},
{ ex: CACHE_TTL_SECONDS },
);
// Returns typed object -- no JSON.parse needed
const user = await redis.get<UserProfile>("user:123");
// user is UserProfile | nullWhy good: TypeScript generics provide type safety, no manual serialization, TTL set via options object
typescript
// Bad Example -- unnecessary manual serialization
await redis.set("user:123", JSON.stringify({ name: "Alice" }));
const raw = await redis.get("user:123");
const user = JSON.parse(raw as string); // Double-serialized: "{\"name\":\"Alice\"}"Why bad: Auto-serialization already calls -- doing it manually results in double-encoded strings that return as escaped JSON
JSON.stringifyUpstash在写入时自动使用序列化对象,读取时使用反序列化。类型安全模式和禁用自动序列化的方法请参见examples/core.md。
JSON.stringifyJSON.parsetypescript
// 良好示例——对象可自动往返
interface UserProfile {
name: string;
email: string;
loginCount: number;
}
const CACHE_TTL_SECONDS = 3600;
await redis.set<UserProfile>(
"user:123",
{
name: "Alice",
email: "alice@example.com",
loginCount: 42,
},
{ ex: CACHE_TTL_SECONDS },
);
// 返回类型化对象——无需JSON.parse
const user = await redis.get<UserProfile>("user:123");
// user的类型为UserProfile | null为什么良好: TypeScript泛型提供类型安全,无需手动序列化,通过选项对象设置TTL
typescript
// 不良示例——不必要的手动序列化
await redis.set("user:123", JSON.stringify({ name: "Alice" }));
const raw = await redis.get("user:123");
const user = JSON.parse(raw as string); // 双重序列化:"{\"name\":\"Alice\"}"为什么不良: 自动序列化已调用——手动执行会导致双重编码的字符串,读取时会返回转义后的JSON
JSON.stringifyPattern 3: Pipeline Batching
模式3:管道批量处理
Batch multiple commands into a single HTTP request. Without pipelining, each command is a separate round-trip (~5-15ms each). See examples/core.md for typed pipeline results.
typescript
// Good Example -- single HTTP request for all commands
const USER_TTL_SECONDS = 3600;
const pipe = redis.pipeline();
pipe.set("user:123:name", "Alice", { ex: USER_TTL_SECONDS });
pipe.set("user:123:email", "alice@example.com", { ex: USER_TTL_SECONDS });
pipe.incr("stats:signups");
const results = await pipe.exec<["OK", "OK", number]>();
// results[0] => "OK"
// results[1] => "OK"
// results[2] => 1 (incremented value)Why good: Single HTTP round-trip for 3 commands, typed results with generics, named TTL constant
typescript
// Bad Example -- 3 separate HTTP requests
await redis.set("user:123:name", "Alice");
await redis.set("user:123:email", "alice@example.com");
await redis.incr("stats:signups");
// 3 round-trips = ~15-45ms total vs ~5-15ms with pipelineWhy bad: Each is a separate HTTP request, tripling latency in serverless where every millisecond of cold start matters
await将多个命令批量处理为单个HTTP请求。不使用管道的话,每个命令都是一次独立的往返(每次约5-15ms)。类型化管道结果请参见examples/core.md。
typescript
// 良好示例——所有命令通过单个HTTP请求执行
const USER_TTL_SECONDS = 3600;
const pipe = redis.pipeline();
pipe.set("user:123:name", "Alice", { ex: USER_TTL_SECONDS });
pipe.set("user:123:email", "alice@example.com", { ex: USER_TTL_SECONDS });
pipe.incr("stats:signups");
const results = await pipe.exec<["OK", "OK", number]>();
// results[0] => "OK"
// results[1] => "OK"
// results[2] => 1(递增后的值)为什么良好: 3个命令仅需一次HTTP往返,泛型提供类型化结果,TTL使用具名常量
typescript
// 不良示例——3次独立的HTTP请求
await redis.set("user:123:name", "Alice");
await redis.set("user:123:email", "alice@example.com");
await redis.incr("stats:signups");
// 3次往返 = 总延迟约15-45ms,而使用管道仅需约5-15ms为什么不良: 每个都是一次独立的HTTP请求,在无服务器环境中,冷启动的每一毫秒都很重要,这样会使延迟增加三倍
awaitPattern 4: Atomic Transactions
模式4:原子事务
Use when commands must execute atomically. See examples/core.md for examples.
redis.multi()typescript
// Good Example -- atomic counter + flag update
const tx = redis.multi();
tx.incr("order:count");
tx.set("order:last-updated", Date.now());
const [count, status] = await tx.exec<[number, "OK"]>();Why good: All commands execute atomically (no interleaving from other clients), typed results
When to use pipeline vs transaction:
- Pipeline () -- Commands are independent, you want batching for speed, atomicity not required
redis.pipeline() - Transaction () -- Commands must all succeed together, no interleaving allowed
redis.multi()
当命令必须原子执行时,使用。示例请参见examples/core.md。
redis.multi()typescript
// 良好示例——原子计数器+标志更新
const tx = redis.multi();
tx.incr("order:count");
tx.set("order:last-updated", Date.now());
const [count, status] = await tx.exec<[number, "OK"]>();为什么良好: 所有命令原子执行(不会被其他客户端交错执行),结果类型化
何时使用管道vs事务:
- 管道()——命令相互独立,希望通过批量处理提升速度,无需原子性
redis.pipeline() - 事务()——命令必须全部成功执行,不允许交错
redis.multi()
Pattern 5: Rate Limiting with @upstash/ratelimit
模式5:使用@upstash/ratelimit实现限流
Pre-built rate limiting that handles all Redis internals. See examples/rate-limiting.md for all algorithms, middleware integration, and analytics.
typescript
// Good Example
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const MAX_REQUESTS = 10;
const WINDOW_DURATION = "10 s";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(MAX_REQUESTS, WINDOW_DURATION),
analytics: true,
});
const { success, limit, remaining, reset, pending } =
await ratelimit.limit("user:123");
// CRITICAL: In edge runtimes, handle the pending promise
// context.waitUntil(pending);
if (!success) {
return new Response("Too Many Requests", {
status: 429,
headers: {
"X-RateLimit-Limit": String(limit),
"X-RateLimit-Remaining": String(remaining),
"X-RateLimit-Reset": String(reset),
},
});
}Why good: No Lua scripts needed, named constants for limits, analytics for monitoring, proper 429 response with standard headers
预构建的限流功能,处理所有Redis内部逻辑。所有算法、中间件集成和分析请参见examples/rate-limiting.md。
typescript
// 良好示例
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const MAX_REQUESTS = 10;
const WINDOW_DURATION = "10 s";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(MAX_REQUESTS, WINDOW_DURATION),
analytics: true,
});
const { success, limit, remaining, reset, pending } =
await ratelimit.limit("user:123");
// 重要提示:在边缘运行时中,必须处理pending Promise
// context.waitUntil(pending);
if (!success) {
return new Response("请求过于频繁", {
status: 429,
headers: {
"X-RateLimit-Limit": String(limit),
"X-RateLimit-Remaining": String(remaining),
"X-RateLimit-Reset": String(reset),
},
});
}为什么良好: 无需Lua脚本,使用具名常量设置限制,支持分析监控,返回符合标准的429响应头
Pattern 6: QStash Background Jobs
模式6:QStash后台任务
Push-based messaging for serverless. See examples/qstash.md for scheduling, retries, and receiver verification.
typescript
// Good Example -- publish a background job
import { Client } from "@upstash/qstash";
const qstash = new Client({
token: process.env.QSTASH_TOKEN!,
});
await qstash.publishJSON({
url: "https://your-app.com/api/process-order",
body: { orderId: "order-456", action: "fulfill" },
retries: 3,
delay: "10s",
});Why good: Fire-and-forget from handler, automatic retries on failure, configurable delay, at-least-once delivery guaranteed
</patterns>
<decision_framework>
针对无服务器环境的基于推送的消息传递。调度、重试和接收方验证请参见examples/qstash.md。
typescript
// 良好示例——发布后台任务
import { Client } from "@upstash/qstash";
const qstash = new Client({
token: process.env.QSTASH_TOKEN!,
});
await qstash.publishJSON({
url: "https://your-app.com/api/process-order",
body: { orderId: "order-456", action: "fulfill" },
retries: 3,
delay: "10s",
});为什么良好: 处理器中即发即弃,失败时自动重试,可配置延迟,保证至少一次送达
</patterns>
<decision_framework>
Decision Framework
决策框架
Upstash vs ioredis/node-redis
Upstash vs ioredis/node-redis
Which Redis client should I use?
|-- Running in edge runtime (Cloudflare Workers, Vercel Edge)?
| --> @upstash/redis (only option -- no TCP available)
|-- Running in serverless (Lambda, Vercel Serverless)?
| |-- Short-lived functions with no connection reuse?
| | --> @upstash/redis (no connection management overhead)
| |-- Long-lived functions with connection pooling?
| --> ioredis (lower per-command latency)
|-- Running on a persistent server (Docker, EC2, K8s)?
| --> ioredis (persistent TCP = <1ms latency vs ~5-15ms HTTP)
|-- Need Pub/Sub, blocking commands, or Lua scripts?
| --> ioredis (REST API cannot support these)
|-- Need to run in browser or WebAssembly?
--> @upstash/redis (HTTP works everywhere)我应该使用哪个Redis客户端?
|-- 是否在边缘运行时(Cloudflare Workers、Vercel Edge)中运行?
| --> @upstash/redis(唯一选项——无TCP可用)
|-- 是否在无服务器环境(Lambda、Vercel Serverless)中运行?
| |-- 短生命周期函数,无法复用连接?
| | --> @upstash/redis(无连接管理开销)
| |-- 长生命周期函数,可使用连接池?
| --> ioredis(单命令延迟更低)
|-- 是否在持久服务器(Docker、EC2、K8s)上运行?
| --> ioredis(持久TCP连接 = <1ms延迟 vs HTTP的约5-15ms)
|-- 是否需要Pub/Sub、阻塞命令或Lua脚本?
| --> ioredis(REST API不支持这些)
|-- 是否需要在浏览器或WebAssembly中运行?
--> @upstash/redis(HTTP可在所有环境中运行)Which Rate Limiting Algorithm?
选择哪种限流算法?
Which @upstash/ratelimit algorithm should I use?
|-- Need strict, evenly distributed limiting?
| --> slidingWindow -- smoothest, no burst-at-boundary issues
|-- Need simple, low-overhead limiting?
| --> fixedWindow -- cheapest computationally, allows boundary bursts
|-- Need to allow burst traffic up to a capacity?
| --> tokenBucket -- smooths bursts, allows initial spike up to maxTokens
|-- Need multi-region rate limiting?
--> fixedWindow (slidingWindow has high Redis command overhead in multi-region)我应该使用哪种@upstash/ratelimit算法?
|-- 是否需要严格、均匀分布的限流?
| --> slidingWindow——最平滑,无边界突发问题
|-- 是否需要简单、低开销的限流?
| --> fixedWindow——计算成本最低,允许边界突发
|-- 是否需要允许达到容量上限的突发流量?
| --> tokenBucket——平滑突发流量,允许初始峰值达到maxTokens
|-- 是否需要多区域限流?
--> fixedWindow(slidingWindow在多区域设置中Redis命令开销较高)Pipeline vs Transaction vs Sequential
管道vs事务vs顺序执行
How should I batch these Redis commands?
|-- Commands are independent (no ordering dependency)?
| --> Pipeline (redis.pipeline()) -- non-atomic but single HTTP request
|-- Commands must execute atomically (all-or-nothing)?
| --> Transaction (redis.multi()) -- atomic, single HTTP request
|-- Only 1-2 commands?
--> Sequential is fine -- pipeline overhead not worth it我应该如何批量处理这些Redis命令?
|-- 命令相互独立(无顺序依赖)?
| --> 管道(redis.pipeline())——非原子但仅需一次HTTP请求
|-- 命令必须原子执行(要么全部成功,要么全部失败)?
| --> 事务(redis.multi())——原子性,仅需一次HTTP请求
|-- 仅1-2个命令?
--> 顺序执行即可——管道的开销得不偿失Global Database vs Regional
全局数据库vs区域数据库
Should I use Upstash Global Database?
|-- Read-heavy workload with users worldwide?
| --> Global Database -- reads from nearest replica
|-- Write-heavy workload?
| --> Regional Database -- writes always go to primary, replication doubles write cost
|-- Need strong consistency?
| --> Regional Database -- Global is eventually consistent
|-- Latency-sensitive reads from multiple continents?
--> Global Database -- sub-1ms reads from nearest region</decision_framework>
<red_flags>
我应该使用Upstash全局数据库吗?
|-- 读密集型工作负载,用户遍布全球?
| --> 全局数据库——从最近的副本读取
|-- 写密集型工作负载?
| --> 区域数据库——写入始终路由到主节点,复制会使写入成本翻倍
|-- 是否需要强一致性?
| --> 区域数据库——全局数据库是最终一致性
|-- 多大陆的延迟敏感型读取?
--> 全局数据库——从最近区域读取延迟低于1ms</decision_framework>
<red_flags>
RED FLAGS
警示事项
High Priority Issues:
- Using before passing objects to
JSON.stringify()-- auto-serialization already handles this, resulting in double-encoded strings likeredis.set()that break on read"{\"name\":\"Alice\"}" - Ignoring the promise from
pendingin edge runtimes -- analytics data and multi-region sync are lost silently; useratelimit.limit()context.waitUntil(pending) - Issuing 5+ sequential calls without pipelining -- each is a separate HTTP request, adding 25-75ms of unnecessary latency
await redis.get/set() - Attempting Pub/Sub (), blocking commands (
redis.subscribe,BRPOP), or Lua scripting (BLPOP) -- Upstash REST API does not support these; use ioredis with TCPeval
Medium Priority Issues:
- Missing TTL on cached keys -- same as any Redis: unbounded memory growth until eviction kicks in
- Using Global Database for write-heavy workloads -- writes always route to primary region and replication doubles command costs
- Not setting when interoperating with non-Upstash clients -- other clients store raw strings, Upstash will fail to parse them as JSON
automaticDeserialization: false - Creating a new instance per request instead of reusing a module-level singleton -- while connectionless, the client still benefits from HTTP keep-alive and warm connections
Redis
Common Mistakes:
- Expecting to return a string when an object was stored -- auto-deserialization returns the original object type, not a JSON string
redis.get() - Assuming pipeline execution is atomic -- pipelines batch for network efficiency but other clients can interleave; use for atomicity
redis.multi() - Using with
Ratelimit.slidingWindow-- sliding window has high Redis command overhead in multi-region setups; useMultiRegionRatelimitinsteadfixedWindow - Storing values larger than 1 MB -- REST API has payload size limits; store references and fetch large data from object storage
Gotchas & Edge Cases:
- Large numbers become strings: JavaScript cannot safely handle numbers > (Number.MAX_SAFE_INTEGER). Upstash returns these as strings even when the TypeScript type says
2^53 - 1. Always validate large numeric values.number - Base64 encoding by default: The SDK requests base64-encoded responses to handle edge cases. If you see garbled output like , the response encoding is interfering -- check
dmFsdWU=option.responseEncoding - returns
redis.get()for missing keys, notnull: This matters for TypeScript narrowing -- checkundefined, not truthiness.result !== null - SET options use an object, not positional args: Upstash uses not
redis.set("key", "value", { ex: 300 })-- the ioredis positional argument style does not work.redis.set("key", "value", "EX", 300) - Global Database is eventually consistent: A write followed immediately by a read from a different region may return stale data. Design for eventual consistency or use regional database for strong consistency.
- returns an empty object
hgetallfor non-existent keys: Check{}, notObject.keys(result).length === 0.result === null - does not work on Cloudflare Workers: Cloudflare's
blockUntilReady()behaves differently; useDate.now()with manual retry logic instead.limit() - No WATCH command: Upstash REST API does not support for optimistic locking. Use
WATCHfor atomic operations or implement application-level optimistic concurrency.redis.multi() - Auto-pipelining is available: The SDK can automatically batch commands issued during the same event loop tick via in the constructor.
enableAutoPipelining: true
</red_flags>
<critical_reminders>
高优先级问题:
- 在将对象传递给前使用
redis.set()——自动序列化已处理此操作,会导致双重编码的字符串(如JSON.stringify()),读取时会出错"{\"name\":\"Alice\"}" - 在边缘运行时中忽略返回的
ratelimit.limit()Promise——分析数据和多区域同步会静默丢失;请使用pendingcontext.waitUntil(pending) - 不使用管道执行5次及以上顺序调用——每个命令都是一次独立的HTTP请求,会增加25-75ms的不必要延迟
await redis.get/set() - 尝试使用Pub/Sub()、阻塞命令(BRPOP、BLPOP)或Lua脚本(
redis.subscribe)——Upstash REST API不支持这些;请改用带TCP连接的iorediseval
中优先级问题:
- 缓存键未设置TTL——与任何Redis一样,内存会无限增长直到触发淘汰机制
- 将全局数据库用于写密集型工作负载——写入始终路由到主区域,复制会使命令成本翻倍
- 与非Upstash客户端交互时未设置——其他客户端存储原始字符串,Upstash会无法将其解析为JSON
automaticDeserialization: false - 每个请求创建新的实例而非复用模块级单例——虽然是无连接设计,但客户端仍能从HTTP长连接和预热连接中受益
Redis
常见错误:
- 期望在存储对象时返回字符串——自动反序列化会返回原始对象类型,而非JSON字符串
redis.get() - 假设管道执行是原子的——管道仅为了网络效率进行批量处理,但其他客户端可能会交错执行;请使用实现原子性
redis.multi() - 在中使用
MultiRegionRatelimit——滑动窗口在多区域设置中Redis命令开销较高;请改用Ratelimit.slidingWindowfixedWindow - 存储超过1 MB的值——REST API有负载大小限制;请存储引用,从对象存储中获取大数据
注意事项与边缘情况:
- 大数字会变为字符串:JavaScript无法安全处理大于(Number.MAX_SAFE_INTEGER)的数字。即使TypeScript类型标注为
2^53 - 1,Upstash也会将这些数字返回为字符串。请始终验证大数值。number - 默认Base64编码:SDK请求Base64编码的响应以处理边缘情况。如果看到类似的乱码输出,说明响应编码产生了干扰——请检查
dmFsdWU=选项。responseEncoding - 对不存在的键返回
redis.get(),而非null:这对TypeScript类型收窄很重要——请检查undefined,而非真值判断。result !== null - SET选项使用对象,而非位置参数:Upstash使用而非
redis.set("key", "value", { ex: 300 })——ioredis的位置参数风格不适用。redis.set("key", "value", "EX", 300) - 全局数据库是最终一致性:在不同区域写入后立即读取可能会返回陈旧数据。请针对最终一致性进行设计,或使用区域数据库实现强一致性。
- 对不存在的键返回空对象
hgetall:请检查{},而非Object.keys(result).length === 0。result === null - 在Cloudflare Workers中无法工作:Cloudflare的
blockUntilReady()行为不同;请改用带手动重试逻辑的Date.now()。limit() - 无WATCH命令:Upstash REST API不支持用于乐观锁的。请使用
WATCH执行原子操作,或实现应用级乐观并发。redis.multi() - 支持自动管道:SDK可通过在构造函数中设置,自动批量处理同一事件循环tick中发出的命令。
enableAutoPipelining: true
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
重要提醒
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST use for initialization in production code -- never hardcode or values)
Redis.fromEnv()UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN(You MUST handle the promise from responses in edge runtimes -- use on Vercel Edge/Cloudflare Workers or analytics data is lost)
pending@upstash/ratelimitcontext.waitUntil(pending)(You MUST use when issuing 3+ independent commands in a single handler -- each command is a separate HTTP round-trip without pipelining)
redis.pipeline()(You MUST NOT use Upstash for Pub/Sub, blocking commands (BRPOP, BLPOP, XREAD BLOCK), or Lua scripting -- REST API does not support these; use ioredis with a TCP connection instead)
Failure to follow these rules will cause credential leaks, silent data loss in edge runtimes, unnecessary latency from sequential HTTP requests, and runtime errors from unsupported commands.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目规范(短横线命名法、具名导出、导入顺序、、具名常量)import type
(生产代码中必须使用进行初始化——绝对不要硬编码或的值)
Redis.fromEnv()UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN(在边缘运行时中,必须处理响应中的 Promise——在Vercel Edge/Cloudflare Workers上使用,否则分析数据会丢失)
@upstash/ratelimitpendingcontext.waitUntil(pending)(在单个处理器中执行3个及以上独立命令时,必须使用——不使用管道的话,每个命令都是一次独立的HTTP往返)
redis.pipeline()(绝对不要将Upstash用于Pub/Sub、阻塞命令(BRPOP、BLPOP、XREAD BLOCK)或Lua脚本——REST API不支持这些功能;请改用带TCP连接的ioredis)
不遵守这些规则会导致密钥泄露、边缘运行时中静默数据丢失、顺序HTTP请求带来的不必要延迟,以及不支持命令导致的运行时错误。
</critical_reminders>