Skip to content

feat: add function to manually check rate limit#392

Merged
Eomm merged 12 commits intofastify:mainfrom
Charioteer:master
May 18, 2025
Merged

feat: add function to manually check rate limit#392
Eomm merged 12 commits intofastify:mainfrom
Charioteer:master

Conversation

@Charioteer
Copy link
Copy Markdown
Contributor

@Charioteer Charioteer commented Nov 10, 2024

Hey fastify contributors,

Following on from issue #346 and my comment, I finally had some spare time to implement a draft version of my idea as suggested by @mcollina. I realized that any store (child) instance receives all the options provided to fastify.rateLimit when it is constructed, which is why I had to adjust my initial idea and come up with a slightly different solution.

I did not provide any documentation or additional tests yet (all existing tests run green tho), because I wanted to ask for your feedback first.

Please let me know what you think and as soon as everything looks good to you, I will finalize my PR with proper docs and tests.

Thanks and cheers
Patrick

How to Use

import Fastify from "fastify";
import { fastifyRateLimit } from "@fastify/rate-limit";

const fastify = Fastify();

await fastify.register(fastifyRateLimit, {
  global: false,
  max: 10,
  timeWindow: "10 seconds",
});

const checkRateLimit = fastify.createRateLimit(); // this will use the global options provided to fastifyRateLimit

fastify.get("/", async (request, reply) => {
  const limit = await checkRateLimit(request);

  if(!limit.isAllowed && limit.isExceeded) {
    return reply.code(429).send("Limit exceeded");
  }

  return reply.send("Hello world");
});


const checkCustomRateLimit = fastify.createRateLimit({ max: 100 }); // max provided to override global options

fastify.get("/custom", async (request, reply) => {
  const limit = await checkCustomRateLimit(request);

  if(!limit.isAllowed && limit.isExceeded) {
    return reply.code(429).send("Limit exceeded");
  }

  return reply.send("Hello world");
});

Checklist

@Fdawgs Fdawgs requested a review from Copilot November 20, 2024 19:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 2 out of 2 changed files in this pull request and generated no suggestions.

Comments skipped due to low confidence (3)

index.js:285

  • The variable timeWindowString is only assigned a value if params.timeWindow is a number. Ensure it is always defined or handle the undefined case properly.
timeWindowString = ms.format(params.timeWindow, true)

index.js:193

  • [nitpick] The function name createLimiterArgs is ambiguous. Consider renaming it to generateLimiterArguments for better clarity.
