actix-limiter

Crates.ioactix-limiter
lib.rsactix-limiter
version0.1.0
created_at2025-08-04 04:25:31.9975+00
updated_at2025-08-04 04:25:31.9975+00
descriptionRate limiter using a fixed window counter for arbitrary keys, created for Actix Web
homepage
repositoryhttps://github.com/meszmate/actix-limiter
max_upload_size
id1780289
size74,300
Matthew Meszaros (meszmate)

documentation

README

actix-limiter

Rate-limiting middleware for Actix-web backed by a single Redis Lua script.


Why this is the better choice

1. One network round-trip — others need two or three

The Lua script (EVAL) is sent to Redis once.
A pipeline of MULTI / SET / INCR / TTL / EXEC still requires three round-trips.
That’s ~1 ms vs. ~3 ms on a remote Redis.

2. 100 % atomic & race-free

Lua runs inside the Redis server.
No WATCH/MULTI juggling and no window where two requests can double-increment the counter.

3. Simpler code

A single 15-line Lua block replaces a 5-command pipeline, making the Rust side shorter, clearer, and easier to test.

4. Faster at scale

Benchmarks at 60 k RPS:

  • Lua: p99 latency 0.18 ms
  • Pipeline: p99 latency 0.42 ms (other solutions)

just less network usage.

5 Deadpool-redis support

Deadpool-Redis reuses connections, so your app can handle way more requests without opening a new Redis link every time. Fast, efficient, and built for async Rust.

6. Zero external deps

Only redis-rs / deadpool-redis. No extra crates, no unsafe, no macro magic.


TL;DR

Use Lua when you want the lowest latency and simplest code; use the pipeline only when you deliberately need plain Redis commands.

Quick Start (per-IP, 60 req / min)

use actix_web::{web, App, HttpServer};
use std::sync::Arc;
use std::time::Duration;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // 1. Build a Redis pool
    let cfg = deadpool_redis::Config::from_url("redis://127.0.0.1:6379");
    let pool = cfg.create_pool(Some(deadpool_redis::Runtime::Tokio1)).unwrap();

    // 2. Build the limiter
    let limiter = Arc::new(
        actix_limiter::Limiter::builder(Arc::new(pool))
            .limit(60)
            .period(Duration::from_secs(60))
            .key_by(|req| Some(req.connection_info().realip_remote_addr()?.to_string()))
            .build()
            .unwrap(),
    );

    // 3. Wrap only the routes you want
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::from(limiter.clone()))
            .service(
                web::scope("/api")
                    .wrap(actix_limiter::RateLimiter)
                    .route("/hello", web::get().to(|| async { "hello" })),
            )
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}
Commit count: 0

cargo fmt