Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new example for rate limiting API routes. #19509

Merged
merged 3 commits into from
Jan 4, 2021

Conversation

leerob
Copy link
Member

@leerob leerob commented Nov 25, 2020

Adds an example using lru-cache to implement a simple rate limiter for API routes (Serverless Functions).

Demo: https://nextjs-rate-limit.vercel.app/

@ijjk ijjk added the examples Issue/PR related to examples label Nov 25, 2020
@vercel vercel bot temporarily deployed to Preview November 25, 2020 03:29 Inactive
@ijjk
Copy link
Member

ijjk commented Nov 25, 2020

Stats from current PR

Default Server Mode
General
vercel/next.js canary leerob/next.js rate-limit Change
buildDuration 11.2s 11.1s -140ms
nodeModulesSize 84.9 MB 84.9 MB
Page Load Tests Overall increase ✓
vercel/next.js canary leerob/next.js rate-limit Change
/ failed reqs 0 0
/ total time (seconds) 2.62 2.592 -0.03
/ avg req/sec 954.35 964.47 +10.12
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.771 1.782 ⚠️ +0.01
/error-in-render avg req/sec 1411.64 1402.7 ⚠️ -8.94
Client Bundles (main, webpack, commons)
vercel/next.js canary leerob/next.js rate-limit Change
677f882d2ed8..8b81.js gzip 12.8 kB 12.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-fbd7082..8b7a.js gzip 6.54 kB 6.54 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 59 kB 59 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary leerob/next.js rate-limit Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary leerob/next.js rate-limit Change
_app-3b0cf13..85f8.js gzip 1.28 kB 1.28 kB
_error-6f635..c393.js gzip 3.44 kB 3.44 kB
hooks-d4ffc3..9e0f.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-b618194..5477.js gzip 1.61 kB 1.61 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 8.01 kB 8.01 kB
Client Build Manifests
vercel/next.js canary leerob/next.js rate-limit Change
_buildManifest.js gzip 321 B 321 B
Overall change 321 B 321 B
Rendered Page Sizes
vercel/next.js canary leerob/next.js rate-limit Change
index.html gzip 614 B 614 B
link.html gzip 621 B 621 B
withRouter.html gzip 608 B 608 B
Overall change 1.84 kB 1.84 kB

Serverless Mode
General
vercel/next.js canary leerob/next.js rate-limit Change
buildDuration 13s 12.8s -154ms
nodeModulesSize 84.9 MB 84.9 MB
Client Bundles (main, webpack, commons)
vercel/next.js canary leerob/next.js rate-limit Change
677f882d2ed8..8b81.js gzip 12.8 kB 12.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-fbd7082..8b7a.js gzip 6.54 kB 6.54 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 59 kB 59 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary leerob/next.js rate-limit Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary leerob/next.js rate-limit Change
_app-3b0cf13..85f8.js gzip 1.28 kB 1.28 kB
_error-6f635..c393.js gzip 3.44 kB 3.44 kB
hooks-d4ffc3..9e0f.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-b618194..5477.js gzip 1.61 kB 1.61 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 8.01 kB 8.01 kB
Client Build Manifests
vercel/next.js canary leerob/next.js rate-limit Change
_buildManifest.js gzip 321 B 321 B
Overall change 321 B 321 B
Serverless bundles
vercel/next.js canary leerob/next.js rate-limit Change
_error.js 915 kB 915 kB
404.html 2.67 kB 2.67 kB
hooks.html 1.92 kB 1.92 kB
index.js 915 kB 915 kB
link.js 973 kB 973 kB
routerDirect.js 966 kB 966 kB
withRouter.js 966 kB 966 kB
Overall change 4.74 MB 4.74 MB
Commit: 2bd58c9

@leerob
Copy link
Member Author

leerob commented Nov 25, 2020

I'm going to update this to use Redis or something else persisted 👍

Copy link
Member

@Timer Timer left a comment

Choose a reason for hiding this comment

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

LGTM for now

@leerob
Copy link
Member Author

leerob commented Jan 4, 2021

Yeah I think Redis would be nice but this is better than nothing!

@ijjk
Copy link
Member