function createLimiterArgs (pluginComponent, globalParams, options) {

index.js:216

  • Ensure that the new behavior introduced by the applyRateLimit function is covered by tests.
async function applyRateLimit (pluginComponent, params, req) {

Copy link
Copy Markdown
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Finish it up (be careful that CI is failing).

@Charioteer Charioteer marked this pull request as ready for review February 3, 2025 22:07
@Charioteer
Copy link
Copy Markdown
Contributor Author

Hi @mcollina,

after @umakantp has closed their PR #410 because of the "authorship problem", I thought of putting more pressure on myself to finally get this done. I added unit tests, type tests, as well as documentation about the usage of this feature. I also merged the latest commit of fastify-rate-limit to my fork already to avoid conflicts. LMKWYT.

At this point, I also would like to appreciate @umakantp again for continuing this PR and putting time and effort into it. Please excuse me if I caused any trouble on your side.

@umakantp
Copy link
Copy Markdown

Hi @mcollina If you can please take a look and merge if things are good?

@Charioteer Charioteer requested a review from mcollina February 18, 2025 22:21
@gurgunday
Copy link
Copy Markdown
Member

It looks interesting, I'll take a look at this tomorrow

Copy link
Copy Markdown
Member

@Eomm Eomm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this change is awesome!

A minor suggestion, but LGTM!

Copy link
Copy Markdown
Member

@gurgunday gurgunday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run a benchmark using the basic example in the examples folder with autocannon?

https://github.com/fastify/fastify-rate-limit/blob/main/example/example-simple.mjs

@Charioteer
Copy link
Copy Markdown
Contributor Author

Hi @gurgunday,

sorry for yet anoter late reply...

I wasn't sure which kind of benchmarks you'd expect. I still ran two benchmarks with autocannon and its default settings on my machine with my initial version without @Eomm's suggestion:

Running 10s test @ http://localhost:3000
10 connections


┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max   │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 0 ms │ 0 ms │ 1 ms  │ 1 ms │ 0.05 ms │ 0.29 ms │ 16 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg       │ Stdev    │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
│ Req/Sec   │ 14,959  │ 14,959  │ 18,431  │ 19,039  │ 18,150.91 │ 1,103.65 │ 14,959  │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
│ Bytes/Sec │ 4.42 MB │ 4.42 MB │ 6.95 MB │ 7.18 MB │ 6.73 MB   │ 751 kB   │ 4.42 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴─────────┘

Req/Bytes counts sampled once per second.
# of samples: 11

10000 2xx responses, 189656 non 2xx responses
200k requests in 11.02s, 74 MB read

and with @Eomm's suggestion:

Running 10s test @ http://localhost:3000
10 connections


┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max   │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 0 ms │ 0 ms │ 1 ms  │ 1 ms │ 0.05 ms │ 0.24 ms │ 18 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg       │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Req/Sec   │ 15,791  │ 15,791  │ 18,399  │ 19,247  │ 18,348.73 │ 897.8  │ 15,785  │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Bytes/Sec │ 4.73 MB │ 4.73 MB │ 6.93 MB │ 7.25 MB │ 6.81 MB   │ 673 kB │ 4.73 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴────────┴─────────┘

Req/Bytes counts sampled once per second.
# of samples: 11

10000 2xx responses, 191806 non 2xx responses
202k requests in 11.02s, 74.9 MB read

Machine and Versions

  • Machine (MacBook Pro 2019):
    • CPU: 2,3 GHz 8-Core Intel Core i9
    • GPU: AMD Radeon Pro 5500M 4 GB
    • RAM: 16 GB 2667 MHz DDR4
    • OS: Sonoma 14.5
  • Node v22.14.0
  • Fastify 5.3.0

@umakantp
Copy link
Copy Markdown

umakantp commented May 6, 2025

Looks like this has fallen behind on the priority list? @gurgunday ?

@gurgunday
Copy link
Copy Markdown
Member

Hey, sorry for the late reply, just to get an idea, can you run the same on master too? I will approve if there isn't a huge regression

@Charioteer
Copy link
Copy Markdown
Contributor Author

Charioteer commented May 8, 2025

Hey @gurgunday,

I ran the benchmark again for main@latest and for my PR in sequence so that the comparison is as fair as possible. System and versions did not change.

TL;DR

My PR has about 2.9% less throughput. Let me know if that is too much regression. I did not check whether there is room for improvement yet.

Master

Running 10s test @ http://localhost:3000
10 connections


┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max   │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 0 ms │ 0 ms │ 1 ms  │ 1 ms │ 0.05 ms │ 0.32 ms │ 22 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg       │ Stdev    │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
│ Req/Sec   │ 14,175  │ 14,175  │ 19,119  │ 19,423  │ 18,633.82 │ 1,436.04 │ 14,174  │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
│ Bytes/Sec │ 4.12 MB │ 4.12 MB │ 7.21 MB │ 7.32 MB │ 6.91 MB   │ 888 kB   │ 4.12 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴─────────┘

Req/Bytes counts sampled once per second.
# of samples: 11

10000 2xx responses, 194989 non 2xx responses
205k requests in 11.02s, 76.1 MB read

My PR

Running 10s test @ http://localhost:3000
10 connections


┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max   │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 0 ms │ 0 ms │ 1 ms  │ 1 ms │ 0.05 ms │ 0.32 ms │ 23 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg       │ Stdev    │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
│ Req/Sec   │ 15,399  │ 15,399  │ 18,159  │ 19,151  │ 18,119.64 │ 1,019.03 │ 15,392  │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
│ Bytes/Sec │ 4.58 MB │ 4.58 MB │ 6.85 MB │ 7.22 MB │ 6.72 MB   │ 707 kB   │ 4.58 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴─────────┘

Req/Bytes counts sampled once per second.
# of samples: 11

10000 2xx responses, 189305 non 2xx responses
199k requests in 11.02s, 73.9 MB read

Copy link
Copy Markdown
Member

@gurgunday gurgunday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 2% is acceptable, we should be able to improve this further with some optimizations but it lgtm currently for this PR

@gurgunday
Copy link
Copy Markdown
Member

Thanks for the PR

We should still wait for at least another approval

@gurgunday gurgunday requested review from a team, Eomm and climba03003 May 9, 2025 11:03
Copy link
Copy Markdown
Member

@Fdawgs Fdawgs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking until I can find time to review, from brief glance the docs could be refined.

Fdawgs added 3 commits May 13, 2025 18:33
Signed-off-by: Frazer Smith <frazer.dev@icloud.com>
Signed-off-by: Frazer Smith <frazer.dev@icloud.com>
Signed-off-by: Frazer Smith <frazer.dev@icloud.com>
Co-authored-by: Manuel Spigolon <behemoth89@gmail.com>
Signed-off-by: Frazer Smith <frazer.dev@icloud.com>
@Fdawgs Fdawgs linked an issue May 16, 2025 that may be closed by this pull request
2 tasks
@Fdawgs Fdawgs changed the title Add function to manually check rate limit (#346) feat: add function to manually check rate limit May 16, 2025
Signed-off-by: Manuel Spigolon <behemoth89@gmail.com>
@Eomm Eomm merged commit c993058 into fastify:main May 18, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add parser option

7 participants