Há várias perguntas aqui.
1) Por que não podemos executar o incremento na transação que não pode ser interrompida por outro comando?
Observe primeiro que as "transações" do Redis são completamente diferentes do que a maioria das pessoas pensa que as transações são no DBMS clássico.
# Does not work
redis.multi()
current = redis.get('powerlevel')
redis.set('powerlevel', current + 1)
redis.exec()
Você precisa entender o que é executado no lado do servidor (no Redis) e o que é executado no lado do cliente (no seu script). No código acima, os comandos GET e SET serão executados no lado do Redis, mas a atribuição de current e o cálculo de current + 1 devem ser executados no lado do cliente.
Para garantir a atomicidade, um bloco MULTI/EXEC atrasa a execução dos comandos Redis até o exec. Assim, o cliente apenas acumulará os comandos GET e SET na memória e os executará de uma só vez e atomicamente no final. É claro que a tentativa de atribuir corrente ao resultado de GET e incrementação ocorrerá bem antes. Na verdade, o método redis.get retornará apenas a string "QUEUED" para sinalizar que o comando está atrasado e o incremento não funcionará.
Nos blocos MULTI/EXEC você só pode usar comandos cujos parâmetros possam ser totalmente conhecidos antes do início do bloco. Você pode querer ler a documentação para obter mais informações.
2) Por que precisamos iterar e esperar até que ninguém altere o valor antes do início da transação?
Este é um exemplo de padrão otimista simultâneo.
Se não usássemos WATCH/MULTI/EXEC, teríamos uma possível condição de corrida:
# Initial arbitrary value
powerlevel = 10
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: SET powerlevel 11
session B: SET powerlevel 11
# In the end we have 11 instead of 12 -> wrong
Agora vamos adicionar um bloco WATCH/MULTI/EXEC. Com uma cláusula WATCH, os comandos entre MULTI e EXEC são executados somente se o valor não for alterado.
# Initial arbitrary value
powerlevel = 10
session A: WATCH powerlevel
session B: WATCH powerlevel
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: MULTI
session B: MULTI
session A: SET powerlevel 11 -> QUEUED
session B: SET powerlevel 11 -> QUEUED
session A: EXEC -> success! powerlevel is now 11
session B: EXEC -> failure, because powerlevel has changed and was watched
# In the end, we have 11, and session B knows it has to attempt the transaction again
# Hopefully, it will work fine this time.
Portanto, você não precisa iterar para esperar até que ninguém altere o valor, mas tentar a operação repetidamente até que o Redis tenha certeza de que os valores são consistentes e sinalize que foi bem-sucedido.
Na maioria dos casos, se as "transações" forem rápidas o suficiente e a probabilidade de haver contenção for baixa, as atualizações serão muito eficientes. Agora, se houver contenção, algumas operações extras terão que ser feitas para algumas "transações" (devido à iteração e novas tentativas). Mas os dados sempre serão consistentes e nenhum bloqueio é necessário.