36

We are planning to develop an Azure function for which the input trigger is a service bus message and the output will be blob storage. The service bus message will contain a image url and the function will resize the image to a predefined resolution and will upload to azure blob storage.

The resolution to which the image should be resized is stored in the database and the Azure function needs to make a call to database to get to know the resolution that is supposed to be used for the image in the input message. The resolution would actually be a master data configured based on the source of the input message.

Making a database call would be a expensive call as it would have to go to the database for each call. Is there any way to cache the data and use it without calling the database. Like in memory caching?

rene
  • 41,474
  • 78
  • 114
  • 152
Silly John
  • 1,584
  • 2
  • 16
  • 34
  • Some suggestions as well in a previous thread. https://stackoverflow.com/questions/38597683/in-memory-caching-in-azure-function – Baskar Rao Dec 08 '17 at 22:14

5 Answers5

51

You are free to use the usual approaches that you would use in other .NET applications:

  • You can cache it in memory. The easiest way is just to declare a static dictionary and put database values inside (use concurrent dictionary if needed). The cached values will be reused for all subsequent Function executions which run on the same instance. If an instance gets idle for 5 minutes, or if App scales out to an extra instance, you will have to read the database again;

  • You can use distributed cache, e.g. Redis, by using its SDK from Function code. Might be a bit nicer, since you keep the stateless nature of Functions, but might cost a bit more. Table Storage is a viable alternative to Redis, but with more limited API.

There's no "caching" feature of Azure Functions themselves, that would be ready to use without any extra code.

Mikhail Shilkov
  • 34,128
  • 3
  • 68
  • 107
  • 5
    Wouldn't caching in memory be pointless if the function is hosted in Consumption plan? Wouldn't the function be removed (after each execution??) and potentially be spun up in a different VM thereby making in-memory caching not a viable approach? – Andy T Dec 08 '17 at 23:22
  • 15
    No, functions aren't removed after each execution. The instances will be reused if the requests are coming faster than once per several (5 by default) minutes. – Mikhail Shilkov Dec 08 '17 at 23:25
  • but what happens if requests come in longer periods - one every 10+ minutes? – robs Dec 16 '17 at 06:34
  • 3
    @robs Then, if you still need to cache (does it still make sense?), your only option is out-of-proc cache – Mikhail Shilkov Dec 16 '17 at 07:46
  • 3
    or set a timer to hit a function that's literally just called `Ping`. Your whole app is the unit that is unloaded, so calling `Ping` will keep it alive. I don't technically know how this fits in with their terms of service. – Simon_Weaver Aug 19 '18 at 02:15
  • Does the 5 minute rule apply when not running under consumption plan? EG running on standard? – Adrian Jul 26 '19 at 19:12
  • 1
    It's actually 20 minutes now until the last idle instance on Consumption Plan gets killed. It does not apply to fixed or premium plans. – Mikhail Shilkov Jul 26 '19 at 19:34
  • THank you for the answers. 1 thing I dislike about first approach is that the static property would be declared on the function class, and many times there is SOC such that the code that is needing the cached key is not in the function class, but in another class, and passing into a method,ctor of the dependent class is not necessarily optimal. – Judy007 Oct 11 '19 at 20:15
  • 1
    @MikhailShilkov could you post a reference to the documentation about the TTL on the Consumption Plan? Does this mean we could locally cache in memory static settings, things like Cosmos client objects? I'm sure the code would need to check for null. – Larry Aultman Dec 28 '20 at 10:39
  • @LarryAultman I don't think this is documented anywhere: the behavior may change over time. You can find the tests in my blog https://mikhail.io/serverless/coldstarts/azure/ – Mikhail Shilkov Dec 28 '20 at 14:51
9

You can use Azure Cache service (https://azure.microsoft.com/en-us/services/cache/) to cache your data. Basically, In your Azure Function instead of calling database all the time, call Azure cache and use if it is not expired and if it is expired or not set then call database to get the value and populate the cache with appropriate expiry logic (timeout after fixed time or some other custom logic).

user862268
  • 456
  • 1
  • 4
  • 7
6

You could use Durable Functions and make the database call via an activity or sub-Orchestration, the return value is essentially cached for you then and will be returned without making the underlying call again each time the function replays.

Dennis
  • 81
  • 4
5

Redis is in-memory cache and there is custom output binding that you can use to keep your function clean:

[FunctionName("SetPoco")]
public static async Task<IActionResult> SetPoco(
    [HttpTrigger("POST", Route = "poco/{key}")] HttpRequest request,
    [Redis(Key = "{key}")] IAsyncCollector<CustomObject> collector)
{
    string requestBody;
    using (var reader = new StreamReader(request.Body))
    {
        requestBody = reader.ReadToEnd();
        var value = JsonConvert.DeserializeObject<CustomObject>(requestBody);
        await collector.AddAsync(value);
    }
    return new OkObjectResult(requestBody);
}

Link to the project: https://github.com/daulet/Indigo.Functions#redis

However if by in-memory cache you mean in memory of the function I'd strongly recommend otherwise as function are meant to be stateless and you won't be able to share that memory across multiple hosts running your function. This is also not recommended in Azure Functions best practices

  • 3
    I can see why you advice against state in this specific case (a master setting like this is state and could be kept in a setting instead of the database), but in general caching is, IMHO, considered a performance optimization and not state. The function is still stateless and idempotent, it just avoids hitting the database with every request coming in. – Casper Oct 01 '20 at 10:11
1

Here's a little class I built to simplify the task of storing and re-using objects in the running instance's memory whilst it remains alive. Of course this means each new instance will need to populate itself but this can provide some useful optimisations.

// A simple light-weight cache, used for storing data in the memory of each running instance of the Azure Function.
// If an instance gets idle (for 5 minutes or whatever the latest time period is) or if the Function App scales out to an extra instance then the cache is re-populated.
// To use, create a static readonly instance of this class in the Azure Function class, in the constructor pass a function which populates the object to cache.
// Then simply reference the Data object.  It will be populated on the first call and re-used on future calls whilst the same instance remains alive.
public class FunctionInstanceCache<T>
{
    public FunctionInstanceCache(Func<T> populate)
    {
        Populate = populate;
        IsInit = false;
    }

    public Func<T> Populate { get; set; }

    public bool IsInit { get; set; }

    private T data;

    public T Data
    {
        get
        {
            if (IsInit == false)
            {
                Init();
            };
            return data;
        }

    }

    public void Init()
    {
        data = Populate();
        IsInit = true;
    }
}

Then in your Azure Function instance implementation create a static readonly instance of this, passing in a Populate method:

    private static readonly FunctionInstanceCache<string[]> Fic = new FunctionInstanceCache<string[]>(PopulateCache);

Then implement this

private static string[] PopulateCache()
{
    return DOSOMETHING HERE;
}

Then simply call Fic.Data when needed - it will be populated on first use and then re-used whilst the instance remains alive.

Mister Cook
  • 1,552
  • 1
  • 13
  • 26
  • 6
    isnt it easier just to use memorycache class which already handles all those for you? – Emil Jan 31 '21 at 16:50