mcpField
A
api-database-upstash

api-database-upstash

github.com/agents-inc数据与数据库2638 次安装2 天前更新
代码仓库

关于 api-database-upstash

api-database-upstash 汇总在无 TCP 连接的无服务器和边缘运行时中使用 Upstash Redis、限流与后台任务的开发模式。内容涵盖 REST 客户端、命令批处理、事务及适用场景和限制。

主要功能

  1. 01通过 REST/HTTP 使用 Redis
  2. 02自动序列化和反序列化 JSON
  3. 03用 pipeline 批处理独立命令
  4. 04用 multi 执行原子事务
  5. 05提供滑动窗口、固定窗口和令牌桶限流模式
  6. 06使用 QStash 推送后台任务

适用场景

  1. 01在边缘运行时缓存数据
  2. 02为 API 路由设置请求限流
  3. 03合并多个 Redis 命令以减少请求往返
  4. 04通过 HTTP Webhook 安排后台任务

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill api-database-upstash

安装后在 Claude Code 中输入/api-database-upstash确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
core.md
qstash.md
rate-limiting.md

详细介绍

Upstash 使用模式

快速指南: Upstash 提供基于 REST/HTTP 的 Redis 客户端(@upstash/redis),专为无法使用 TCP 连接的无服务器和边缘运行时设计。与 ioredis/node-redis 不同,每条命令都是一个 HTTP 请求——没有持久连接、没有连接池,也无需拆除连接。客户端会自动序列化/反序列化 JSON(通过 set 存储的对象会在 get 时以对象形式返回),使用方便,但处理大数字和跨客户端兼容性时需要注意。使用 redis.pipeline() 将多条命令批量发送为一个 HTTP 请求,使用 redis.multi() 执行原子事务,并使用 @upstash/ratelimit 实现预构建的限流算法。对于后台任务,使用 @upstash/qstash,它会通过 HTTP webhook 向你的 API 推送消息。


<critical_requirements>

重要:使用此技能前

所有代码都必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、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>


示例

  • 核心模式 -- 客户端设置、命令、自动序列化、管道、事务
  • 速率限制 -- @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、无服务器 Redis、边缘 Redis、REST Redis

适用场景:

  • 无法维持 TCP 连接的无服务器函数(AWS Lambda、Vercel、Netlify)
  • 仅支持 HTTP 的边缘运行时(Cloudflare Workers、Vercel Edge、Fastly Compute)
  • 使用预置算法(滑动窗口、固定窗口、令牌桶)限制 API 路由的速率
  • 在 ioredis 连接池不切实际的无服务器/边缘环境中进行缓存
  • 使用 QStash 调度后台任务(基于推送,无需长期运行的消费者)
  • 通过 Upstash Global Database 和读取副本优化全局读取延迟

涵盖的关键模式:

  • 使用 Redis.fromEnv() 和构造函数选项设置 @upstash/redis 客户端
  • 自动 JSON 序列化/反序列化的行为与注意事项
  • 管道批处理(redis.pipeline())和原子事务(redis.multi())
  • @upstash/ratelimit 算法:滑动窗口、固定窗口、令牌桶
  • 使用 @upstash/qstash 处理无服务器后台任务和调度
  • Global Database 架构(主区域 + 读取区域、最终一致性)
  • 边缘运行时兼容性和 context.waitUntil() 模式

不适用场景:

  • 使用持久连接的长期运行服务器(使用 ioredis,通过 TCP 降低每条命令的延迟)
  • Pub/Sub、阻塞命令或 Lua 脚本(REST API 不支持这些功能)
  • Global Database 上写入密集型的工作负载(写入始终发送到主区域)
  • 无法接受每条命令产生约 5–15 ms HTTP 开销的延迟关键路径(使用 ioredis,通过 TCP 将每条命令的延迟降至 1 ms 以下)
  • 大型负载(>1 MB)——REST API 有负载大小限制

理念

Upstash 的存在,是因为无服务器和边缘运行时无法维持 TCP 连接。传统 Redis 客户端(ioredis、node-redis)依赖持久 TCP 套接字——它们在 Cloudflare Workers 中会失效,在生命周期短暂的 Lambda 函数中会出问题,也无法在浏览器/WebAssembly 环境中运行。Upstash 以 REST/HTTP 取代 TCP,以每条命令的延迟(约 5–15 ms,相比之下 TCP 低于 1 ms)换取广泛兼容性。

核心原则:

  1. 从设计上采用无连接模式——每条命令都是无状态的 HTTP 请求。无需连接池、拆除流程或连接数限制。这是特性,而非限制。
  2. 默认自动序列化——传入对象,取回对象。无需手动调用 JSON.stringify/JSON.parse。这简化了 90% 的用例,但对于期望原始字符串行为的开发者来说可能出乎意料。
  3. 使用管道提升性能——不使用管道时,N 条命令 = N 个 HTTP 请求。始终使用 redis.pipeline() 批处理互不依赖的命令,以减少往返次数。
  4. 将速率限制作为一等功能——@upstash/ratelimit 提供可用于生产环境的算法,无需编写 Lua 脚本。该库会在内部处理所有 Redis 操作。
  5. 基于推送的消息传递——QStash 通过 HTTP Webhook 向你的 API 传递消息。无需长期运行的消费者进程——非常适合无服务器环境。

