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]>
This commit is contained in:
1 parent
57346d438c
commit
61f585a707
12 files changed
+466
-5
No files matched your search
+21
@@ -0,0 +1,21 @@
|
||||
// 子进程:对同一 key 连续扣 N 个令牌,每次成功输出一行时间戳(毫秒)。由 rate-limit.test.ts 以 Deno 子进程启动。
|
||||
import { Kysely, PostgresDialect } from "kysely";
|
||||
import pg from "pg";
|
||||
import { PgTokenBucket } from "../../src/rate-limit.ts";
|
||||
|
||||
const [url, schema, key, n, capacity, rate] = Deno.args as [string, string, string, string, string, string];
|
||||
const db = new Kysely<unknown>({
|
||||
dialect: new PostgresDialect({
|
||||
pool: new pg.Pool({ connectionString: url, max: 1, options: `-c search_path=${schema},public` }),
|
||||
}),
|
||||
});
|
||||
const bucket = new PgTokenBucket(db);
|
||||
try {
|
||||
for (let i = 0; i < Number(n); i++) {
|
||||
const r = await bucket.acquire(key, 1, { capacity: Number(capacity), refillPerSec: Number(rate), waitMs: 30_000 });
|
||||
if (!r.ok) throw new Error("等待超时");
|
||||
console.log(Date.now());
|
||||
}
|
||||
} finally {
|
||||
await db.destroy();
|
||||
}
|
||||
Reference in new issue
Block a user