About
In this post, I’ll show you how to prevent your Azure Functions from going to sleep thus preventing a cold start and improving the response time of the first call. This can be especially important if you have multiple function chained because you now have multiple cold starts in a row.
In some cases, you can simply go into your function settings and set the “always on” option. Or you can add a warmup trigger as described here. However, this isn’t available for all app service plans for example the consumption plan.
The alternative is to always keep your function “warm” by sending it a dummy request every couple of minutes. This is what I will demonstrate in this post.
Note: You can learn more about Azure functions in this post I made.
Function Code
For the function to be warmed, I will just reuse the code from this post and I add the warmer endpoint.
using System.Net; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; using System.Text.Json; namespace DemoFunctionApp { public class Function1 { private readonly ILogger _logger; public Function1(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<Function1>(); } class TestModel { public string Test { get; set; } public string Text { get; set; } } [Function("Function1")] public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) { //This is how you can use the logger to log information and errors. _logger.LogInformation("C# HTTP trigger function processed a request."); _logger.LogError("This is an error message."); _logger.LogWarning("This is a warning message."); //This is how you can read url parameters. var query = System.Web.HttpUtility.ParseQueryString(req.Url.Query); string message = query["message"]; //Read the body of the request. string body = await req.ReadAsStringAsync(); //Parse the JSON body into a model. TestModel model = JsonSerializer.Deserialize<TestModel>(body); //Get an environmental variable. if (Environment.GetEnvironmentVariable("TEST_ENVAR") == model.Test) { //Create a text response. var responseTest = req.CreateResponse(HttpStatusCode.NoContent); responseTest.Headers.Add("Content-Type", "text/plain; charset=utf-8"); //Add text to response. responseTest.WriteString("Test. Query param: " + message); //Return the response. return responseTest; } else { var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); response.WriteString("Not test. Query param: " + message); return response; } } [Function("WarmerEndpoint")] public async Task<HttpResponseData> WarmerEndpoint([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req) { _logger.LogInformation("Function was warmed."); var responseTest = req.CreateResponse(HttpStatusCode.NoContent); responseTest.Headers.Add("Content-Type", "text/plain; charset=utf-8"); responseTest.WriteString("Function was warmed."); return responseTest; } } }
Warmer Function Code
This is the warmer function that will run on a timer every 5 minutes and hit the warmer endpoints of all the specified functions.
using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; using System.Text.Json; using System.Net; namespace DemoFunctionApp { public class WarmerFunction { private readonly ILogger _logger; private readonly HttpClient _client; public WarmerFunction(ILoggerFactory loggerFactory, IHttpClientFactory clientFactory) { _logger = loggerFactory.CreateLogger<WarmerFunction>(); _client = clientFactory.CreateClient(); } [Function("WarmTimer")] [FixedDelayRetry(5, "00:00:10")] public async Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo, FunctionContext context) { _logger.LogInformation($"Warmer timer function executed at: {DateTime.Now}"); try { await WarmUrls(_logger); } catch (Exception ex) { _logger.LogError($"Exception Type:{ex.GetType()}, Message: {ex.Message}, Stack Trace: {ex.StackTrace}, Inner Exception:{ex.InnerException}"); } } [Function("WarmManually")] public async Task<HttpResponseData> WarmManually([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req) { _logger.LogInformation("Warmer function was run manually."); try { await WarmUrls(_logger); var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); response.WriteString("Functions were warmed."); return response; } catch (Exception ex) { _logger.LogError($"Exception Type:{ex.GetType()}, Message: {ex.Message}, Stack Trace: {ex.StackTrace}, Inner Exception:{ex.InnerException}"); var response = req.CreateResponse(HttpStatusCode.FailedDependency); response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); response.WriteString("Failed to warm function: " + ex.Message); return response; } } private async Task MakeRequest(string url, ILogger log) { HttpResponseMessage response = await _client.GetAsync(url); log.LogInformation($"Request to {url} has had the following response: Code: {response.StatusCode} Message: {response.ReasonPhrase}"); } private async Task WarmUrls(ILogger log) { //Get the urls of functions to be warmed. string[] urls = await getUrls(); Task[] warmingTasks = new Task[urls.Length]; //Make async requests and save the tasks into an array. for (byte i = 0; i < urls.Length; i++) warmingTasks[i] = MakeRequest(urls[i], log); //Wait for all the tasks in the array to finish. Task.WaitAll(warmingTasks); } private async Task<string[]> getUrls() { //For this demo I will just hardcode the urls. //For production you could get the urls from a json file in the blob storage or a database. return new string[] { "warm endpoint url of the function to be warmed", "warm endpoint url of the function to be warmed", "warm endpoint url of the function to be warmed" }; #region Url Source JSON File Example //Get url of the json file containing all the urls to warm. string fileUrl = Environment.GetEnvironmentVariable("URLS_TO_WARM"); //Make request. HttpResponseMessage response = await _client.GetAsync(fileUrl); //Read response. string strContent = await response.Content.ReadAsStringAsync(); //Deserialize json into a string array. return JsonSerializer.Deserialize<string[]>(strContent); #endregion } } }