Search code examples
asp.net.netredisconnection-poolingstackexchange.redis

StackExchange.Redis ConnectionMultiplexer pool for synchronous methods


Does implementing a ConnectionMultiplexer pool make sense if we use it for synchronous methods?

So by pool I mean creating multiple instances of StackExchange.Redis ConnectionMultiplexer, storing those objects and when I want to communicate with Redis server I take the least used one from the pool. This is to prevent timeouts due to large queue size as per No. 10 suggestion in this article: https://azure.microsoft.com/en-us/blog/investigating-timeout-exceptions-in-stackexchange-redis-for-azure-redis-cache/

I'm having doubts because I'm not sure how can a queue even happen if connectionMultiplexer blocks a thread until a call returns.

It seems to me that having a pool is pointless with sync method calls, but Redis best practice articles suggest creating this kind of pool regardless of method type (sync/async)


Solution

  • I think you're getting confused here. ConnectionMultiplexer does not "get blocked". Creating a ConnectionMultiplexer gives you a factory-like object with which you can create IDatabase instances. You then use these instances to perform normal Redis queries. You can also do Redis queries with the connection multiplexer itself, but those are server queries and unlikely to be done often.
    So, to make things short, it can help tremendously to have a pool of connection multiplexers, regardless of sync/async/mixed usage.


    To expand further, here's a very simple pool implementation, which can certainly be enhanced further:

    public interface IConnectionMultiplexerPool
    {
        Task<IDatabase> GetDatabaseAsync();
    }
    
    public class ConnectionMultiplexerPool : IConnectionMultiplexerPool
    {
        private readonly ConnectionMultiplexer[] _pool;
        private readonly ConfigurationOptions _redisConfigurationOptions;
    
        public ConnectionMultiplexerPool(int poolSize, string connectionString) : this(poolSize, ConfigurationOptions.Parse(connectionString))
        {
        }
    
        public ConnectionMultiplexerPool(int poolSize, ConfigurationOptions redisConfigurationOptions)
        {
            _pool = new ConnectionMultiplexer[poolSize];
            _redisConfigurationOptions = redisConfigurationOptions;
        }
    
        public async Task<IDatabase> GetDatabaseAsync()
        {
            var leastPendingTasks = long.MaxValue;
            IDatabase leastPendingDatabase = null;
    
            for (int i = 0; i < _pool.Length; i++)
            {
                var connection = _pool[i];
    
                if (connection == null)
                {
                    _pool[i] = await ConnectionMultiplexer.ConnectAsync(_redisConfigurationOptions);
    
                    return _pool[i].GetDatabase();
                }
    
                var pending = connection.GetCounters().TotalOutstanding;
    
                if (pending < leastPendingTasks)
                {
                    leastPendingTasks = pending;
                    leastPendingDatabase = connection.GetDatabase();
                }
            }
    
            return leastPendingDatabase;
        }
    }