Hi,
I have a question that I don't really see fits the GitHub issue tracker so here goes.
We have been using Lettuce for a few months in a scalability project we are working on (offloading RPC calls to use cached Redis data instead of having to call the application backend directly). This is working quite well, but we've ran into an issue with transactions lately.
Because of what we do, we bulk-load some data into Redis at startup + schedule full loads at certain times. We are not talking about huge amounts of data (a few hundred thousand records per data type, about 800k records in total at the moment) so we use the Lettuce API for this; we do not use the wire protocol directly because it's much more convenient to use Lettuce for us.
Doing all this in a MULTI/SET/SET/SET/.../EXEC sequence is much more efficient than running non-transactional. We are talking about 15 seconds vs about 2 minutes for a full load.
Things are working better now, but there is still a potential issue which is why I am writing this: if something goes wrong in the middle of the transaction, there is a risk that the connection is handed back to the connection pool while still in a transactional state. We have code like this to workaround it (slightly edited for posting):
private void transactionWrapped( Consumer<RedisCommands<String, byte[]>> consumer ) throws SomeException {
redisClientManager.withRedisConnection( redisCommands -> {
try {
redisCommands.multi();
consumer.accept( redisCommands );
redisCommands.exec();
}
catch ( RedisException exception ) {
try {
redisCommands.discard();
}
catch ( RedisException innerException ) {
logger.warn( "Error attempting to discard Redis transaction", innerException );
}
throw new SomeException( exception );
}
} );
}
This works fine for most normal scenarios, but if the command timeout is set very low (5ms in our tests) there is a risk that the following happens:
- redisCommands.multi() succeeds
- Some other Redis calls are performed
- The code tries to call redisCommands.exec() which times out
- The code tries to call redisCommands.discard() which also times out
What happens then is that the connection is left in a transactional state and handed back to the connection pool. This is of course not so great, because it means that other (non-transactional) code might borrow this connection from the pool and get completely broken scenarios: SET commands are succeeding without `OK`, but in reality, the data is just left hanging in the stale Redis transaction.
So my two questions are:
- Are there any good ways to work around this?
- Can you detect an ongoing transaction in RedisCommands somehow? I am thinking: my withRedisConnection method above could detect that the connection coming back is in an invalid state and either discard the transaction itself or even close the connection forcibly.
Other suggestions are highly welcome. Thanks for your support and thanks a lot for writing a great Redis Java driver.
Best regards,
Per