ijjk commented Jan 4, 2021

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General
vercel/next.js canary leerob/next.js rate-limit Change
buildDuration 10.7s 10.6s -46ms
nodeModulesSize 80.6 MB 80.6 MB
Page Load Tests Overall increase ✓
vercel/next.js canary leerob/next.js rate-limit Change
/ failed reqs 0 0
/ total time (seconds) 2.07 2.132 ⚠️ +0.06
/ avg req/sec 1208 1172.6 ⚠️ -35.4
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.324 1.282 -0.04
/error-in-render avg req/sec 1888.08 1949.36 +61.28
Client Bundles (main, webpack, commons)
vercel/next.js canary leerob/next.js rate-limit Change
677f882d2ed8..396f.js gzip 13 kB 13 kB
framework.HASH.js gzip 39 kB 39 kB
main-6b712d3..3360.js gzip 6.63 kB 6.63 kB
webpack-50be..df5b.js gzip 751 B 751 B
Overall change 59.3 kB 59.3 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary leerob/next.js rate-limit Change
polyfills-81..14d7.js gzip 31.2 kB 31.2 kB
Overall change 31.2 kB 31.2 kB
Client Pages
vercel/next.js canary leerob/next.js rate-limit Change
_app-b6fc6bc..222c.js gzip 1.28 kB 1.28 kB
_error-e2ffa..0f3f.js gzip 3.46 kB 3.46 kB
hooks-010c20..8411.js gzip 887 B 887 B
index-bbee2f..528b.js gzip 227 B 227 B
link-705099c..c35d.js gzip 1.64 kB 1.64 kB
routerDirect..bf84.js gzip 303 B 303 B
withRouter-a..5826.js gzip 302 B 302 B
Overall change 8.09 kB 8.09 kB
Client Build Manifests
vercel/next.js canary leerob/next.js rate-limit Change
_buildManifest.js gzip 323 B 323 B
Overall change 323 B 323 B
Rendered Page Sizes
vercel/next.js canary leerob/next.js rate-limit Change
index.html gzip 615 B 615 B
link.html gzip 620 B 620 B
withRouter.html gzip 608 B 608 B
Overall change 1.84 kB 1.84 kB

Serverless Mode
General
vercel/next.js canary leerob/next.js rate-limit Change
buildDuration 12.7s 12.6s -162ms
nodeModulesSize 80.6 MB 80.6 MB
Client Bundles (main, webpack, commons)
vercel/next.js canary leerob/next.js rate-limit Change
677f882d2ed8..396f.js gzip 13 kB 13 kB
framework.HASH.js gzip 39 kB 39 kB
main-6b712d3..3360.js gzip 6.63 kB 6.63 kB
webpack-50be..df5b.js gzip 751 B 751 B
Overall change 59.3 kB 59.3 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary leerob/next.js rate-limit Change
polyfills-81..14d7.js gzip 31.2 kB 31.2 kB
Overall change 31.2 kB 31.2 kB
Client Pages
vercel/next.js canary leerob/next.js rate-limit Change
_app-b6fc6bc..222c.js gzip 1.28 kB 1.28 kB
_error-e2ffa..0f3f.js gzip 3.46 kB 3.46 kB
hooks-010c20..8411.js gzip 887 B 887 B
index-bbee2f..528b.js gzip 227 B 227 B
link-705099c..c35d.js gzip 1.64 kB 1.64 kB
routerDirect..bf84.js gzip 303 B 303 B
withRouter-a..5826.js gzip 302 B 302 B
Overall change 8.09 kB 8.09 kB
Client Build Manifests
vercel/next.js canary leerob/next.js rate-limit Change
_buildManifest.js gzip 323 B 323 B
Overall change 323 B 323 B
Serverless bundles
vercel/next.js canary leerob/next.js rate-limit Change
_error.js 1 MB 1 MB
404.html 2.67 kB 2.67 kB
hooks.html 1.92 kB 1.92 kB
index.js 1 MB 1 MB
link.js 1.06 MB 1.06 MB
routerDirect.js 1.05 MB 1.05 MB
withRouter.js 1.05 MB 1.05 MB
Overall change 5.17 MB 5.17 MB
Commit: 3465b9e

@ijjk
Copy link
Member

ijjk commented Jan 4, 2021

Stats from current PR

Default Server Mode (Decrease detected ✓)
General
vercel/next.js canary leerob/next.js rate-limit Change
buildDuration 8.5s 8.3s -201ms
nodeModulesSize 80.6 MB 80.6 MB
Page Load Tests Overall decrease ⚠️
vercel/next.js canary leerob/next.js rate-limit Change
/ failed reqs 0 0
/ total time (seconds) 1.734 1.684 -0.05
/ avg req/sec 1441.92 1484.99 +43.07
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.03 1.149 ⚠️ +0.12
/error-in-render avg req/sec 2426.48 2175.06 ⚠️ -251.42
Client Bundles (main, webpack, commons)
vercel/next.js canary leerob/next.js rate-limit Change
677f882d2ed8..396f.js gzip 13 kB 13 kB
framework.HASH.js gzip 39 kB 39 kB
main-6b712d3..3360.js gzip 6.63 kB 6.63 kB
webpack-50be..df5b.js gzip 751 B 751 B
Overall change 59.3 kB 59.3 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary leerob/next.js rate-limit Change
polyfills-81..14d7.js gzip 31.2 kB 31.2 kB
Overall change 31.2 kB 31.2 kB
Client Pages
vercel/next.js canary leerob/next.js rate-limit Change
_app-b6fc6bc..222c.js gzip 1.28 kB 1.28 kB
_error-e2ffa..0f3f.js gzip 3.46 kB 3.46 kB
hooks-010c20..8411.js gzip 887 B 887 B
index-bbee2f..528b.js gzip 227 B 227 B
link-705099c..c35d.js gzip 1.64 kB 1.64 kB
routerDirect..bf84.js gzip 303 B 303 B
withRouter-a..5826.js gzip 302 B 302 B
Overall change 8.09 kB 8.09 kB
Client Build Manifests
vercel/next.js canary leerob/next.js rate-limit Change
_buildManifest.js gzip 323 B 323 B
Overall change 323 B 323 B
Rendered Page Sizes
vercel/next.js canary leerob/next.js rate-limit Change
index.html gzip 615 B 615 B
link.html gzip 620 B 620 B
withRouter.html gzip 608 B 608 B
Overall change 1.84 kB 1.84 kB

