Skip to content

Commit 857d80b

Browse files
authored
Merge pull request #114 from codex-team/rate-limits
Rate limits
2 parents 4009bfd + 433a30f commit 857d80b

File tree

18 files changed

+547
-19
lines changed

18 files changed

+547
-19
lines changed

.env.docker

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ HAWK_TOKEN=
2020

2121
ACCOUNTS_MONGODB_URI=mongodb://mongodb:27017/hawk
2222
TOKEN_UPDATE_PERIOD=30s
23+
PROJECTS_LIMITS_UPDATE_PERIOD=10s
2324

2425
REDIS_URL=redis:6379
2526
REDIS_PASSWORD=

.env.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ HAWK_TOKEN=
2020

2121
ACCOUNTS_MONGODB_URI=mongodb://localhost:27017/hawk
2222
TOKEN_UPDATE_PERIOD=30s
23+
PROJECTS_LIMITS_UPDATE_PERIOD=30s
2324

2425
REDIS_URL=localhost:6379
2526
REDIS_PASSWORD=

.github/workflows/go-test.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Go Tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
11+
- name: Set up Go
12+
uses: actions/setup-go@v4
13+
with:
14+
go-version: '1.21'
15+
16+
- name: Install dependencies
17+
run: go mod download
18+
19+
- name: Run tests
20+
run: go test -v ./...

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@
1515
venv
1616
.DS_Store
1717
bin/hawk.collector
18+
bin/golangci-lint
1819
.env

Makefile

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@ DOCKER_IMAGE=hawk.collector
66

77
export GO111MODULE=on
88

9-
all: check lint build
9+
GOLANGCI_LINT_VERSION=v1.63.4
10+
GOLANGCI_LINT=bin/golangci-lint
11+
12+
$(GOLANGCI_LINT):
13+
@echo "Installing golangci-lint..."
14+
@mkdir -p bin
15+
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ./bin $(GOLANGCI_LINT_VERSION)
16+
17+
all: lint build
1018

1119
build:
1220
go build -o $(BINARY_NAME) -v ./
1321
chmod +x $(BINARY_NAME)
14-
check:
15-
gometalinter --vendor --fast --enable-gc --tests --aggregate --disable=gotype --disable=gosec ./
1622
test:
1723
go test ./...
18-
lint:
19-
golint cmd/... lib/... ./
24+
lint: $(GOLANGCI_LINT)
25+
./$(GOLANGCI_LINT) run ./...
2026
clean:
2127
go clean
2228
rm -rf $(BINARY_NAME)

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,70 @@ Basic configuration is taken from `.env` file.
162162
| BLACKLIST_UPDATE_PERIOD | 15s | Time interval to update blacklist |
163163
| BLACKLIST_THRESHOLD | 10000 | Amount of requests, which, when achieved, forces IP to get blocked |
164164
| NOTIFY_URL | https://notify.bot.ifmo.su/u/ABCD1234 | Address to send alerts in case of too many requests |
165+
| TOKEN_UPDATE_PERIOD | 10s | Time interval to update token cache |
166+
| PROJECTS_LIMITS_UPDATE_PERIOD | 3600 | Time interval to update projects limits cache (in seconds) |
167+
# Rate Limiting
168+
169+
Rate limiting is implemented using Redis to track and enforce request limits per project. The system supports configurable limits at the project, workspace and plan level.
170+
171+
## Configuration
172+
173+
Rate limits can be configured at multiple levels and applied in the following order (highest to lowest):
174+
175+
1. Project level - Individual project-specific limits
176+
2. Workspace level - Limits that apply to all projects in a workspace
177+
3. Plan level - Default limits from the workspace's tariff plan
178+
179+
## Implementation
180+
181+
Rate limits are tracked in `rate_limit` Redis set with the following pattern:
182+
183+
```go
184+
// Key: "project_id" -> value: "timestamp:count"
185+
// example: "6762b5db032b200023854b2c" -> "1737483572:5"
186+
```
187+
188+
Each project's rate limit data contains:
189+
- Timestamp of the current window
190+
- Request count in the current window
191+
192+
### Rate Limit Parameters
193+
194+
Two main parameters control the rate limiting:
195+
196+
- `EventsLimit` - Maximum number of events allowed in the period
197+
- `EventsPeriod` - Time window in seconds for the limit (in seconds)
198+
199+
## Configuration
200+
201+
Rate limits are fetched from MongoDB. You can find them in the `rateLimitSettings` field of the `plans,workspaces,projects` collections.
202+
203+
`rateLimitSettings` is object with two fields:
204+
- `N` - Maximum number of events allowed in the period (`int64`)
205+
- `T` - Time window in seconds for the limit (in seconds) (`int64`)
206+
207+
```json
208+
{
209+
"rateLimitSettings": {
210+
"N": {
211+
"$numberLong": "15"
212+
},
213+
"T": {
214+
"$numberLong": "100"
215+
}
216+
}
217+
}
218+
```
219+
220+
Rate limits are automatically enforced for all incoming error and release events. No additional configuration is needed at the client level.
221+
222+
When a rate limit is exceeded, clients will receive a response like:
223+
224+
```json
225+
{
226+
"code": 402,
227+
"error": true,
228+
"message": "Rate limit exceeded"
229+
}
230+
```
231+

