src | ||
.dockerignore | ||
.gitignore | ||
Cargo.lock | ||
Cargo.toml | ||
Dockerfile | ||
LICENSE.md | ||
README.md |
Motivation
mCaptcha uses a leaky- bucket-enabled counter to keep track of traffic/challenge requests.
-
At
t=0
(wheret
is time), if someone is visiting an mCaptcha-protected website, the counter for that website will be initialized and set to 1. -
It should also automatically decrement(by 1) after a certain period, say
t=cooldown
. We call this cool down period and is constant for a website. -
If at
t=x
(wherex<cooldown
), another user visits the same website, the counter becomes 2 and will auto decrement att = cooldown + x
for second user.Note that, for the decrement to work, we require two different timers that goes off at two different instants. The current(
v0.1.3
) oflibmcaptcha
implements this with internal data structures and timers --- something that can't be shared across several machines in a distributed setting.So we figured we'd use Redis to solve this problem and get synchronisation and persistence for free.
This Redis module implements auto decrement on a special data type(which is also defined in this module).
How does it work?
If a timer is supposed to go off to
decrement key myCounter
at t=y
(where y is an instant in future),
-
A hashmap called
mcaptcha_cache:decrement:y
(prefix might vary) is created with key-value pairskeyName: DecrementCount
(myCounter: 1
in our case) -
A timer will be created to go off at
t=y
-
Any further decrement operations that are scheduled for
t=y
are registered with the same hashmap(mcaptcha_cache:decrement:y
). -
At
t=y
, a procedure will be executed to read all values of the hashmap(mcaptcha_cache:decrement:y
) and performs all registered decrements. When its done, it cleans itself up.
This way, we are not spinning timers for every decrement operation but instead, one for every "time pocket".
Gotchas:
This module creates and manages data of two types:
mcaptcha_cache:captcha:y
wherey
(last character) is variablemcaptcha_cache:pocket:x
wherex
(last character) is variable
WARNING: Please don't modify these manually. If you do so, then Redis will panic
This module is capable of cleaning up after itself so manual clean up is unnecessary. If you have needs that are not met my this module and you which access/mutate data manually, please open an issue. I'd be happy to help.
Usage
There are two ways to run cache
:
Docker
Use image from DockerHub:
$ docker run -p 6379:6379 mcaptcha/cache:0.1.1-alpha
or build from source:
Build
$ docker build -t mcaptcha/cache:latest .
Run
$ docker run -p 6379:6379 mcaptcha/cache:latest
Bare-metal
Build
Make sure you have Rust installed: https://www.rust-lang.org/tools/install
Then, build as usual:
cargo build --release
Run
redis-server --loadmodule ./target/release/libcache.so
Commands
Every counter has a name and a leak-rate in seconds.
Create/Increment counter
If counter exists, then count is incremented. Otherwise, it is created.
MCAPTCHA_CACHE.COUNT <counter-name> <leak-rate>
Benchmark
NOTE: These benchmarks are for reference only. Do not depend upon them too much. When in doubt, please craft and run benchmarks that are better suited to your workload.
- platform:
Intel core i7-9750h
With request pipelining
➜ ~ redis-benchmark -n 1000000 -t set,get -P 16 -q # set and get are for baseline/reference
SET: 835421.88 requests per second, p50=0.759 msec
GET: 987166.81 requests per second, p50=0.711 msec
➜ ~ redis-benchmark -n 1000000 -P 16 -q MCAPTCHA_CACHE.COUNT mycounter 45
MCAPTCHA_CACHE.COUNT mycounter 45: 280504.91 requests per second, p50=2.743 msec
Without request pipelining
➜ ~ redis-benchmark -n 1000000 -t set,get -q # set and get are for baseline/reference
SET: 87062.51 requests per second, p50=0.311 msec
GET: 87252.41 requests per second, p50=0.311 msec
➜ ~ redis-benchmark -n 1000000 -q MCAPTCHA_CACHE.COUNT mycounter 45
MCAPTCHA_CACHE.COUNT mycounter 45: 87214.38 requests per second, p50=0.471 msec