Seeking a data structure for distributed counting in C++ for Frequency Cap implementation

66 Views Asked by At

I'm currently working on a project that involves implementing a frequency cap for user actions, and I'm facing some performance issues with my current solution. I would greatly appreciate your expertise and suggestions on how to optimize it. Here's an excerpt of my current code:

auto keyName = "cap:user_id";

increment.push_back(Redis::Command(
    "INCR",
    keyName
));
increment.push_back(Redis::Command(
    "EXPIRE",
    keyName,
    "86400",
    "NX"
));

Redis::Results results = redis.execMulti(increment);

Then, before every action, I perform a Redis GET on the key and check how many times that user ID performed the action. To provide a bit of context, I'm using C++ for this project, and I initially chose Redis for managing the distributed counter since it's a reliable tool. However, I've observed that the performance is not meeting the desired standards for our frequency cap code. Specifically, the counter updates are slower than expected, but also the GETs.

Given the constraints, I'm exploring alternative in-memory data structures that might offer better performance for maintaining a distributed counter. Or maybe is there a service faster than Redis for this type of thing? The goal is to efficiently count how many times a specific user ID performs a particular action. My current code runs in 2 threads, but could be more.

1

There are 1 best solutions below

5
for_stack On

Since your work is in the read (GET frequency), compute (if not reaching the limit, increment it), write (update the counter and set expiration) pattern, you should use Lua scripting to avoid RTT (Round Trip Time).

string script = R"(
local key = KEYS[1]
local limit = ARGV[1]
local cnt = redis.call('get', key)
if cnt ~= nil and cnt == limit then
    -- reach the limit
    return -1
else
    local res = redis.call('incr', key)
    redis.call('expire', key, '86400', 'NX')
end
)"

// If you use redis-plus-plus:
auto r = Redis("redis://127.0.0.1");   // You should reuse Redis object for each `eval` operation.
string key = "counter";
string limit = to_string(10);

auto res = r.eval<long long>(script, {key}, {limit});

Disclamer: I'm the author of redis-plus-plus