It does not only apply to Redis but basically all data stores unless something like transactions in RDBMSs are used and even then you can get into trouble at times.
The last example still has a problem when an exception is thrown and the 'redis.del "setting_up_auth_token"' fails for whatever reason. Furthermore it has a race condition if a process wants to read the content of 'web_service_auth_token' because it sees 'setting_up_auth_token' has been set but the other process who is setting it up has not reached the redis.set line for it.
The example itself is not very good as I'd rather do a little bit of extra work (would not hurt here) than being vulnerable to failures.
Assuming the work really needs to be done strictly once - either because it's a lot or the logic requires it - you would probably have to do something like the following pseudo code:
while (true) {
watch("setup_key")
v = get("setup_key")
if v == 2 {
break
}
if v == 1 {
sleep(1)
continue
}
multi()
setex("setup_key", 10, 1) // 10s timeout
if !exec() {
continue // someone else got the lock in the meantime, try again
}
// we now have a temporary exclusive "lock"
... do the real work ...
set("setup_key", 2)
}
What does this do? Well, setup_key acts like an exclusive lock which has 3 states: non-existing, 1 and 2.non-existing means we are the first, lets do the work. 1 means the lock is being held, sleep and check again. 2 means the work has been finished.
The watch(),multi(),exec() work as a CAS (really, whytf is this not a single command?) to atomically acquire the lock. If the lock is held by someone, we just sleep 1 second and try again. I use a timeout on the key in case the client holding the lock dies to prevent a deadlock.
But this is STILL not perfect. What if your setup work takes longer than the timeout specified for the temporary key? I haven't spent enough time thinking about it right now to come up with a solution if there even is any for redis, so I'll leave it as an exercise to the reader :)
To be honest: with slightly different commands/semantics, Redis could be MUCH easier to write correct applications for. Especially the "transaction" support is mediocre imho.
Conclusion: Many web applications are inherintly and severely concurrent. Concurrency is HARD to get right. There are many patterns like locks, atomic operations, transactions etc. - all with completely different semantics. Redis is singlethreaded for a reason :)