Joey Yi Zhao
Joey Yi Zhao

Reputation: 42644

How to Increment Value Atomically with Redis

I have below code in nodejs:

const decrease = async (userId, points) => {
    const user = await redisClient.hgetall(userId);
    if(user.points - points >= 0) {
       await redisClient.hset(userId, userId, user.points - points);
    }
}

since async/await is not blocking the execution, if there are multiple requests for the same userId, the code is not running as atomically. That means the user points may be decreased multiple times even there is not enough point left on users account. How can I make the method run atomically?

I have checked redis multi command and it works for multiple redis statements. But in my case, I need to calculate the user points which is not part of redis command. So how to make them run as an atomic function.

I also read the INCR pattern: https://redis.io/commands/incr But it doesn't seem to fix my issue. The patterns listed there need to work with expire which I don't have such requirement to give a specific timeout value.

Upvotes: 4

Views: 2119

Answers (1)

Itamar Haber
Itamar Haber

Reputation: 50112

Use the power of (Redis) server-side Lua scripts by calling EVAL. It should probably look something like this:

const lua = `
    local p = redis.call('HGET',KEYS[1],'points')
    local d = p - ARGV[1]
    if d >= 0 then
      redis.call('HSET', KEYS[1], 'points', d)
    end`
const decrease = async (userId, points) => {
    await redisClient.eval(lua, 1, userId, points);
}

Upvotes: 3

Related Questions