# Rate limiting

> Limit request rates on the server with the GCRA.LIMIT command on every plan, or named RATELIMIT.* limiters with sliding window, token bucket and leaky bucket algorithms.

URL: https://docs.hotkv.com/develop/rate-limiting/


Rate limiting caps how often something may happen: API calls per user,
login attempts per IP address, messages per channel. Doing the check on the
server, in one command, means every application instance shares the same
count and no two requests can slip through on the same stale value.

HotKV has two built-in options:

- [GCRA.LIMIT](https://docs.hotkv.com/commands/gcra-limit/index.md), a single command on any key, available
  on every plan and build.
- `RATELIMIT.*`, named limiters with a choice of algorithm and per-limiter
  statistics, an Enterprise feature.

Both replace the Lua scripts or `INCR` plus `EXPIRE` patterns commonly used
with Redis, which has no rate limiting command of its own.

## GCRA.LIMIT

**Availability:** All HotKV Cloud plans. On-Premise: no license needed. Also in the community build.

```
GCRA.LIMIT key period burst [count]
```

`GCRA.LIMIT` allows up to `burst` requests per `period` milliseconds for each
key, spread evenly: with a period of 60000 and a burst of 10, a client can
make 10 requests at once, then one more every 6 seconds. `count` (default 1)
is how many units this request uses. It replies with five integers:

| Position | Field | Meaning |
| --- | --- | --- |
| 1 | `denied` | `0` if the request is allowed, `1` if it is refused |
| 2 | `limit` | The burst size you passed |
| 3 | `remaining` | Requests still allowed right now |
| 4 | `retry_after_ms` | When denied, milliseconds until this request would be allowed; `-1` when allowed |
| 5 | `reset_after_ms` | Milliseconds until the key is back to a full burst |

```
127.0.0.1:6379> GCRA.LIMIT api:user:42 60000 10
1) (integer) 0
2) (integer) 10
3) (integer) 9
4) (integer) -1
5) (integer) 6000
```

After ten quick calls, the eleventh is refused:

```
127.0.0.1:6379> GCRA.LIMIT api:user:42 60000 10
1) (integer) 1
2) (integer) 10
3) (integer) 0
4) (integer) 6000
5) (integer) 60000
```

A denied request uses nothing, so retrying after `retry_after_ms` succeeds.
`GCRA.LIMIT` keeps its state in `key`, which expires
by itself once the key is back to a full burst. A key that already holds
something else (another type, or a string that is not a number) is refused
with `WRONGTYPE` and left alone. [GCRA.RESET](https://docs.hotkv.com/commands/gcra-reset/index.md)
`key [key ...]` deletes the state of one or more keys and replies with how
many existed. `period`, `burst` and `count` must be positive integers.

Limits carry over correctly to replicas, through restarts, and across a
failover.

**Python**

```python
import hotkv

client = hotkv.HotKV(host="localhost", port=6379)

result = client.gcra_limit("api:user:42", 60000, 10)
if result.denied:
    print(f"slow down, retry in {result.retry_after_ms} ms")
else:
    print(f"{result.remaining} requests left")
```
**Node.js**

```ts
import { HotKV } from "@hotkv/client";

const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });

const result = await client.gcraLimit("api:user:42", 60000, 10);
if (result.denied) {
  console.log(`slow down, retry in ${result.retryAfterMs} ms`);
} else {
  console.log(`${result.remaining} requests left`);
}
```
**Go**

```go
result, err := client.GCRALimit(ctx, "api:user:42", time.Minute, 10, 1).Result()
if err != nil {
    log.Fatal(err)
}
if result.Denied {
    fmt.Printf("slow down, retry in %d ms\n", result.RetryAfterMs)
} else {
    fmt.Printf("%d requests left\n", result.Remaining)
}
```
**Java**

```java
var result = client.gcra().limit("api:user:42", 60_000, 10);
if (result.denied()) {
    System.out.println("slow down, retry in " + result.retryAfterMillis() + " ms");
} else {
    System.out.println(result.remaining() + " requests left");
}
```

## RATELIMIT.* limiters

**Availability:** HotKV Cloud: Performance and Scale plans. On-Premise: requires the `rate-limiting` license feature.

`RATELIMIT.*` manages named limiters. You create a limiter once with its
algorithm and limits, then check any number of keys (user ids, IP addresses,
API keys) against it. Each limiter keeps its own allowed and denied totals.

| Command | What it does |
| --- | --- |
| [RATELIMIT.CREATE](https://docs.hotkv.com/commands/ratelimit-create/index.md) `name limit window_ms [ALGO algo] [REFILL rate] [DRAIN rate]` | Create a limiter |
| [RATELIMIT.CHECK](https://docs.hotkv.com/commands/ratelimit-check/index.md) `name key [COST n]` | Check a request for `key` and count it if allowed |
| [RATELIMIT.PEEK](https://docs.hotkv.com/commands/ratelimit-peek/index.md) `name key` | Same reply as `CHECK`, without counting anything |
| [RATELIMIT.RESET](https://docs.hotkv.com/commands/ratelimit-reset/index.md) `name key` | Forget one key's state |
| [RATELIMIT.STATS](https://docs.hotkv.com/commands/ratelimit-stats/index.md) `name` | The limiter's settings, number of keys, and allowed/denied totals |
| [RATELIMIT.DROP](https://docs.hotkv.com/commands/ratelimit-drop/index.md) `name` | Delete the limiter and all its state |
| [RATELIMIT.LIST](https://docs.hotkv.com/commands/ratelimit-list/index.md) | The names of all limiters |

### Algorithms

| `ALGO` | Behavior |
| --- | --- |
| `sliding_window` (default) | At most `limit` units in any `window_ms` period, without the burst at the edge that fixed windows allow |
| `token_bucket` | A bucket of `limit` tokens, refilled at `REFILL` tokens per second (default `limit` per `window_ms`); each request takes tokens, so bursts up to `limit` are allowed |
| `leaky_bucket` | A bucket of size `limit` that drains at `DRAIN` units per second (default `limit` per `window_ms`); each request adds to it, which smooths traffic to a steady rate |

### Checking requests

`RATELIMIT.CHECK` replies with five integers: `allowed` (`1` or `0`),
`remaining`, `retry_after_ms` (`0` when allowed), `limit`, and `current` (the
usage before this request).

```
127.0.0.1:6379> RATELIMIT.CREATE api 100 60000
OK
127.0.0.1:6379> RATELIMIT.CHECK api user:42
1) (integer) 1
2) (integer) 99
3) (integer) 0
4) (integer) 100
5) (integer) 0
127.0.0.1:6379> RATELIMIT.CHECK api user:42 COST 5
1) (integer) 1
2) (integer) 94
3) (integer) 0
4) (integer) 100
5) (integer) 1
127.0.0.1:6379> RATELIMIT.STATS api
 1) "name"
 2) "api"
 3) "algo"
 4) "sliding_window"
 5) "limit"
 6) (integer) 100
 7) "window_ms"
 8) (integer) 60000
 9) "unique_keys"
10) (integer) 1
11) "total_allowed"
12) (integer) 6
13) "total_denied"
14) (integer) 0
```

A token bucket allowing bursts of 20 and a steady 5 requests per second:

```
127.0.0.1:6379> RATELIMIT.CREATE uploads 20 4000 ALGO token_bucket REFILL 5
OK
```

> **Caution**
> `RATELIMIT.CHECK` and `RATELIMIT.PEEK` against a limiter that does not exist
> reply with all zeros (`allowed` is `0`) rather than an error. Create your
> limiters at startup, and treat a reply whose `limit` is `0` as a
> configuration problem.

`RATELIMIT.CREATE` fails with `ERR limiter already exists or limit reached`
when the name is taken or the server already holds its maximum number of
limiters (`ratelimit.max-limiters` in the enterprise configuration).
`RESET`, `STATS` and `DROP` reply `ERR limiter not found` for an unknown name.

Limiters and their per-key state are saved with snapshots and kept in step on
replicas. In a cluster, a limiter lives on the node that serves the slot of
its name, and all of its keys are checked there; `RATELIMIT.LIST` lists the
limiters of the node you ask.

**Python**

```python
client.ratelimit_create("api", 100, 60000)

check = client.ratelimit_check("api", "user:42", cost=1)
if not check.allowed:
    print(f"retry in {check.retry_after_ms} ms")
```
**Node.js**

```ts
await client.rateLimitCreate("api", 100, 60000);

const check = await client.rateLimitCheck("api", "user:42");
if (!check.allowed) {
  console.log(`retry in ${check.retryAfterMs} ms`);
}
```
**Go**

```go
client.RateLimitCreate(ctx, "api", 100, time.Minute, nil)

check, err := client.RateLimitCheck(ctx, "api", "user:42", 1).Result()
if err == nil && !check.Allowed {
    fmt.Printf("retry in %d ms\n", check.RetryAfterMs)
}
```
**Java**

```java
client.rateLimit().create("api", 100, 60_000, null, null, null);

var check = client.rateLimit().check("api", "user:42", null);
if (!check.allowed()) {
    System.out.println("retry in " + check.retryAfterMillis() + " ms");
}
```

## Choosing between them

- Use `GCRA.LIMIT` for most request throttling. It needs no setup, works on
  every plan, and each key is an ordinary key that expires by itself.
- Use `RATELIMIT.*` when you want a choice of algorithm, cost-based checks
  against shared limiter settings, a read-only `PEEK`, or allowed and denied
  totals per limiter.

## Patterns

**Per user and per endpoint.** Put both in the key, for example
`rl:{user:42}:search`, so each user has a separate budget for each endpoint.
The hash tag keeps one user's limits on the same cluster node.

**Several limits at once.** Check a short and a long window, for example 10
per second and 1000 per hour, and allow the request only if both allow it.
Check the stricter one first to avoid using up the other on requests that
will be refused anyway.

**Tell the client when to retry.** Return `retry_after_ms` to the caller, for
example as an HTTP `Retry-After` header (in seconds, rounded up), so
well-behaved clients wait instead of retrying in a tight loop.

**Weighted requests.** Pass `count` to `GCRA.LIMIT`, or `COST` to
`RATELIMIT.CHECK`, to charge expensive operations more than cheap ones.