Give each session a budget on the data path
Only sign-in and the account endpoints were rate limited. JMAP, blob downloads and the image and calendar proxies had no budget at all, and the proxy is one Node process that saturates a core at roughly 2,000 operations a second -- measured at 110% CPU under 150 concurrent users. One signed-in account looping requests could slow every other user on the instance. Each session now gets API_RATE_LIMIT requests a minute on those routes, 1,200 by default: twenty a second sustained, well above what a busy tab does and an order of magnitude below where one tab starts to hurt the rest. Over budget returns 429 with Retry-After. Sign-in keeps its own, separate limiter. Checked in situ: one session driven flat out was cut off after exactly 1,200 requests, and with API_RATE_LIMIT=0 throughput at 50 users is unchanged.
This commit is contained in:
@@ -82,3 +82,19 @@ test("advertised upstream URLs are pinned to the configured origin", async () =>
|
||||
// A relative URL still resolves against the base, as before.
|
||||
assert.equal(absoluteUpstream("/jmap/", "http://stalwart:8080/"), "http://stalwart:8080/jmap/");
|
||||
});
|
||||
|
||||
test("the data path is rate limited per session, and login stays on its own budget", async () => {
|
||||
// No session: every call is refused before the limiter, so it must never 429.
|
||||
const app = createApp();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const res = await app.request("/api/jmap", { method: "POST",
|
||||
headers: { "content-type": "application/json", "x-requested-with": "ihasmail" }, body: "{}" });
|
||||
assert.equal(res.status, 401);
|
||||
}
|
||||
// The limiter itself: a fresh key gets its budget and nothing more.
|
||||
const { RateLimiter } = await import("./ratelimit.js");
|
||||
const l = new RateLimiter(3, 60_000);
|
||||
assert.deepEqual([l.check("s1"), l.check("s1"), l.check("s1"), l.check("s1")], [true, true, true, false]);
|
||||
assert.ok(l.retryAfterSeconds("s1") >= 1);
|
||||
assert.equal(l.check("s2"), true, "another session is not affected");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user