StackExchange / StackExchange.Redis

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

Blocking wait in transaction execution #2683

Closed rayao closed 4 months ago

rayao commented 6 months ago

image It's unexpected to me when all methods I call are async, is there any setting to tune to avoid this blocking wait? I also found similar stack in TakeLock/ReleaseLock calls, seems they're implemented upon transactions. We have a service exposes APIs that do Redis lock take/release, we're afraid that when request rate increases, blocking wait will put the service in danger.

mgravell commented 6 months ago

What is the scenario here? What exactly is your code doing? Is this a transaction with constraints, perhaps? If so: that may be a factor here - I'm a little surprised it didn't offload the entire thing to the write loop, though.

I'm right now in the middle of a big overhaul of the write loop, which may help with the latter, but there will always be a pinch point using transactions with constraints, do to (grungy details that aren't very interesting). However, in most cases Lua will be a much easier, simpler, and more direct mechanism for constrained batches - I wonder if refactoring to use a Lua script would be the best option.

Without example code, it is hard to say more.

rayao commented 6 months ago

Code corresponds to stack above

            var transaction = cache.CreateTransaction();
            if (comparand.HasValue)
            {
                transaction.AddCondition(Condition.HashEqual(key, VersionField, comparand.Version));
            }
            else
            {
                transaction.AddCondition(Condition.HashNotExists(key, VersionField));
            }

            var entries = new HashEntry[]
            {
                new HashEntry(ValueField, value),
                new HashEntry(VersionField, version.Version),
            };
            var redisKey = key;

            transaction.HashSetAsync(redisKey, entries);
            transaction.KeyExpireAsync(redisKey, expiry);
            return transaction.ExecuteAsync();
rayao commented 6 months ago

And even more simple with *Lock methods

        public Task<bool> ExtendLock(string formId)
        {
            return _redisCache.ExtendLock(
                $"{ExcelLockFormat}-{formId}",
                _contextProvider.User.UserSessionId,
                SdxEventLockDuration);
        }

image

rayao commented 6 months ago

Seems there's a lock to take to execute the transaction. Maybe the entire transaction should be queued to run in write loop? Is it possible to replace *Lock with Lua scripts?

mgravell commented 6 months ago

Yes, that is suitable for Lua. Not at PC right now, but will try to provide a translation when I am at desk.

aniprasad commented 6 months ago

Hi @mgravell Would you happen to have any updates on this?

Would something like this work? for taking the lock, extending the lock, and releasing the lock respectively

  const string TakeLock = @"
      local lockDuration = tonumber(ARGV[4])
      local setResult = redis.call('SET', KEYS[1], ARGV[1], ARGV[2], ARGV[3], lockDuration)
      return setResult";
      string[] keys = new string[] { "key" };
      string[] args = new string[] {
        "value",
        "NX",
        "EX",
        duration
  };
const string ExtendLock = @"
    local session = redis.call('GET', KEYS[1])
    if session == ARGV[1]
    then
        local lockDuration = tonumber(ARGV[2])
        return redis.call('SET', KEYS[1], ARGV[1], 'EX', lockDuration)
    end
    return session";
    string[] keys = new string[] { "key };
    string[] args = new string[]
    {
        "value"
        duration
    };
const string ReleaseLock = @"
    local session = redis.call('GET', KEYS[1])
    if session == ARGV[1]
    then
        return redis.call('DEL', KEYS[1])
    end";
    string[] keys = new string[] { "key" };
    string[] args = new string[] { "value" };

I plan on using the ScriptEvaluateAsync method for these scripts like ScriptEvaluateAsync(script, keys, args, CommandFlags.None)

mgravell commented 4 months ago

Sorry, slipped off my plate; that looks broadly OK to me and is a preferable solution than MULTI/EXEC ; some nits: