Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redis Lua script implementing CAS (check-and-set)?

Tags:

redis

lua

cas

I'm just trying to understand Redis/Lua scripting and I want to know if anyone sees a problem with the following code.

It's my attempt to implement very simple "CAS" semantics: call it with a single key and two arguments. It will check to see if the value associated with that key on the server starts with first argument and, if it does, will set set the key's new value to the second argument and return 1 otherwise it will return 0; if the key is associated with some type of data other than a string then Redis will return and error just as it would if you attempted a SET command on such a key/value combination. If the key doesn't exist prior to the call then the function will return 0 (failure).

Here's the script:

local x=string.len(ARGV[1]);
if redis.call('GETRANGE', KEYS[1], 0, x-1) == ARGV[1] then
    redis.call('SET', KEYS[1], ARGV[2]);
    return 1;
    end;
return 0

Here's an example of calling the script on a key "foo" with a prefix value of "bar" (in the redis-cli):

eval "local x=string.len(ARGV[1]); if redis.call('GETRANGE', KEYS[1], 0, x-1) == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]); return 1; end; return 0" 1 foo bar barbazzle

I think the usage pattern for this might be cases where you want to store both a "fencing token" and a value with a key ... allowing concurrent clients to attempt updates to the value if they're holding the correct fencing token.

Does this seem like it would be a safe usage pattern in lieu of WATCH/MULTI/EXEC semantics? (Seems like you can fetch the current value, split off the fencing token in your local code, build a new value and then try to update the key anytime you like with semantics that seem less confusing than the WATCH/MULTI/EXEC calls).

(I'm aware that the semantics of my script differ slightly from the memcached CAS command; that's intentional).

This does pass my limited testing ... so I'm really asking about any potential concurrency/atomicity issues and whether there's anything stupid in the Lua --- as I've barely ever touched Lua in the past).

like image 286
Jim Dennis Avatar asked Apr 06 '16 04:04

Jim Dennis


1 Answers

You will be fine in terms of atomicity according to Redis's documentation:

Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed. This semantic is similar to the one of MULTI / EXEC. From the point of view of all the other clients the effects of a script are either still not visible or already completed.

However, if the script is too slow, it causes problem. So script is the best for light operations that requires some logic and atomicity.

Another loophole you might fall in is, if the script somehow failed in the middle, those calls you've done could not be rolled back, although the script will return error.

E.g: You have a script like this:

redis.call('set', 'foo', 1)
redis.call('rpush', 'foo', 2)

The script execution will return error, but foo has already been set in redis as 1.


Something unrelated to your question: I noticed that you used

eval "your_raw_code" key_count keys argv

actually you could call the lua script file in eval when you are in the terminal:

> redis-cli eval "$(cat path/to/script/script_name.lua)" key_count keys argv
like image 111
nevets Avatar answered Oct 15 '22 05:10

nevets