0

Ours is a travel website. On particular search criteria(Location,checkIn,CheckOut,Adults,Child) , I am displaying the result on UI at the same time storing the result in redis server for caching as the values does not change so often.

i.e. Key would be search criteria and value would be List.Now when ever next request comes up it first searches the redis and if for the requested key , value is there , result will be displayed from Redis other wise a fresh search would be applied and results will be stored in Cache.

Question : If there is no results in Redis and million user apply search for same search criteria , concurrently.How can this situation be handled ?Because all the searches are coming simultaneously and cache has no result at that time. We have to keep performance in mind.

Help would be appreciated.

1 Answers1

0

It seems you want to avoid calling to the data source unnecessarily. Then, if many threads request the same thing at the same time, you want to allow one to query the data source and cache the data and hold the others until the data is populated.

You may want to have one decorator, to make sure that only one query type is performed at the same time. You can represent the query with an object that is in a thread safe collection, and lock on it during the query execution.

So given an interface expressing the way you query your data source:

interface IQueryExecuter<TQuery, TResult>
{
    TResult Execute(TQuery query);
}

You can use a thread safe decorator object that caches queries' results and, in case the query result is not cached, only one thread performs the query to the data source:

Untested code!

class QueryThrottler<TQuery, TResult> : IQueryExecuter<TQuery, TResult>
{
    // do not lock on external objects
    class QueryObject
    {
        public TQuery Query { get; set; }
    }

    readonly IQueryExecuter<TQuery, TResult> _inner;
    readonly ConcurrentDictionary<TQuery, QueryObject> _queries;

    public QueryThrottler(IQueryExecuter<TQuery, TResult> inner)
    {
        _queries = new ConcurrentDictionary<TQuery, QueryObject>();
        _inner = inner;
    }

    public TResult Execute(TQuery query)
    {
        // if it is on cache return the result
        TResult result;
        if (!IsCached(query, out result))
        {
            // otherwise lock other threads
            // on the same query
            var queryObject = _queries.GetOrAdd(query, k => new QueryObject() { Query = k });
            lock (queryObject)
            {
                // double check it is not cached already
                if (!IsCached(query, out result))
                {
                    result = _inner.Execute(queryObject.Query);
                    PopulateCache(query, result);
                }
            }
        }
        return result;
    }

    private void PopulateCache(TQuery query, TResult result)
    {
        // Save the result in Redis using TQuery as key
    }

    private bool IsCached(TQuery query, out TResult result)
    {
        // go to redis and check if the query is cached using TQuery as key
        // if exists, set the result out parameter and return true
        // otherwise, return false
        result = default(TResult);
        return false;
    }
}

This code relies on TQuery having proper implementations of GetHashCode and Equals.

The decorated object (inner in the constructor) is the object that would do the actual query to the data source.

If you have many servers, and want to make sure only one thread from all servers do the actual query to the data source, rather than lock, you can use a distributed lock like LockTake/LockRelease from StackExchange.Redis.

Community
  • 1
  • 1
vtortola
  • 34,709
  • 29
  • 161
  • 263