bin/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
*
2-
!hawk.collector
32
!.gitignore

cmd/collector/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,13 @@ func (x *RunCommand) Execute(args []string) error {
9696
log.Errorf("failed to update token cache: %s", err)
9797
}
9898

99+
err = accountsClient.UpdateProjectsLimitsCache()
100+
if err != nil {
101+
log.Errorf("failed to update projects limits cache: %s", err)
102+
}
103+
99104
go periodic.RunPeriodically(accountsClient.UpdateTokenCache, cfg.TokenUpdatePeriod, doneAccountsContext)
105+
go periodic.RunPeriodically(accountsClient.UpdateProjectsLimitsCache, cfg.ProjectsLimitsUpdatePeriod, doneAccountsContext)
100106
defer close(doneAccountsContext)
101107

102108
// start HTTP and websocket server

cmd/config.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ type Config struct {
4444
RedisPassword string `env:"REDIS_PASSWORD"`
4545

4646
// MongoDB connection URI to the accounts database
47-
AccountsMongoDBURI string `env:"ACCOUNTS_MONGODB_URI"`
48-
TokenUpdatePeriod time.Duration `env:"TOKEN_UPDATE_PERIOD"`
49-
50-
RedisDisabledProjectsSet string `env:"REDIS_DISABLED_PROJECT_SET"`
51-
RedisBlacklistIPsSet string `env:"REDIS_BLACKLIST_IP_SET"`
52-
RedisAllIPsMap string `env:"REDIS_ALL_IPS_MAP"`
53-
RedisCurrentPeriodMap string `env:"REDIS_CURRENT_PERIOD_MAP"`
47+
AccountsMongoDBURI string `env:"ACCOUNTS_MONGODB_URI"`
48+
TokenUpdatePeriod time.Duration `env:"TOKEN_UPDATE_PERIOD" defaultEnv:"1m"`
49+
ProjectsLimitsUpdatePeriod time.Duration `env:"PROJECTS_LIMITS_UPDATE_PERIOD" defaultEnv:"1m"`
50+
RedisDisabledProjectsSet string `env:"REDIS_DISABLED_PROJECT_SET"`
51+
RedisBlacklistIPsSet string `env:"REDIS_BLACKLIST_IP_SET"`
52+
RedisAllIPsMap string `env:"REDIS_ALL_IPS_MAP"`
53+
RedisCurrentPeriodMap string `env:"REDIS_CURRENT_PERIOD_MAP"`
5454

5555
BlockedIDsLoad time.Duration `env:"BLOCKED_PROJECTS_UPDATE_PERIOD"`
5656

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module github.com/codex-team/hawk.collector
22

33
require (
4+
github.com/alicebob/miniredis/v2 v2.34.0
45
github.com/caarlos0/env/v6 v6.6.0
56
github.com/cenkalti/backoff/v4 v4.1.0
67
github.com/codex-team/hawk.go v1.0.5
@@ -14,6 +15,7 @@ require (
1415
github.com/savsgio/gotils v0.0.0-20210520110740-c57c45b83e0a // indirect
1516
github.com/sirupsen/logrus v1.8.1
1617
github.com/streadway/amqp v1.0.0
18+
github.com/stretchr/testify v1.7.0
1719
github.com/tidwall/gjson v1.8.0
1820
github.com/tidwall/sjson v1.1.6
1921
github.com/valyala/fasthttp v1.25.0

0 commit comments

Comments
 (0)