-3

I want to initiate the Redis connection as a background task, so that the user need not await for the Redis connection to get established. If Redis connection is not setup or if Redis is unavailable then data shall be fetched from the source, without impacting the user.

The application is an AWS Lambda Web API project. I want to avoid connection delays in individual requests but I don't want to increase the cold startup time by waiting to connect at startup either.

I'm using the StackExchange Redis Client library.

In order to accomplish this, I want to initiate the Redis connection in background task.

Shaheer
  • 155
  • 1
  • 7
  • Show the patterns that you have seen and then you might get an answer whether this pattern is thread safe or not. – SomeBody Oct 23 '20 at 05:57
  • 2
    This is far too vague - for what kind of application? What does `the user need not await for the Redis connection to get established` mean? For desktop apps, it means the UI doesn't freeze. Web apps don't freeze while waiting. And what does `in thread safe manner` mean for your code? If you don't modify global state, there's no risk, and a simple `await myClient.ConnectAsync()` would do, assuming your Redis library has async methods – Panagiotis Kanavos Oct 23 '20 at 06:00
  • That's a method call, not an approach, and isn't needed for asynchronous methods. Your use case doesn't seem to have anything to do with thread safety either. You need to write logic that tries to connect to Redis and falls back to another source. There's nothing thread-related in that. If both sources have async methods, you only need to `await` – Panagiotis Kanavos Oct 23 '20 at 06:03
  • And once again, web apps don't freeze, so they don't need background threads. Each request is already processed by a different thread – Panagiotis Kanavos Oct 23 '20 at 06:04
  • @PanagiotisKanavos Its Web API. Establishing Redis connection sometimes takes upto 3 sec. I dont want the API user to await and instead data will be fetched from the source the if Redis is not ready. So I want to initiate the Redis connection in the background. Another usecase is that if Redis is down for maintenance, I dont want the end user to get impacted. Do you have any suggestion on how connection can be established as Fire and Forget manner. – Shaheer Oct 23 '20 at 06:06
  • 2
    And none of those things has to do with thread safety or freezing. you're asking for *caching with fallback*. Which has nothing to do with threads, and ... is probably already implemented, at least partially, through [the distributed caching middleware](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-3.1) – Panagiotis Kanavos Oct 23 '20 at 06:07
  • @PanagiotisKanavos I am not asking about catching with fallback. As I mentioned I simply do not want end user to wait till connection get established. – Shaheer Oct 23 '20 at 06:10
  • A *simple* async function that tries to connect to the cache and if that times out connects to eg the database, is more than enough. The tricky part is deciding what to do the *next* time - try Redis again, or cache the previous decision? – Panagiotis Kanavos Oct 23 '20 at 06:11
  • @Shaheer so use a timeout in the connection call. You still haven't explained what Redis client you use by the way. What you ask has nothing to do with waiting, nothing to do with threads and you're *already in a background thread!* – Panagiotis Kanavos Oct 23 '20 at 06:12
  • @Shaheer btw one way to solve this is to use one source that's both an in-memory cache and a database, like SQL Server's in-memory tables. No need to try multiple sources if the one true source already offers in-memory, low-latency, low-lock storage – Panagiotis Kanavos Oct 23 '20 at 06:15
  • @PanagiotisKanavos as I said I do not want any millisecond delay for the end user when it takes few seconds to initialize the Redis connection. Lets the connection get initialized in different thread and meanwhile data will be fetched from the source. I am using StackExchange Redis Client. – Shaheer Oct 23 '20 at 06:15
  • 2
    This is a case of the [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). You have a problem X (how to use Redis with fallback to another source) and assumed that Y is the solution (background threads), so when things don't work, you ask about Y. But in this case Y has nothing to do with X – Panagiotis Kanavos Oct 23 '20 at 06:16
  • 1
    @Shaheer ********you still have to wait if you do that********. Anyway, post your real code. – Panagiotis Kanavos Oct 23 '20 at 06:17
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/223500/discussion-between-shaheer-and-panagiotis-kanavos). – Shaheer Oct 23 '20 at 06:17
  • 1
    There's no reason to. It's not that people don't understand your problem. You're asking about the wrong problem. If you want to test the connection *do it at startup*. Subsequent connections *won't* take that long. Add a Redis health check if you want to. If you suspect the connection will go down, write your code so it falls back to the database or whatever other source you have. – Panagiotis Kanavos Oct 23 '20 at 06:20
  • @Shaheer It seems you haven't thought this through. If you do not want _any millisecond delay_ if Redis Connection is not "ready", then you have to fallback immediately when you get aware it isn't (i.e. when you want to call some connect method). As soon as you start to connect, you'll have to wait at least the timeout period to _know_ it is "not ready". But by then, you already have waited x amount of time. – Fildor Oct 23 '20 at 06:20
  • 1
    If you want something more sophisticated, check the [Polly](https://github.com/App-vNext/Polly) library. You can use it to automatically fallback during timeouts, even permanently switch to the other source/system if Redis fails too often, through policies – Panagiotis Kanavos Oct 23 '20 at 06:22
  • 1
    From the Redis Client [docs](https://stackexchange.github.io/StackExchange.Redis/Basics) `Because the ConnectionMultiplexer does a lot, it is designed to be shared and reused between callers.` Which means you *have* to create that object at startup and share it among requests. If that fails you *know* you can't connect to Redis. So there's no need to connect for individual requests and hence no delay – Panagiotis Kanavos Oct 23 '20 at 06:25
  • Also check the [StackExchange.Redis.Extensions](https://github.com/imperugo/StackExchange.Redis.Extensions) package, which adds connection pooling so requests don't have to use the same ConnectionMultiplexer – Panagiotis Kanavos Oct 23 '20 at 06:35
  • I cannot afford to initialze the connection in the Startup as my API is hosted as Lambda in AWS, and additional delay in the startup will add to the coldstart. – Shaheer Oct 23 '20 at 06:35
  • 1
    Fixed: You could even create that connection in Startup.ConfigureServices and if it fails, register the other data source. Action calls should not have to deal with Redis at all – Panagiotis Kanavos Oct 23 '20 at 06:36
  • 1
    @Shaheer ??? Then it's not a Web API at all. It's an AWS Lambda. Probably talking to AWS ElastiCache. What you asked seems to have **nothing** to do with what you actually try to do, and each comment changes the picture completely, except for one thing - this has nothing to do with threading – Panagiotis Kanavos Oct 23 '20 at 06:38
  • `and additional delay in the startup will add to the coldstart.` but opening the connection in the controller will add delays to the *requests*. And using another thread to wait for the connection won't reduce the wait. If your Elasticache connection is so slow, you should probably talk to AWS support. – Panagiotis Kanavos Oct 23 '20 at 06:41
  • Its Web API in AWS Lambda. I believe It has something to do with threading. My requirement is simple as to initialize connection as Fire and Forget manner so as to avoid any delay for the user. – Shaheer Oct 23 '20 at 06:42
  • 1
    I'll have to repeat Fildor's suggestion. You have to think this through – Panagiotis Kanavos Oct 23 '20 at 06:42
  • 1
    Your belief is wrong, as you're already in a background thread. No, your "requirement" isn't simple, a requirement or even fire-and-forget as you **want that connection**, so there's no "forget" involved. So you *have* to wait to get that connection multiplexer from somewhere - if not in the current request, then the next. – Panagiotis Kanavos Oct 23 '20 at 06:43
  • The term "forget" is valid as far as user is concerned, but connection will be used once its ready. My only point is that I do not want to await for the connection to get ready. I am keeping a volatile variable to see if connection has been setup. If this variable is set then it means connection is setup and then data will be fetched from Redis. – Shaheer Oct 23 '20 at 06:47
  • Shaheer, I am afraid at this point discussion has become fruitless. If you are not ready to take advice into consideration (at least), then why asking in the first place. You seem determined to your path of "solving" that problem, that you actually do not have. So good luck with that. (And this is _not_ meant to offend. Sorry, if it does.) – Fildor Oct 23 '20 at 06:50
  • No it's not valid at all, because the user is your code, which cares a *lot*. You tried something and didn't post it in the question, which makes this an XYZ problem. A `volatile` won't help at all, it's [Lazy](https://learn.microsoft.com/en-us/dotnet/api/system.lazy-1?view=netcore-3.1) that's needed or better yet, [AzyncLazy](https://github.com/StephenCleary/AsyncEx/wiki/AsyncLazy) to start opening the connection at startup and use it when the first request arrives. – Panagiotis Kanavos Oct 23 '20 at 06:55
  • I have tried Lazy pattern. But the problem which I am facing is that it also await for the connection to get established. – Shaheer Oct 23 '20 at 06:58
  • You can simplify this by creating and registering a service class whose constructor starts the connection process but whose methods use the Redis connection only if it's completed, otherwise they use the other source. The constructor should *store* the task that opens the connection, eg `_openTask = _mux.ConnectAsync(..)`. Actions could check if the task is completed with `if (_openTask.IsComplete){..}` to decide what to do – Panagiotis Kanavos Oct 23 '20 at 06:59
  • @Shaheer `I have tried` but you haven't posted any of that information in the question. You asked about something unrelated. That's what's so frustrating about XY problems. It took 1 hour to get the relevant facts, and you may not even need to use `await` – Panagiotis Kanavos Oct 23 '20 at 07:00
  • @PanagiotisKanavos Sorry if I missed to post all the necessary information in the first place. I posted it as general question on how to accomplish Fire and Forget pattern in elegant manner. Thank you for giving all the valid inputs – Shaheer Oct 23 '20 at 07:05
  • @PanagiotisKanavos. I have modified the question to specify my use-case. How to reopen this question. – Shaheer Oct 26 '20 at 11:06

1 Answers1

2

There's no need for extra background tasks. The StackExchange.Redis library allows asynchronous connections with ConnectionMultiplexer.ConnectAsync. What's needed is a way to check if that connection is complete and if not, use another source.

One way to do this is to wrap data access in a service that opens the connection asynchronously in its constructor. Its methods can check whether the connection is open and either use Redis or a fallback source (eg a database) if not.

class MyFallbackService
{
    Task<ConnectionMultiplexer> _redisTask;

    public MyFallbackService(string redisConf,string conString)
    {
        _redisTask = ConnectionMultiplexer.ConnectAsync(redisConf);
        ...
    }

    public async Task<string> GetValue(string key)
    {
        if (_redisTask.IsCompleted)
        {
            var redis=_redisTask.Result;
            var db=redis.GetDatabase(...);
            var value= await db.StringGetAsync(key);
        }
        else
        {
            //Use the fallback source
        }
    }
}

This service can be created and registered as a Singleton instance in Startup.ConfigureServices. This way the connection process will begin at at startup but won't block the startup process :

services.AddSingleton(new MyFallbackService(...));
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thank you very much Panagiotis Kanavos. Looks promising. In this implementation, at which point the Connection will actually start. – Shaheer Oct 23 '20 at 07:25
  • 1
    When `ConnectAsync` is called. But the thread won't block while waiting for it to complete. It's the same as calling `OpenAsync()` in a database connection. In both cases, you get back a task that you can `await` immediately or later. Even `await` doesn't *block* the calling thread. It releases it, and once the async operation completes execution will resume after the `await`. In the meantime that thread is available to serve other requests – Panagiotis Kanavos Oct 23 '20 at 07:40
  • If any exception occurs in the Task and if I want to restart the task then how that can be accomplished in the above implementation. – Shaheer Oct 23 '20 at 09:15
  • The exception will be raised when the task is awaited or `.Result` is called. If you want to handle errors in a different way, create a method that wraps the async operation in `try/catch`, logs the error etc, and call that method from the constructor – Panagiotis Kanavos Oct 23 '20 at 09:36