api-database-upstash

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Upstash Patterns

Upstash 使用模式

Quick Guide: Upstash provides a REST/HTTP-based Redis client (
@upstash/redis
) 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
set
come back as objects from
get
), which is convenient but has gotchas with large numbers and cross-client compatibility. Use
redis.pipeline()
to batch commands into a single HTTP request,
redis.multi()
for atomic transactions, and
@upstash/ratelimit
for pre-built rate limiting algorithms. For background jobs, use
@upstash/qstash
which pushes messages to your API via HTTP webhooks.

<critical_requirements>
快速指南: Upstash 提供了一款基于REST/HTTP的Redis客户端(
@upstash/redis
),专为无法维持TCP连接的无服务器(serverless)和边缘运行时(edge runtimes)设计。与ioredis/node-redis不同,每个命令都是一次HTTP请求——无需持久连接、连接池或销毁操作。该客户端会自动序列化/反序列化JSON(通过
set
存储的对象会以对象形式从
get
返回),这一特性虽然便捷,但在处理大数字和跨客户端兼容性时存在一些注意事项。使用
redis.pipeline()
将命令批量处理为单个HTTP请求,使用
redis.multi()
执行原子事务,使用
@upstash/ratelimit
调用预构建的限流算法。对于后台任务,使用
@upstash/qstash
通过HTTP Webhook将消息推送到你的API。

<critical_requirements>

CRITICAL: Before Using This Skill

