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
@@ -131,6 +131,19 @@ describe.skipIf(!up)("来源管理与站点工作台 API", () => {
|
||||
expect(observed.length).toBe(r.newCount);
|
||||
const site = await json(await h.req(`/sites/${siteId}`));
|
||||
expect(site.body.data.lastSnapshotAt).not.toBeNull();
|
||||
// 限流间隔 > 0 时令牌在 PG 跨进程共享(rate_limit_bucket,key = site:<id>);0 = 不限流只走本地
|
||||
await h.db
|
||||
.updateTable("site")
|
||||
.set({ rate_limit: JSON.stringify({ minIntervalMs: 5, concurrency: 5 }) })
|
||||
.where("id", "=", siteId)
|
||||
.execute();
|
||||
expect((await snapshotSite(h.rt, { siteId, kind: "manual" })).status).toBe("ok");
|
||||
const bucket = await h.db
|
||||
.selectFrom("rate_limit_bucket")
|
||||
.selectAll()
|
||||
.where("key", "=", `site:${siteId}`)
|
||||
.executeTakeFirst();
|
||||
expect(bucket).toMatchObject({ capacity: 5, refill_per_sec: 200 });
|
||||
});
|
||||
|
||||
it("工作台列表:快照优先,标注来源与更新时间;筛选与分页", async () => {
|
||||
|
||||
Reference in new issue
Block a user