Search code examples
javaspringredisspring-data-redislettuce

How do I use transactions in Spring Data Redis Reactive?


I'm trying to use ReactiveRedisOperations from spring-data-redis 2.1.8 to do transactions, for example:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

But I cannot seem to find a way to do this when browsing the docs or the ReactiveRedisOperations. Is this not available in the reactive client, or how can you achieve this?


Solution

  • TL;DR: There's no proper support for Redis Transactions using the Reactive API

    The reason lies in the execution model: How Redis executes transactions and how the reactive API is supposed to work.

    When using transactions, a connection enters transactional state, then commands are queued and finally executed with EXEC. Executing queued commands with exec makes the execution of the individual commands conditional on the EXEC command.

    Consider the following snippet (Lettuce code):

    RedisReactiveCommands<String, String> commands = …;
    
    commands.multi().then(commands.set("key", "value")).then(commands.exec());
    

    This sequence shows command invocation in a somewhat linear fashion:

    • Issue MULTI
    • Once MULTI completes, issue a SET command
    • Once SET completes, call EXEC

    The caveat is with SET: SET only completes after calling EXEC. So this means we have a forward reference to the exec command. We cannot listen to a command that is going to be executed in the future.

    You could apply a workaround:

    RedisReactiveCommands<String, String> commands = …
    
    Mono<TransactionResult> tx = commands.multi()
            .flatMap(ignore -> {
    
                commands.set("key", "value").doOnNext(…).subscribe();
    
                return commands.exec();
            });
    

    The workaround would incorporate command subscription within your code (Attention: This is an anti-pattern in reactive programming). After calling exec(), you get the TransactionResult in return.

    Please also note: Although you can retrieve results via Mono<TransactionResult>, the actual SET command also emits its result (see doOnNext(…)).

    That being said, it allows us circling back to the actual question: Because these concepts do not work well together, there's no API for transactional use in Spring Data Redis.