StackExchange / StackExchange.Redis

General purpose redis client
https://stackexchange.github.io/StackExchange.Redis/
Other
5.85k stars 1.5k forks source link

Empty transaction submitted when using KeyExpireAsync with ExpireWhen.GreaterThanCurrentExpiry on Sorted Set #2616

Closed tomwolanski closed 6 months ago

tomwolanski commented 6 months ago

I am having some troubles understanding the behaviour of the transaction.

context I want to have a sorted set, where the score is a member expiration time, and the key also have the same expiration. Both expiration values needs to be in sync, to achieve this I am using transaction:

IDatabase _database = ...
DateTimeOffset expiryTime = ....

var transaction = _database.CreateTransaction();

_ = transaction.SortedSetAddAsync(
        "key",
        "member",
        expiryTime.ToUnixTimeSeconds()); 

_ = transaction.KeyExpireAsync(
    "key",
    expiryTime.UtcDateTime);  

await transaction.ExecuteAsync();

This scenario works perfectly and I have my member added to the set, and the whole set has an expiry added. using redis-cli monitor command I can see expected traffic:

[0 [::100:0:0:0]:64000] "MULTI"
[0 [::100:0:0:0]:64000] "ZADD" "key" "1702594164" "member"
[0 [::100:0:0:0]:64000] "PEXPIREAT" "key" "1702594164427"
[0 [::100:0:0:0]:64000] "EXEC"

problem I'd like to make sure that my whole set does not expire prematurely if a member with ealier expiration time is added. I'd like to keep the larger expiration value, so I decided to use ExpireWhen.GreaterThanCurrentExpiry.

IDatabase _database = ...
DateTimeOffset expiryTime = ....

var transaction = _database.CreateTransaction();

_ = transaction.SortedSetAddAsync(
        "key",
        "member",
        expiryTime.ToUnixTimeSeconds()); 

_ = transaction.KeyExpireAsync(
    "key",
    expiryTime.UtcDateTime,
    ExpireWhen.GreaterThanCurrentExpiry);    // <--- always keep the higher TTL

await transaction.ExecuteAsync();

however this operation throws StackExchange.Redis.RedisServerException: EXECABORT Transaction discarded because of previous errors.

The redis-cli monitor shows an empty transaction was submitted:

[0 [::100:0:0:0]:64000] "MULTI"
[0 [::100:0:0:0]:64000] "EXEC"

I do not understand why this code is failing to produce a proper sequence of commands. Is there a reason why this scenario is not supported, or I am doing something wrong here?

expected result I'd expect to have a valid transaction submitted but with extra flag 'GT' added to PEXPIREAT, since it seems to be supported by the command: https://redis.io/commands/pexpireat/

[0 [::100:0:0:0]:64000] "MULTI"
[0 [::100:0:0:0]:64000] "ZADD" "key" "1702594164" "member"
[0 [::100:0:0:0]:64000] "PEXPIREAT" "key" "1702594164427" GT   <--- a new flag
[0 [::100:0:0:0]:64000] "EXEC"
mgravell commented 6 months ago

The GT flag is redis 7 or above; what version is the server? If a command fails pre-validation on the server, then indeed: it would appear as an empty transaction and everything would be aborted

tomwolanski commented 6 months ago

You are correct. I have been using v6, and the commands works correclty after update to v7.

I believe the ticket can be closed.

tomwolanski commented 6 months ago

closing as 'resolved'