Files
UHD_Media_Tool/packages/db/migrations/0041_rate_limit.ts
T
rootandClaude Opus 5.5 61f585a707 feat(sites): cross-process site rate limit via PG token bucket
Migration 0041_rate_limit adds rate_limit_bucket. PgTokenBucket (@umt/db)
refills and takes tokens under SELECT ... FOR UPDATE on the DB clock and
polls with jitter within a wait budget; penalize() carries Retry-After.
Site adapters get createSharedSiteRateLimiter (key site:<id>), shared by
api / worker / worker:agent, with an in-process serial queue in front and
a local fallback when the bucket is unavailable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:51:32 +08:00

31 lines
1.2 KiB
TypeScript

import type { Kysely } from "kysely";
import { dropTables, execDdl } from "../src/migration-helpers.ts";
// C2-A 跨进程站点限流:PG 令牌桶(api / worker / worker:agent 共用)。
// - key:`site:<站点 id>` 等;tokens 为上次结算时的令牌数(可为负,表示被 Retry-After 顺延);
// - capacity / refill_per_sec 由调用方每次扣令牌时带上(站点限流设置变化即时生效);
// - blocked_until:站点要求暂停(429 Retry-After)时在此之前不发放令牌。
// 扣令牌走 `select … for update`,补充按 DB 时钟(clock_timestamp)结算,多进程时钟不一致也不影响。
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
export async function up(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
create table rate_limit_bucket (
key text primary key,
tokens double precision not null,
capacity double precision not null check (capacity > 0),
refill_per_sec double precision not null check (refill_per_sec > 0),
blocked_until timestamptz,
updated_at timestamptz not null default now()
);
`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: 同上
export async function down(db: Kysely<any>): Promise<void> {
await execDdl(db, dropTables(["rate_limit_bucket"]));
}