Skip to content

Commit

Permalink
test: dynamic boundaries (#6)
Browse files Browse the repository at this point in the history
* test: dynamic boundaries

* feat: global sliding window

* fix: delete line

* ci: load secrets in env

* docs: add global ratelimit

* test: do not sanitize

* test: wait for requests in global tests

* test: boundaries

* test: boundaries

* style: fmt

* style: fmt

* style: fmt

* style: fmt

* style: fmt

* style: fmt

* style: fmt

* test: testcases
  • Loading branch information
chronark committed May 10, 2022
1 parent e1fc97a commit d23da03
Show file tree
Hide file tree
Showing 15 changed files with 3,747 additions and 140 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ jobs:
# with:
# deno-version: v1.x
- run: curl -fsSL https://deno.land/x/install/install.sh | sh

- run: echo "$HOME/.deno/bin" > $GITHUB_PATH

- name: Verify formatting
Expand All @@ -35,10 +34,6 @@ jobs:

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
algorithm: ["fixedWindow", "slidingWindow", "tokenBucket"]
name: Tests
steps:
- name: Setup repo
Expand All @@ -64,4 +59,9 @@ jobs:
env:
UPSTASH_REDIS_REST_URL: http://127.0.0.1:6379
UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN }}
TEST_ONLY: ${{ matrix.algorithm }}
EU2_UPSTASH_REDIS_REST_URL: ${{ secrets.EU2_UPSTASH_REDIS_REST_URL }}
EU2_UPSTASH_REDIS_REST_TOKEN: ${{ secrets.EU2_UPSTASH_REDIS_REST_TOKEN }}
APN_UPSTASH_REDIS_REST_URL: ${{ secrets.APN_UPSTASH_REDIS_REST_URL }}
APN_UPSTASH_REDIS_REST_TOKEN: ${{ secrets.APN_UPSTASH_REDIS_REST_TOKEN }}
US1_UPSTASH_REDIS_REST_URL: ${{ secrets.US1_UPSTASH_REDIS_REST_URL }}
US1_UPSTASH_REDIS_REST_TOKEN: ${{ secrets.US1_UPSTASH_REDIS_REST_TOKEN }}
80 changes: 79 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ An HTTP/REST based Redis client built on top of Upstash REST API.

