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]>
31 lines
1.2 KiB
TypeScript
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"]));
|
|
}
|