核心模式

模式 1:使用 Redis.fromEnv() 配置客户端

使用环境变量初始化,实现零配置部署。有关包含构造函数选项和超时配置的完整示例,请参阅 examples/core.md。

// Good Example
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
// Reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN automatically

export { redis };

为何推荐: 零配置,平台(Vercel、Fly.io)注入环境变量,代码中不含密钥

// Bad Example
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: "https://us1-merry-cat-12345.upstash.io",
  token: "AXXXAAIgcDE...",
});

为何不推荐: 硬编码凭据会泄露到版本控制中,且无法在不同环境间移植


模式 2:自动 JSON 序列化

Upstash 在写入时使用 JSON.stringify 自动序列化对象,在读取时使用 JSON.parse 自动反序列化。有关类型安全模式和禁用自动序列化的说明,请参阅 examples/core.md。

// 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: "[email protected]",
    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

为何推荐: TypeScript 泛型提供类型安全,无需手动序列化,通过选项对象设置 TTL

// 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\"}"

为何不推荐: 自动序列化已经会调用 JSON.stringify——手动调用会导致双重编码,返回的字符串会转义 JSON


模式 3:Pipeline 批处理

将多个命令批量发送到单个 HTTP 请求中。不使用管道时,每条命令都需要单独往返一次(每次约 5-15ms)。有关带类型的 Pipeline 结果,请参阅 examples/core.md。

// 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", "[email protected]", { 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)

为何推荐: 3 条命令只需一次 HTTP 往返,泛型提供带类型的结果,TTL 使用命名常量

// Bad Example -- 3 separate HTTP requests
await redis.set("user:123:name", "Alice");
await redis.set("user:123:email", "[email protected]");
await redis.incr("stats:signups");
// 3 round-trips = ~15-45ms total vs ~5-15ms with pipeline

为何不推荐: 每个 await 都会发起单独的 HTTP 请求,在每一毫秒冷启动时间都很重要的无服务器环境中,延迟会增加三倍


模式 4:原子事务

当命令必须原子执行时,使用 redis.multi()。示例请参阅 examples/core.md。

// 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"]>();

为何推荐: 所有命令均以原子方式执行(不会被其他客户端的操作插入),结果带有类型

何时使用 Pipeline,何时使用事务:

  • Pipeline(redis.pipeline())——命令彼此独立,需要通过批处理提升速度,无需保证原子性
  • 事务(redis.multi())——所有命令必须一起成功,不允许插入其他操作

模式 5:使用 @upstash/ratelimit 进行速率限制

开箱即用的速率限制工具,负责处理所有 Redis 内部细节。有关所有算法、中间件集成和分析功能,请参阅 examples/rate-limiting.md。

// 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),
    },
  });
}

为何推荐: 无需 Lua 脚本,使用命名常量设置限制,提供分析功能以便监控,并通过标准标头返回规范的 429 响应


模式 6:QStash 后台作业

面向无服务器环境的推送式消息传递。有关调度、重试和接收方验证,请参阅 examples/qstash.md。

// 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",
});

为何推荐: 处理程序可以发起即发即弃的调用,失败时自动重试,可配置延迟,并保证至少一次投递


<decision_framework>

决策框架

Upstash 与 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)
选择哪种速率限制算法?
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)
Pipeline、事务还是顺序执行
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
全局数据库还是区域数据库
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>

危险信号

高优先级问题:

  • 在将对象传递给 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 一样,会导致内存无限增长,直到触发驱逐
  • 对写入密集型工作负载使用 Global Database——写入始终路由到主区域,且复制会使命令成本翻倍
  • 与非 Upstash 客户端交互时未设置 automaticDeserialization: false——其他客户端会存储原始字符串,Upstash 会无法将其解析为 JSON
  • 每个请求都创建新的 Redis 实例,而不是复用模块级单例——虽然无需连接,但客户端仍可从 HTTP keep-alive 和预热连接中受益

常见错误:

  • 预期 redis.get() 返回字符串,但存储的是对象——自动反序列化会返回原始对象类型,而不是 JSON 字符串
  • 误以为管道执行具有原子性——管道通过批处理提高网络效率,但其他客户端仍可穿插执行;需要原子性时请使用 redis.multi()
  • 将 Ratelimit.slidingWindow 与 MultiRegionRatelimit 搭配使用——滑动窗口在多区域设置中会造成较高的 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 的位置参数写法不起作用。
  • Global Database 是最终一致的:写入后立即从其他区域读取,可能会返回过时数据。请针对最终一致性进行设计,或使用区域数据库以实现强一致性。
  • 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,自动批处理同一事件循环周期内发出的命令。

</red_flags>


<critical_reminders>

关键提醒

所有代码都必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、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 请求造成不必要的延迟,以及不受支持的命令引发的运行时错误。

评论

支持 Markdown