Search code examples
redislua

If a executable redis's lua script fail to return value to client (java application), is that transaction rolled back or not?


Here is my Lua script.

local field = ARGV[1]
local amount = ARGV[2]

local newAmount = redis.call("HINCRBYFLOAT", key, field, amount)
return newAmount

I have this lua script in my java application. My concern is if this script fail to return "newAmount" then the script's transactions will roll back or not?

My expectation is the roll back operation should happen.


Solution

  • No, there are no automatic rollbacks of transactions in Redis Lua scripts. The executed commands are not rolled back in the event of an failure later in the script. In fact, rollbacks in Redis doesn't exist at all.

    But there are discussions about implementing some kind of rollback feature.

    Important definition of "atomically":

    Redis guarantees the script's atomic execution. While executing the script, all server activities are blocked during its entire runtime.

    That means that Redis treats the complete script as one operation with no other operation interrupting it.

    To prevent errors you should validate all your input arguments very thoroughly before using them. To handle potential errors, you can use redis.pcall() instead of redis.call(). With redis.pcall() you can handle runtime errors that are raised by Redis as redis.pcall() always returns a reply and never throws a runtime exception. This can be helpful in creating your own rollback logic and to log errors.

    Example:

    local function scriptNoRollback(keys)
        redis.call("INCR", keys[1])
        redis.call("INCR", keys[2])
    
        return "OK"
    end
    
    local function scriptRollback(keys)
        -- Store initial value.
        local initValueCounter1 = redis.call("GET", keys[1])
    
        redis.call("INCR", keys[1])
        local reply = redis.pcall("INCR", keys[2])
        if type(reply) == "table" and reply["err"] ~= nil then
            reply["err"] = "Oops, error!"
            -- Try to rollback data.
            redis.call("SET", keys[1], initValueCounter1)
    
            return reply
        end
    
        return "OK"
    end
    

    scriptNoRollback OK:

    127.0.0.1:6379> MSET counter1 1 counter2 2
    OK
    127.0.0.1:6379> FCALL scriptNoRollback 2 counter1 counter2
    "OK"
    127.0.0.1:6379> MGET counter1 counter2
    1) "2"
    2) "3"
    

    scriptNoRollback ERR:

    127.0.0.1:6379> MSET counter1 1 counter2 A
    OK
    127.0.0.1:6379> FCALL scriptNoRollback 2 counter1 counter2
    (error) ERR value is not an integer or out of range script: scriptNoRollback, on @user_function:118.
    127.0.0.1:6379> MGET counter1 counter2
    1) "2"
    2) "A"
    

    counter1 is not rolled back to initial state.

    scriptRollback OK:

    127.0.0.1:6379> MSET counter1 1 counter2 2
    OK
    127.0.0.1:6379> FCALL scriptRollback 2 counter1 counter2
    "OK"
    127.0.0.1:6379> MGET counter1 counter2
    1) "2"
    2) "3"
    

    scriptRollback ERR:

    127.0.0.1:6379> MSET counter1 1 counter2 A
    OK
    127.0.0.1:6379> FCALL scriptRollback 2 counter1 counter2
    (error) Oops, error!
    127.0.0.1:6379> MGET counter1 counter2
    1) "1"
    2) "A"
    

    counter1 is rolled back to initial state.