[![Tests](https://github.com/upstash/ratelimit/actions/workflows/tests.yaml/badge.svg)](https://github.com/upstash/ratelimit/actions/workflows/tests.yaml)
![npm (scoped)](https://img.shields.io/npm/v/@upstash/ratelimit)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/@upstash/ratelimit)

It is the only connectionless (HTTP based) ratelimiter and designed for:

Expand All @@ -17,6 +16,38 @@ It is the only connectionless (HTTP based) ratelimiter and designed for:
- WebAssembly
- and other environments where HTTP is preferred over TCP.

<!-- toc -->

- [Quick Start](#quick-start)
- [Install](#install)
- [npm](#npm)
- [Deno](#deno)
- [Create database](#create-database)
- [Use it](#use-it)
- [Block until ready](#block-until-ready)
- [Globally replicated ratelimiting](#globally-replicated-ratelimiting)
- [Usage](#usage)
- [Example](#example)
- [Ratelimiting algorithms](#ratelimiting-algorithms)
- [Fixed Window](#fixed-window)
- [Pros:](#pros)
- [Cons:](#cons)
- [Usage:](#usage)
- [Sliding Window](#sliding-window)
- [Pros:](#pros-1)
- [Cons:](#cons-1)
- [Usage:](#usage-1)
- [Token Bucket](#token-bucket)
- [Pros:](#pros-2)
- [Cons:](#cons-2)
- [Usage:](#usage-2)
- [Contributing](#contributing)
- [Install Deno](#install-deno)
- [Database](#database)
- [Running tests](#running-tests)

<!-- tocstop -->

## Quick Start

### Install
Expand Down Expand Up @@ -124,6 +155,51 @@ doExpensiveCalculation();
return "Here you go!";
```

## Globally replicated ratelimiting

Using a single redis instance has the downside of providing low latencies to the
part of your userbase closest to the deployed db. That's why we also built
`GlobalRatelimit` which replicates the state across multiple redis databases as
well as offering lower latencies to more of your users.

`GlobalRatelimit` does this by checking the current limit in the closest db and
returning immediately. Only afterwards will the state be asynchronously
replicated to the other datbases leveraging
[CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type). Due
to the nature of distributed systems, there is no way to guarantee the set
ratelimit is not exceeded by a small margin. This is the tradeoff for reduced
global latency.

### Usage

The api is the same, except for asking for multiple redis instances:

```ts
import { GlobalRatelimit } from "@upstash/ratelimit"; // for deno: see above
import { Redis } from "@upstash/redis";

// Create a new ratelimiter, that allows 10 requests per 10 seconds
const ratelimit = new GlobalRatelimit({
redis: [
new Redis({/* auth */}),
new Redis({/* auth */}),
new Redis({/* auth */}),
],
limiter: Ratelimit.slidingWindow(10, "10 s"),
});

// Use a constant string to limit all requests with a single ratelimit
// Or use a userID, apiKey or ip address for individual limits.
const identifier = "api";
const { success } = await ratelimit.limit(identifier);
```

### Example

Let's assume you have customers in the US and Europe. In this case you can
create 2 regional redis databases on [Upastash](https://console.upstash.com) and
your users will enjoy the latency of whichever db is closest to them.

## Ratelimiting algorithms

We provide different algorithms to use out of the box. Each has pros and cons.
Expand Down Expand Up @@ -199,6 +275,8 @@ const ratelimit = new Ratelimit({

### Token Bucket

_Not yet supported for `GlobalRatelimit`_

Consider a bucket filled with `{maxTokens}` tokens that refills constantly at
`{refillRate}` per `{interval}`. Every request will remove one token from the
bucket and if there is no token to take, the request is rejected.
Expand Down
Empty file added examples/cf-worker/.cargo-ok
Empty file.
3 changes: 3 additions & 0 deletions examples/cf-worker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
.mf
19 changes: 19 additions & 0 deletions examples/cf-worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Miniflare Example Project

This is an example [Cloudflare Workers](https://workers.cloudflare.com/) project
that uses [Miniflare](https://github.com/cloudflare/miniflare) for local
development, [TypeScript](https://www.typescriptlang.org/),
[esbuild](https://github.com/evanw/esbuild) for bundling, and
[Jest](https://jestjs.io/) for testing, with
[Miniflare's custom Jest environment](https://v2.miniflare.dev/jest.html).

```shell
# Install dependencies
$ npm install
# Start local development server with live reload
$ npm run dev
# Run tests
$ npm test
# Run type checking
$ npm run types:check
```
4 changes: 4 additions & 0 deletions examples/cf-worker/bindings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Bindings {
UPSTASH_REDIS_REST_URL: string;
UPSTASH_REDIS_REST_TOKEN: string;
}
20 changes: 20 additions & 0 deletions examples/cf-worker/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import path from "path";
import { fileURLToPath } from "url";
import { build } from "esbuild";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

try {
await build({
bundle: true,
sourcemap: true,
format: "esm",
target: "esnext",
entryPoints: [path.join(__dirname, "src", "index.ts")],
outdir: path.join(__dirname, "dist"),
outExtension: { ".js": ".mjs" },
});
} catch {
process.exitCode = 1;
}
25 changes: 25 additions & 0 deletions examples/cf-worker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "miniflare-typescript-esbuild-jest",
"version": "1.0.0",
"description": "Example project using Miniflare, TypeScript, esbuild and Jest",
"type": "module",
"module": "./dist/index.mjs",
"scripts": {
"build": "node build.js",
"dev": "miniflare --live-reload --debug",
"types:check": "tsc && tsc -p test/tsconfig.json"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@cloudflare/workers-types": "^3.1.1",
"esbuild": "^0.13.13",
"miniflare": "^2.0.0",
"typescript": "^4.4.4"
},
"dependencies": {
"@upstash/ratelimit": "../../dist",
"@upstash/redis": "^1.3.4"
}
}
Loading

0 comments on commit d23da03

Please sign in to comment.