Serverless Mode
General
vercel/next.js canary leerob/next.js rate-limit Change
buildDuration 10.1s 10s -31ms
nodeModulesSize 80.6 MB 80.6 MB
Client Bundles (main, webpack, commons)
vercel/next.js canary leerob/next.js rate-limit Change
677f882d2ed8..396f.js gzip 13 kB 13 kB
framework.HASH.js gzip 39 kB 39 kB
main-6b712d3..3360.js gzip 6.63 kB 6.63 kB
webpack-50be..df5b.js gzip 751 B 751 B
Overall change 59.3 kB 59.3 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary leerob/next.js rate-limit Change
polyfills-81..14d7.js gzip 31.2 kB 31.2 kB
Overall change 31.2 kB 31.2 kB
Client Pages
vercel/next.js canary leerob/next.js rate-limit Change
_app-b6fc6bc..222c.js gzip 1.28 kB 1.28 kB
_error-e2ffa..0f3f.js gzip 3.46 kB 3.46 kB
hooks-010c20..8411.js gzip 887 B 887 B
index-bbee2f..528b.js gzip 227 B 227 B
link-705099c..c35d.js gzip 1.64 kB 1.64 kB
routerDirect..bf84.js gzip 303 B 303 B
withRouter-a..5826.js gzip 302 B 302 B
Overall change 8.09 kB 8.09 kB
Client Build Manifests
vercel/next.js canary leerob/next.js rate-limit Change
_buildManifest.js gzip 323 B 323 B
Overall change 323 B 323 B
Serverless bundles
vercel/next.js canary leerob/next.js rate-limit Change
_error.js 1 MB 1 MB
404.html 2.67 kB 2.67 kB
hooks.html 1.92 kB 1.92 kB
index.js 1 MB 1 MB
link.js 1.06 MB 1.06 MB
routerDirect.js 1.05 MB 1.05 MB
withRouter.js 1.05 MB 1.05 MB
Overall change 5.17 MB 5.17 MB
Commit: 58b7278

@kodiakhq kodiakhq bot merged commit 5678d24 into vercel:canary Jan 4, 2021
@cbdeveloper
Copy link

cbdeveloper commented Jul 24, 2021

Thanks @leerob . I guess I'll be implementing something similar.

I have some questions, though:

uniqueTokenPerInterval: 500, // Max 500 users per second

What does "500 users per second" mean? What would that equate to in total number of requests/interval? Because from what I've tested, both my PC and my phone (with different IPs) were sharing the same 10 req/60s limit.

Is this IP-based? Would you suggest using req.ip for the 'CACHE_TOKEN' ?

Are serverless functions on Vercel auto-scalable? For example, if a DDOS attack is happening, wouldn't it spin multiple instances of that API function? Will the attacker get an extra rate-limit per instance? How would that work? Hope my question is making sense.

@leerob leerob deleted the rate-limit branch July 25, 2021 01:36
Comment on lines +12 to +16
const tokenCount = tokenCache.get(token) || [0]
if (tokenCount[0] === 0) {
tokenCache.set(token, tokenCount)
}
tokenCount[0] += 1
Copy link

Choose a reason for hiding this comment

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

An observation for other passers-by looking to implement something similar. 😅

This bit looks a little hacky to me. (To the point where I don't understand how using get rather than peek doesn't actually seem to break this, while it kind of should?) If we're avoiding updating the "recently used"-ness of the values, wouldn't a plain Map work, given that the elements are in insertion order? Looks like we're asking an LRU cache to be a FIFO cache (with some timestamp-related behavior on get and set). Something like the following might be enough for this use-case?

type WithTimestamp<Value> = { value: Value, timestamp: number }
type Options = { max: number, maxAge: number }

const Cache = <Key, Value>(options: Options) => {
  const map = new Map<Key, WithTimestamp<Value>>()

  return {
    get: (key: Key): Value | undefined => {
      const element = map.get(key)
      if (!element) return undefined
      if (Date.now() - element.timestamp > options.maxAge) {
        map.delete(key)
        return undefined
      }
      return element.value
    },
    set: (key: Key, value: Value): void => {
      if (map.size === options.max) map.delete(map.keys().next().value)
      const timestamp = map.get(key)?.timestamp ?? Date.now()
      map.set(key, { value, timestamp })
    },
    clear: (): void => map.clear(),
    size: (): number => map.size
  }
}

@vercel vercel locked as resolved and limited conversation to collaborators Jan 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
examples Issue/PR related to examples
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants