Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 140 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,140 @@
# limitron
Go rate limiter
# Limitron

[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![GoDoc](https://pkg.go.dev/badge/github.com/iryndin/limitron.svg)](https://pkg.go.dev/github.com/iryndin/limitron)
[![Go Report Card](https://goreportcard.com/badge/github.com/iryndin/limitron)](https://goreportcard.com/report/github.com/iryndin/limitron)

**Limitron** is a lean, lock-free, zero-allocation, garbage-collector-friendly rate limiter designed for ultra-high cardinality use cases (e.g., per-IP, per-user, per-API key).

It encodes the entire limiter state into a single `uint64` and is designed for extreme performance, minimal memory overhead, and safe concurrent access in Go applications.

## 1. Features

- **Zero allocations** per operation
- **GC-friendly**: Stores all state in a plain `uint64`
- **Lock-free**, non-blocking design with atomic CAS retries
- **Concurrency-safe** for shared use by multiple goroutines
- Customizable rate limits per time interval
- Designed for **high cardinality** scenarios (millions of limiters)
- Suitable for **per-identity** limits: IPs, users, API keys, etc.

## 2. Installation

```bash
go get github.com/iryndin/limitron
```

## 3. Concept

**Limitron** uses a token bucket-like algorithm encoded into a single `uint64`, split as follows:
- High 16 bits: number of available tokens (requests). 16 bits allow to store number up to `65536` - that is a maximum number of requests/tokens stored by rate limiter state.
- Low 48 bits: timestamp of last update in Unix milliseconds. Millisecond precision packed in 48 bites allows time interval up to `8925 years`

```
64 bits: [ 16-bit tokens ][ 48-bit timestamp in ms ]
```

Limiter state is updated atomically using CAS operations.
Refill logic is based on elapsed time since the last update and configured refill rate.

## 4. Usage

### 4.1. Import

```go
import "github.com/iryndin/limitron"
```

### 4.2. Example: Per-Second Rate Limit

```go
limiter := limitron.CreateLeanRateLimiterRps(10) // 10 requests per second
state := limiter.CreateNewRl()

if limiter.Take1IfAllowed(&state) {
// Allowed – process request
} else {
// Denied – rate limit exceeded
}
```


### 4.3. Example: Custom Interval Rate Limit

```go
limiter := limitron.CreateLeanRateLimiter(100, time.Minute) // 100 reqs per minute
state := limiter.CreateNewRl()

allowed := limiter.TakeNIfAllowed(&state, 5) // Try to take 5 tokens
if allowed {
// Allowed
} else {
// Rate limit hit
}
```

### 4.4. Example: Free and Paid plan rate limiting

```go
import (
"math/rand"
"net/http"

"github.com/iryndin/limitron"
)

const FreePlanAPIRateLimit = 1
const PaidPlanAPIRateLimit = 10

var freeRateLimiter = limitron.CreateLeanRateLimiterRps(FreePlanAPIRateLimit) // 1 rps
var paidRateLimiter = limitron.CreateLeanRateLimiterRps(PaidPlanAPIRateLimit) // 10 rps

var userRateLimitMap = make(map[string]*uint64, 10)

func getRateLimiterForUser(token string) limitron.LeanRateLimiter {
if rand.Intn(2) == 0 {
return freeRateLimiter
} else {
return paidRateLimiter
}
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
apiToken := r.Header.Get("Authorization")
rateLimiter := getRateLimiterForUser(apiToken)

if rl, ok := userRateLimitMap[apiToken]; !ok {
rlval := rateLimiter.CreateNewRl()
userRateLimitMap[apiToken] = &rlval
} else {
if rateLimiter.Take1IfAllowed(rl) {
// hande API call normally
} else {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
}
}
```

## 5. Internals

**Limitron** represents rate limiter state using a compact 64-bit integer:

```
64 bits: [ 16-bit tokens ][ 48-bit timestamp in ms ]
```

Refill logic:
* At each call, it calculates tokens based on now - last_timestamp and a precomputed tokens/ms rate.
* Capped by a burst size (maxreq).

CAS loop with configurable retries ensures safe concurrent mutation of shared limiter state.


## 6. Best Practices

* Store `rl uint64` values in maps keyed by user/IP/key.
* Use separate `LeanRateLimiter` instances for each configuration (they are stateless). E.g. create one instance of `LeanRateLimiter` for free plan users, and another `LeanRateLimiter` instance for paid plan users with higher rate.
* Do not share `rl` values across identities.

Loading