重要提示:使用此技能前须知

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST use
Redis.fromEnv()
for initialization in production code -- never hardcode
UPSTASH_REDIS_REST_URL
or
UPSTASH_REDIS_REST_TOKEN
values)
(You MUST handle the
pending
promise from
@upstash/ratelimit
responses in edge runtimes -- use
context.waitUntil(pending)
on Vercel Edge/Cloudflare Workers or analytics data is lost)
(You MUST use
redis.pipeline()
when issuing 3+ independent commands in a single handler -- each command is a separate HTTP round-trip without pipelining)
(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_URL
UPSTASH_REDIS_REST_TOKEN
的值)
(在边缘运行时中,必须处理
@upstash/ratelimit
响应中的
pending
Promise——在Vercel Edge/Cloudflare Workers上使用
context.waitUntil(pending)
,否则分析数据会丢失)
(在单个处理器中执行3个及以上独立命令时,必须使用
redis.pipeline()
——不使用管道的话,每个命令都是一次独立的HTTP往返)
(绝对不要将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:
  • @upstash/redis
    client setup with
    Redis.fromEnv()
    and constructor options
  • Automatic JSON serialization/deserialization behavior and gotchas
  • Pipeline batching (
    redis.pipeline()
    ) and atomic transactions (
    redis.multi()
    )
  • @upstash/ratelimit
    algorithms: sliding window, fixed window, token bucket
  • @upstash/qstash
    for serverless background jobs and scheduling
  • Global Database architecture (primary + read regions, eventual consistency)
  • Edge runtime compatibility and
    context.waitUntil()
    patterns
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:
  1. 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.
  2. Auto-serialization is default -- Objects go in, objects come out. No manual
    JSON.stringify
    /
    JSON.parse
    . This simplifies 90% of use cases but surprises developers who expect raw string behavior.
  3. Pipeline for performance -- Without pipelining, N commands = N HTTP requests. Always batch independent commands with
    redis.pipeline()
    to reduce round-trips.
  4. Rate limiting as a first-class citizen --
    @upstash/ratelimit
    provides production-ready algorithms without writing Lua scripts. The library handles all the Redis plumbing internally.
  5. Push-based messaging -- QStash delivers messages TO your API via HTTP webhooks. No long-running consumer processes needed -- perfect for serverless.
</philosophy>
<patterns>
Upstash的存在是因为无服务器和边缘运行时无法维持TCP连接。传统Redis客户端(ioredis、node-redis)依赖持久TCP套接字——它们在Cloudflare Workers中会失败,在短生命周期的Lambda函数中会中断,且无法在浏览器/WebAssembly环境中运行。Upstash用REST/HTTP替代TCP,以单命令延迟(约5-15ms vs <1ms)为代价换取了通用兼容性。
核心原则:
  1. 无连接设计——每个命令都是无状态的HTTP请求。无需连接池、销毁操作或连接限制。这是特性,而非局限。
  2. 自动序列化默认开启——对象存入,对象取出。无需手动调用
    JSON.stringify
    /
    JSON.parse
    。这简化了90%的使用场景,但会让期望原始字符串行为的开发者感到意外。
  3. 管道提升性能——不使用管道的话,N个命令=N次HTTP请求。始终使用
    redis.pipeline()
    批量处理独立命令以减少往返次数。
  4. 限流作为一等公民——
    @upstash/ratelimit
    提供生产就绪的算法,无需编写Lua脚本。该库内部处理所有Redis相关逻辑。
  5. 基于推送的消息传递——QStash通过HTTP Webhook将消息推送到你的API。无需长期运行的消费者进程——非常适合无服务器环境。
</philosophy>
<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
JSON.stringify
on write and
JSON.parse
on read. See examples/core.md for type-safe patterns and disabling auto-serialization.
typescript
// 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 | null
Why 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
JSON.stringify
-- doing it manually results in double-encoded strings that return as escaped JSON

Upstash在写入时自动使用
JSON.stringify
序列化对象,读取时使用
JSON.parse
反序列化。类型安全模式和禁用自动序列化的方法请参见examples/core.md
typescript
// 良好示例——对象可自动往返
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.stringify
——手动执行会导致双重编码的字符串,读取时会返回转义后的JSON

Pattern 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 pipeline
Why bad: Each
await
is a separate HTTP request, tripling latency in serverless where every millisecond of cold start matters

将多个命令批量处理为单个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
为什么不良: 每个
await
都是一次独立的HTTP请求,在无服务器环境中,冷启动的每一毫秒都很重要,这样会使延迟增加三倍

Pattern 4: Atomic Transactions

模式4:原子事务

Use
redis.multi()
when commands must execute atomically. See examples/core.md for examples.
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 (
    redis.pipeline()
    ) -- Commands are independent, you want batching for speed, atomicity not required
  • Transaction (
    redis.multi()
    ) -- Commands must all succeed together, no interleaving allowed

当命令必须原子执行时,使用
redis.multi()
。示例请参见examples/core.md
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
    JSON.stringify()
    before passing objects to
    redis.set()
    -- auto-serialization already handles this, resulting in double-encoded strings like
    "{\"name\":\"Alice\"}"
    that break on read
  • Ignoring the
    pending
    promise from
    ratelimit.limit()
    in edge runtimes -- analytics data and multi-region sync are lost silently; use
    context.waitUntil(pending)
  • Issuing 5+ sequential
    await redis.get/set()
    calls without pipelining -- each is a separate HTTP request, adding 25-75ms of unnecessary latency
  • Attempting Pub/Sub (
    redis.subscribe
    ), blocking commands (
    BRPOP
    ,
    BLPOP
    ), or Lua scripting (
    eval
    ) -- Upstash REST API does not support these; use ioredis with TCP
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
    automaticDeserialization: false
    when interoperating with non-Upstash clients -- other clients store raw strings, Upstash will fail to parse them as JSON
  • Creating a new
    Redis
    instance per request instead of reusing a module-level singleton -- while connectionless, the client still benefits from HTTP keep-alive and warm connections
Common Mistakes:
  • Expecting
    redis.get()
    to return a string when an object was stored -- auto-deserialization returns the original object type, not a JSON string
  • Assuming pipeline execution is atomic -- pipelines batch for network efficiency but other clients can interleave; use
    redis.multi()
    for atomicity
  • Using
    Ratelimit.slidingWindow
    with
    MultiRegionRatelimit
    -- sliding window has high Redis command overhead in multi-region setups; use
    fixedWindow
    instead
  • 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 >
    2^53 - 1
    (Number.MAX_SAFE_INTEGER). Upstash returns these as strings even when the TypeScript type says
    number
    . Always validate large numeric values.
  • Base64 encoding by default: The SDK requests base64-encoded responses to handle edge cases. If you see garbled output like
    dmFsdWU=
    , the response encoding is interfering -- check
    responseEncoding
    option.
  • redis.get()
    returns
    null
    for missing keys, not
    undefined
    : This matters for TypeScript narrowing -- check
    result !== null
    , not truthiness.
  • SET options use an object, not positional args: Upstash uses
    redis.set("key", "value", { ex: 300 })
    not
    redis.set("key", "value", "EX", 300)
    -- the ioredis positional argument style does not work.
  • 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.
  • hgetall
    returns an empty object
    {}
    for non-existent keys
    : Check
    Object.keys(result).length === 0
    , not
    result === null
    .
  • blockUntilReady()
    does not work on Cloudflare Workers
    : Cloudflare's
    Date.now()
    behaves differently; use
    limit()
    with manual retry logic instead.
  • No WATCH command: Upstash REST API does not support
    WATCH
    for optimistic locking. Use
    redis.multi()
    for atomic operations or implement application-level optimistic concurrency.
  • Auto-pipelining is available: The SDK can automatically batch commands issued during the same event loop tick via
    enableAutoPipelining: true
    in the constructor.
</red_flags>

<critical_reminders>
高优先级问题:
  • 在将对象传递给
    redis.set()
    前使用
    JSON.stringify()
    ——自动序列化已处理此操作,会导致双重编码的字符串(如
    "{\"name\":\"Alice\"}"
    ),读取时会出错
  • 在边缘运行时中忽略
    ratelimit.limit()
    返回的
    pending
    Promise——分析数据和多区域同步会静默丢失;请使用
    context.waitUntil(pending)
  • 不使用管道执行5次及以上顺序
    await redis.get/set()
    调用——每个命令都是一次独立的HTTP请求,会增加25-75ms的不必要延迟
  • 尝试使用Pub/Sub(
    redis.subscribe
    )、阻塞命令(BRPOP、BLPOP)或Lua脚本(
    eval
    )——Upstash REST API不支持这些;请改用带TCP连接的ioredis
中优先级问题:
  • 缓存键未设置TTL——与任何Redis一样,内存会无限增长直到触发淘汰机制
  • 将全局数据库用于写密集型工作负载——写入始终路由到主区域,复制会使命令成本翻倍
  • 与非Upstash客户端交互时未设置
    automaticDeserialization: false
    ——其他客户端存储原始字符串,Upstash会无法将其解析为JSON
  • 每个请求创建新的
    Redis
    实例而非复用模块级单例——虽然是无连接设计,但客户端仍能从HTTP长连接和预热连接中受益
常见错误:
  • 期望
    redis.get()
    在存储对象时返回字符串——自动反序列化会返回原始对象类型,而非JSON字符串
  • 假设管道执行是原子的——管道仅为了网络效率进行批量处理,但其他客户端可能会交错执行;请使用
    redis.multi()
    实现原子性
  • MultiRegionRatelimit
    中使用
    Ratelimit.slidingWindow
    ——滑动窗口在多区域设置中Redis命令开销较高;请改用
    fixedWindow
  • 存储超过1 MB的值——REST API有负载大小限制;请存储引用,从对象存储中获取大数据
注意事项与边缘情况:
  • 大数字会变为字符串:JavaScript无法安全处理大于
    2^53 - 1
    (Number.MAX_SAFE_INTEGER)的数字。即使TypeScript类型标注为
    number
    ,Upstash也会将这些数字返回为字符串。请始终验证大数值。
  • 默认Base64编码:SDK请求Base64编码的响应以处理边缘情况。如果看到类似
    dmFsdWU=
    的乱码输出,说明响应编码产生了干扰——请检查
    responseEncoding
    选项。
  • redis.get()
    对不存在的键返回
    null
    ,而非
    undefined
    :这对TypeScript类型收窄很重要——请检查
    result !== null
    ,而非真值判断。
  • SET选项使用对象,而非位置参数:Upstash使用
    redis.set("key", "value", { ex: 300 })
    而非
    redis.set("key", "value", "EX", 300)
    ——ioredis的位置参数风格不适用。
  • 全局数据库是最终一致性:在不同区域写入后立即读取可能会返回陈旧数据。请针对最终一致性进行设计,或使用区域数据库实现强一致性。
  • hgetall
    对不存在的键返回空对象
    {}
    :请检查
    Object.keys(result).length === 0
    ,而非
    result === null
  • blockUntilReady()
    在Cloudflare Workers中无法工作
    :Cloudflare的
    Date.now()
    行为不同;请改用带手动重试逻辑的
    limit()
  • 无WATCH命令:Upstash REST API不支持用于乐观锁的
    WATCH
    。请使用
    redis.multi()
    执行原子操作,或实现应用级乐观并发。
  • 支持自动管道:SDK可通过在构造函数中设置
    enableAutoPipelining: true
    ,自动批量处理同一事件循环tick中发出的命令。
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

重要提醒

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST use
Redis.fromEnv()
for initialization in production code -- never hardcode
UPSTASH_REDIS_REST_URL
or
UPSTASH_REDIS_REST_TOKEN
values)
(You MUST handle the
pending
promise from
@upstash/ratelimit
responses in edge runtimes -- use
context.waitUntil(pending)
on Vercel Edge/Cloudflare Workers or analytics data is lost)
(You MUST use
redis.pipeline()
when issuing 3+ independent commands in a single handler -- each command is a separate HTTP round-trip without pipelining)
(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_URL
UPSTASH_REDIS_REST_TOKEN
的值)
(在边缘运行时中,必须处理
@upstash/ratelimit
响应中的
pending
Promise——在Vercel Edge/Cloudflare Workers上使用
context.waitUntil(pending)
,否则分析数据会丢失)
(在单个处理器中执行3个及以上独立命令时,必须使用
redis.pipeline()
——不使用管道的话,每个命令都是一次独立的HTTP往返)
(绝对不要将Upstash用于Pub/Sub、阻塞命令(BRPOP、BLPOP、XREAD BLOCK)或Lua脚本——REST API不支持这些功能;请改用带TCP连接的ioredis)
不遵守这些规则会导致密钥泄露、边缘运行时中静默数据丢失、顺序HTTP请求带来的不必要延迟,以及不支持命令导致的运行时错误。
</critical_reminders>