The problem/use-case that the feature addresses
If the data (value of key) changes during a round-trip to the user interface and back, we need a way to detect this situation in order to avoid dataloss in situations when there are multiple users editing data on same keys.
Description of the feature
Prevent changes to the data when another user has already changed the data behind the scenes. Implementation as CAS token.
If CAS token does not match current token, then set must fail. If CAS token matches, then set to given value and create new cas token
First part: Get CAS token
SET hello "Hello, World!"
GET hello CAS "Hello, World!" "37eaa6435955b815"
Alternatively: GETCAS hello "37eaa6435955b815"
Second part:
SET hello "Hello, You!" CAS "37eaa6435955b815"
If the previous data is still "Hello, World!" this key 'hello' should be set to "Hello, You!", because the CAS token has not changed. However, if another user has meanwhile changed the data into "Hello, Me!" and thereby changed its CAS token into "37eaa6434791eb9b" then the SET should fail.
In the given example above the tokens have been calculated with simple hashing as follows:
uint64_t simpleCasToken(const uint8_t* data, size_t length) { uint64_t token = 0; for (size_t i = 0; i < length; i++) { token = token * 31 + data[i]; } return token; }
void CasTokenToString(uint64_t token, char* buffer) { sprintf(buffer, "%llx", token); }
uint64_t stringToCasToken(const char* buffer) { uint64_t token; sscanf(buffer, "%llx", &token); return token; }
In Redis code there are already tested functions to do similar hashing (such as siphash), which could be used instead.
This functionality would fit with the NX/XX/LT/GT type of "Only Set If Condition" (OSIC) -features.
Alternatives you've considered
Client code can implement this feature by themselves, but this is not atomic and can still happen. WATCH, MULTI, EXEC can be used to create limited CAS functionality, but using it in a web environment, or in situations when the connections may disconnect, can be very tricky.
Additional information
Relates to #12454 #8361 #5917
Regarding key types that have multiple fields or items, maybe this discussion about inner workings ja Java hashcode may come in handy: https://www.baeldung.com/java-hashcode
Comment From: zuiderkwast
This has been requested by many. Is it finally time to accept CAS, optimistic concurrency, in Redis 8?
This old discussion needs to be mentioned (although I don't think the arguments against it in this thread are valid): https://groups.google.com/forum/#!topic/redis-db/a4zK2k1Lefo. (We're not going to add a 64 bit token to every key. Nobody wants that.)
The simplest form is just to compare to the old value. (Option name can be discussed.)
SET foo bar IF-EQ baz
The simple form is not suitable for large values, like editing a wiki page, so for that a CAS-token or hash is better.
If we use a known hash like SHA1 (we already use SHA1 for EVALSHA), we can let the user compute the hash and just add an option to SET:
SET foo bar IF-SHA 37eaa6435955b815
If we really want a digest provided by Redis, we usually want to avoid options that alter the return type of a command, so GET foo CAS is probably not a good idea. A new command is better. Maybe simply SHA key. Or DIGEST key and SET key value IF-DIGEST dddd if we want to be algorithm agnostic.
To get the value and digest together, you can use a transaction:
MULTI
GET hello
DIGEST hello
EXEC
1) "Hello, World!"
2) "37eaa6435955b815"
If DIGEST runs immediately after GET, the key is most likely in a CPU cache line so it won't take much extra time. (Or we could just add a GETCAS or GET-WITH-DIGEST command.)