-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9dc042c
Showing
41 changed files
with
2,240 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"use strict"; | ||
module.exports = require("./.eslintrc.base.js")(__dirname, { | ||
"import/no-extraneous-dependencies": "error", | ||
"@typescript-eslint/explicit-function-return-type": [ | ||
"error", | ||
{ allowExpressions: true }, | ||
], | ||
"lodash/import-scope": ["error", "method"], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: "CI Full Run" | ||
on: | ||
pull_request: | ||
branches: | ||
- main | ||
- grok/*/* | ||
push: | ||
branches: | ||
- main | ||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
node-version: ["20.x"] | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Use Node.js ${{ matrix.node-version }} | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
- run: npm install -g pnpm --force | ||
- run: pnpm install | ||
- run: pnpm run build | ||
- run: pnpm run lint | ||
- run: pnpm run test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Name of this GitHub Actions workflow. | ||
name: Semgrep | ||
|
||
on: | ||
# Scan changed files in PRs (diff-aware scanning): | ||
pull_request: | ||
branches: ['main'] | ||
|
||
# Schedule the CI job (this method uses cron syntax): | ||
schedule: | ||
- cron: '0 0 * * MON-FRI' | ||
|
||
jobs: | ||
semgrep: | ||
# User definable name of this GitHub Actions job. | ||
name: Scan | ||
# If you are self-hosting, change the following `runs-on` value: | ||
runs-on: ubuntu-latest | ||
|
||
container: | ||
# A Docker image with Semgrep installed. Do not change this. | ||
image: returntocorp/semgrep@sha256:6c7ab81e4d1fd25a09f89f1bd52c984ce107c6ff33affef6ca3bc626a4cc479b | ||
|
||
# Skip any PR created by dependabot to avoid permission issues: | ||
if: (github.actor != 'dependabot[bot]') | ||
|
||
steps: | ||
# Fetch project source with GitHub Actions Checkout. | ||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 | ||
# Run the "semgrep ci" command on the command line of the docker image. | ||
- run: semgrep ci | ||
env: | ||
# Connect to Semgrep Cloud Platform through your SEMGREP_APP_TOKEN. | ||
# Generate a token from Semgrep Cloud Platform > Settings | ||
# and add it to your GitHub secrets. | ||
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
dist | ||
|
||
# Common in both .gitignore and .npmignore | ||
node_modules | ||
package-lock.json | ||
yarn.lock | ||
pnpm-lock.yaml | ||
.DS_Store | ||
*.log | ||
*.tmp | ||
*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
dist/__tests__ | ||
dist/**/__tests__ | ||
dist/tsconfig.tsbuildinfo | ||
.npmrc | ||
|
||
# Common in both .gitignore and .npmignore | ||
node_modules | ||
package-lock.json | ||
yarn.lock | ||
pnpm-lock.yaml | ||
.DS_Store | ||
*.log | ||
*.tmp | ||
*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"overrides": [ | ||
{ | ||
"files": ["*.ts", "*.tsx"], | ||
"options": { "parser": "typescript" } | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"recommendations": [ | ||
"dbaeumer.vscode-eslint", | ||
"esbenp.prettier-vscode", | ||
"mhutchie.git-graph", | ||
"trentrand.git-rebase-shortcuts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "git grok: push local commits as individual PRs", | ||
"detail": "Install git-grok first: https://github.com/dimikot/git-grok", | ||
"type": "shell", | ||
"command": "git grok", | ||
"problemMatcher": [], | ||
"hide": false | ||
}, | ||
{ | ||
"label": "git rebase --interactive", | ||
"detail": "Opens a UI for interactive rebase (install \"Git rebase shortcuts\" extension).", | ||
"type": "shell", | ||
"command": "GIT_EDITOR=\"code --wait\" git rebase -i", | ||
"problemMatcher": [] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 Mango Technologies, Inc. DBA ClickUp | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# distributed-pacer: A concurrency aware Redis-backed rate limiter with pacing delay prediction and Token Bucket bursts handling | ||
|
||
See also [Full API documentation](https://github.com/clickup/distributed-pacer/blob/master/docs/modules.md). | ||
|
||
![CI run](https://github.com/clickup/distributed-pacer/actions/workflows/ci.yml/badge.svg?branch=main) | ||
|
||
## Pacing | ||
|
||
Pacing controls the rate at which concurrent clients perform some operations. It | ||
introduces deliberate delays between client requests. The primary goal of pacing | ||
is to ensure that the rate of operations (such as outgoing requests) does not | ||
exceed a certain threshold (e.g. QPS - "queries per second"). | ||
|
||
Use Cases: | ||
|
||
- **Outgoing Requests.** Pacing is typically used by clients to manage the rate | ||
at which they send requests to an external service that imposes rate limits. | ||
- **Load Management.** In scenarios where the external service might be | ||
sensitive to sudden spikes in traffic, pacing helps in distributing the load | ||
more evenly over time. | ||
|
||
Notice that the term "pacing" is typically used for outgoing requests from | ||
clients (to slow down the requests flow without dropping them), whilst "rate | ||
limiting" is for incoming requests on servers (to reject non-conforming | ||
requests). | ||
|
||
### Pacing Usage Example | ||
|
||
```ts | ||
import { DistributedPacer } from "@clickup/distributed-pacer"; | ||
import { Redis } from "ioredis"; | ||
import { setTimeout } from "timers/promises"; | ||
|
||
const myIoRedis = new Redis(); | ||
|
||
async function mySendApiRequest() { | ||
// sends an API request somewhere | ||
} | ||
|
||
async function myWorkerRunningOnMultipleMachines() { | ||
while (true) { | ||
const lightweightPacer = new DistributedPacer(myIoRedis, { | ||
key: "myKey", | ||
qps: 10, | ||
maxBurst: 1, // optional | ||
burstAllowanceFactor: 0.5, // optional | ||
}); | ||
const outcome = await lightweightPacer.pace(1 /* weight */); | ||
|
||
console.log(outcome.reason); | ||
await setTimeout(outcome.delayMs); | ||
|
||
await mySendApiRequest(); | ||
} | ||
} | ||
``` | ||
|
||
### How it Works | ||
|
||
`DistributedPacer` spreads the requests issued by some concurrent workers or | ||
processes uniformly into the future to satisfy the desired downstream QPS | ||
(queries per second) exactly. The implementation is inspired by Leaky Bucket for | ||
Queues algorithm. | ||
|
||
The general use case is to introduce some artificial back-pressure when sending | ||
requests to external services, to avoid overloading them, e.g.: | ||
|
||
- Pacing outgoing requests to some external API to meet its rate limits. | ||
- Protecting the local database from overloading with concurrent writes done by | ||
multiple workers. | ||
|
||
Imagine we have a time machine, and we can send requests (events) into the exact | ||
provided moment of time in the future. To send a request into the future, the | ||
Lua script in Redis returns that moment's timestamp, and then the worker needs | ||
to call delay() to wake up at that moment. We also store the last moment of the | ||
future to where we sent a previous request, so next requests coming (if they | ||
come too quickly) will be sent further and further away. | ||
|
||
Another analogy is booking a meeting in the calendar. When a new request | ||
arrives, it's not executed immediately, but instead scheduled in the calendar | ||
according to the QPS allowance. | ||
|
||
Thus, after the returned `delayMs` is awaited, the request will happen in at | ||
least 1/QPS seconds after the previous request; thus, it will satisfy the target | ||
QPS. Also, if there were no requests in the past within 1/QPS seconds from the | ||
present time, then `delayMs` returned will be 0. | ||
|
||
### Bursts Allowance | ||
|
||
Imagine that each call to `pace(weight)` adds `weight` of water to the bucket of | ||
`maxBurst` volume, and every second, `1/qps*burstAllowanceFactor` of water leaks | ||
out of the bucket at a constant rate (but only when the pacer is idle, i.e. | ||
there are no requests scheduled to the future on top of the bucket). If the | ||
bucket is not yet full (its watermark is below `maxBurst` level), then the | ||
returned `delayMs` will be 0, so the worker can proceed with the request | ||
immediately. Otherwise, pacing will start to happen. I.e. we pace only the | ||
requests which cause the bucket to overflow (Leaky Bucket algorithm). | ||
|
||
The default value of `burstAllowanceFactor` is less than 1, which forces the | ||
burst allowance to be earned slightly slower than the target QPS. | ||
|
||
## Rate Limiting | ||
|
||
Although pacing is the primary use case for this module, it also supports "rate | ||
limiting" mode, where it's expected that requests out of quota will be rejected | ||
(instead of being delayed). This is useful for handling *incoming* requests on | ||
servers (as opposed to pacing, where the requests *originate* from workers). | ||
|
||
To use `DistributedPacer` in rate limiting mode, call `rateLimit()` method on | ||
it. Logically, it works exactly the same way as `pace()`, but when it returns a | ||
non-zero `delayMs`, it doesn't alter the state in Redis, assuming that the | ||
request will be rejected and won't contribute to `maxBurst` allowance. | ||
|
||
To utilize the power of Leaky Bucket algorithm (or its equivalent here, Token | ||
Bucket), pass a nonzero value to `maxBurst`. With the default value (which is | ||
0), no bursts will be allowed, so the requests will need to come in not less | ||
than `1/qps` seconds in between. | ||
|
||
### Rate Limiting Usage Example | ||
|
||
Disclaimer: here, we use Express just as an illustration: there is obviously a | ||
ready middleware module for Express rate limiting use case. Use | ||
`DistributedPacer` in other applications, like GraphQL processing, WebSockets, | ||
internal IO services etc. | ||
|
||
```ts | ||
import { DistributedPacer } from "@clickup/distributed-pacer"; | ||
import { Redis } from "ioredis"; | ||
import express from "express"; | ||
|
||
const myIoRedis = new Redis(); | ||
|
||
express() | ||
.get('/', (req, res) => { | ||
const lightweightPacer = new DistributedPacer(myIoRedis, { | ||
key: "myKey", | ||
qps: 10, | ||
maxBurst: 20, | ||
}); | ||
const outcome = await lightweightPacer.rateLimit(1 /* weight */); | ||
|
||
if (outcome.delay > 0) { | ||
console.log(outcome.reason); | ||
res.status(429).send(`Rate limited, try again in ${outcome.delay} ms.`); | ||
} else { | ||
res.send("Hello World!") | ||
} | ||
}) | ||
.listen(port); | ||
``` | ||
|
||
## Performance | ||
|
||
This module is cheap and can be put on a critical path in your application. | ||
|
||
`DistributedPacer` objects are lightweight, so you can create them as often as | ||
you want (even on every request). | ||
|
||
Each call to `pace()` or `rateLimit()` causes one round-trip to Redis (it runs a | ||
custom Lua function), and the timing of that call is O(1). | ||
|
||
If you need to use multiple keys, you can use Redis in cluster mode to spread | ||
those keys across multiple Redis nodes (pass an instance of `Redis.Cluster` to | ||
`DistributedPacer` constructor). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Security | ||
|
||
Keeping our clients' data secure is an absolute top priority at ClickUp. Our goal is to provide a secure environment, while also being mindful of application performance and the overall user experience. | ||
|
||
ClickUp believes effective disclosure of security vulnerabilities requires mutual trust, respect, transparency and common good between ClickUp and Security Researchers. Together, our vigilant expertise promotes the continued security and privacy of ClickUp customers, products, and services. | ||
|
||
If you think you've found a security vulnerability in any ClickUp-owned repository, please let us know as outlined below. | ||
|
||
ClickUp defines a security vulnerability as an unintended weakness or exposure that could be used to compromise the integrity, availability or confidentiality of our products and services. | ||
|
||
## Our Commitment to Reporters | ||
|
||
- **Trust**. We maintain trust and confidentiality in our professional exchanges with security researchers. | ||
- **Respect**. We treat all researchers with respect and recognize your contribution for keeping our customers safe and secure. | ||
- **Transparency**. We will work with you to validate and remediate reported vulnerabilities in accordance with our commitment to security and privacy. | ||
- **Common Good**. We investigate and remediate issues in a manner consistent with protecting the safety and security of those potentially affected by a reported vulnerability. | ||
|
||
## What We Ask of Reporters | ||
|
||
- **Trust**. We request that you communicate about potential vulnerabilities in a responsible manner, providing sufficient time and information for our team to validate and address potential issues. | ||
- **Respect**. We request that researchers make every effort to avoid privacy violations, degradation of user experience, disruption to production systems, and destruction of data during security testing. | ||
- **Transparency**. We request that researchers provide the technical details and background necessary for our team to identify and validate reported issues, using the form below. | ||
- **Common Good**. We request that researchers act for the common good, protecting user privacy and security by refraining from publicly disclosing unverified vulnerabilities until our team has had time to validate and address reported issues. | ||
|
||
## Vulnerability Reporting | ||
|
||
ClickUp recommends that you share the details of any suspected vulnerabilities across any asset owned, controlled, or operated by ClickUp (or that would reasonably impact the security of ClickUp and our users) using our vulnerability disclosure form at <http://clickup.com/bug-bounty>. The ClickUp Security team will acknowledge receipt of each valid vulnerability report, conduct a thorough investigation, and then take appropriate action for resolution. | ||
|
||
## Safe Harbor | ||
|
||
When conducting vulnerability research according to this policy, we consider this research to be: | ||
|
||
- Authorized in accordance with the Computer Fraud and Abuse Act (CFAA) (and/or similar state laws), and we will not initiate or support legal action against you for accidental, good faith violations of this policy; | ||
- Exempt from the Digital Millennium Copyright Act (DMCA), and we will not bring a claim against you for circumvention of technology controls; | ||
- Exempt from restrictions in our Terms & Conditions that would interfere with conducting security research, and we waive those restrictions on a limited basis for work done under this policy; and | ||
- Lawful, helpful to the overall security of the Internet, and conducted in good faith. | ||
- You are expected, as always, to comply with all applicable laws. | ||
|
||
If at any time you have concerns or are uncertain whether your security research is consistent with this policy, please inquire via <[email protected]> before going any further. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
services: | ||
redis: | ||
image: bitnami/redis:6.2.11 | ||
ports: | ||
- ${REDISCLI_PORT:?err}:6379 | ||
environment: | ||
- "ALLOW_EMPTY_PASSWORD=yes" | ||
- "REDIS_PASSWORD=bitnami" | ||
command: | ||
- bash | ||
- "-c" | ||
- | | ||
/opt/bitnami/scripts/redis/run.sh \ | ||
--appendonly yes \ | ||
--auto-aof-rewrite-percentage 100 \ | ||
--auto-aof-rewrite-min-size 104857600 \ | ||
--maxclients 40000 \ | ||
--loglevel warning | ||
healthcheck: | ||
test: ["CMD", "redis-cli", "ping"] | ||
interval: 1s | ||
timeout: 20s | ||
retries: 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. |
Oops, something went wrong.