Building Single Page App with Azure Functions and improving cold start time

Ajay Bhosale
ITNEXT
Published in
5 min readFeb 15, 2018

--

“Colorful lines of code on a MacBook screen” by Caspar Rubin on Unsplash

Recently we were building a single page app (actually it was a set of multiple SPA’s) using React, Node, Express and MongoDB. The initial plan was to deploy it within company’s data center, but typical corporate processes causing delays in getting required hardware. There was also requirement to provide email capabilities and such things are cumbersome with private data centers.

Considering cost factor and low traffic requirement of the app, we decided to go serverless with Azure Functions. Its consumption based pricing model was perfect for our situation.

Click here to share this article on LinkedIn »

Part 1 : Moving to Azure Functions

As the SPA was interacting using REST based API with server, the migration was simple. Only puzzle was - how to host static files. There were two options, first creating a static website using Azure Blob storage, and second using a azure function to serve static files. We opted for azure function as it can provide consistent security mechanism for static content, reduce the deployment steps, and no need to enable CORS.

The folder structure looked something like this

App
- staticserver/
index.js
function.json
wwwroot ==> contained all the static files generated by Webpack
for SPA
- abc/
index.js
function.json
- xyz/
index.js
function.json
- packages.json
- proxies.json

The staticserver was Azure Functions Proxy, which handled all the request to https://ourpwa.azurewebsites.net/ui/*, and served files from wwwroot folder. For example, if user requests for /ui/home.html it will simply look for “home.html” under wwwroot folder and return it.

API were mapped as follows

https://ourpwa.azurewebsites.net/api/v1/abc
https://ourpwa.azurewebsites.net/api/v1/xyz

Azure Table storage and Blob storage used to store the data. azure-storage and uuid are only two npm packages used to implement various API’s.

Everything worked well, local development and debugging was pretty easy. Refer code and test azure functions locally for instructions. Do note that version 2.x do not have support for proxies yet, so you have to use runtime version 1.x.

Part 2 : Deployment to Azure and fight with cold start

Deployment with “local git” was also simple, just a “git commit” was enough to get everything up and running on Azure.

The trial runs were also pretty impressive, but soon we hit the road block, the pain of “cold start”. Under consumption based model, Azure do not keep your functions deployed 24 * 7. For any new request, Azure will first deploy the functions to some VM, and then let them serve the request. This cold start was taking about 20 seconds, some times 80-90 seconds. Subsequent request were served in almost 50 ms to 500 ms. From our observation, functions remains deployed for about 10 minutes of inactivity and then they are flushed.

Azure Function Response Time — Red indicates cold start

This was a big problem. An user interacting with PWA after a while, was facing significant performance issue.

This is known problem and discussed at lengths here and here. Common solutions are pinging the azure function at regular interval or moving to “Always On” option. Both defeat the purpose of consumption based model of paying only when your function is running.

Part 3: Temporary solution Always On

Team needed some solution, converting it back to Node.js based Azure web app was one of the option. Azure functions can be hosted with “Always On” option under App Service Plan. In the App Service plan, your function apps run on dedicated VMs similar to web app, which solves the problem of cold start but comes with cost. Refer Azure functions scaling and hosting for more details.

We wrote some powershell scripts to upgrade to “Always On” during busy hours and then downgrade to “Consumption Plan”.

Script to turn “Always On” for Function App

$ResourceGroupName = "YOUR_RSGRP"
$FunctionAppName = "YOUR_FUNCAPP"
$WebAppResourceType = "microsoft.web/sites"
$TempPlanName = "ALWAYS_ON_PLAN"
$PlanLocation = "PLAN_LOCATION"
$WebAppPropertiesObject = @{"siteConfig" = @{"AlwaysOn" = $true}}
#Create new plan
New-AzureRmAppServicePlan -ResourceGroupName $ResourceGroupName
-Name $TempPlanName
-Location $PlanLocation
-Tier "Basic"
-NumberofWorkers 1
-WorkerSize "Small"
#Set the web app to the new plan
Set-AzureRmWebApp -ResourceGroupName $ResourceGroupName
-Name $FunctionAppName
-AppServicePlan $TempPlanName
#Set the alwayson to true
$webAppResource = Get-AzureRmResource
-ResourceType $WebAppResourceType
-ResourceGroupName $ResourceGroupName
-ResourceName $FunctionAppName
$webAppResource | Set-AzureRmResource
-PropertyObject $WebAppPropertiesObject

Script to revert back to consumption plan

$ResourceGroupName = "YOUR_RSGRP"
$FunctionAppName = "YOUR_FUNCAPP"
$WebAppResourceType = "microsoft.web/sites"
$TempPlanName = "ALWAYS_ON_PLAN"
$PayAsYouGoPlanName = "CONSUMPTION_PLAN"
$PlanLocation = "PLAN_LOCATION"
$WebAppPropertiesObject = @{"siteConfig" = @{"AlwaysOn" = $false}}

#Set the always on to false
#without that you wont be able to change the plan

$webAppResource = Get-AzureRmResource
-ResourceType $WebAppResourceType
-ResourceGroupName $ResourceGroupName
-ResourceName $FunctionAppName
$webAppResource | Set-AzureRmResource
-PropertyObject $WebAppPropertiesObject
#Update the plan for Azure function app
Set-AzureRmWebApp -ResourceGroupName $ResourceGroupName
-Name $FunctionAppName
-AppServicePlan $PayAsYouGoPlanName
#Delete the plan
Remove-AzureRmAppServicePlan -ResourceGroupName $ResourceGroupName
-Name $TempPlanName

Important : An empty azure service plan costs same as azure service plan hosting an application, so always ensure to delete azure service plans which are not attached to any app.

This provided temporary relief from cold start problem, but at a cost of basic app service plan.

Part 4: Getting rid of npm packages, Webpack to rescue.

After investigation, it was discovered that most of the time is taken to restore npm packages on the VM during cold start. To get rid of npm packages, we decided to package functions using webpack. Typically while building node applications with webpack, “webpack-node-externals” helps to not to bundle its node modules dependencies. But here there is need to bundle all the dependencies together and create one single file. Using tree shaking technique with the help of UglifyJSPlugin, the bundle can be further optimized.

At the same time, we converted the functions to TypeScript. Here is the updated structure

App
- staticserver/
index.js
function.json
wwwroot ==> contained all the static files generated by Webpack
for SPA
- abc/
index.js
function.json
- xyz/
index.js
function.json
- proxies.json

Note : Now every index.js is kind of complied JavaScript, holding all the code it is required to run, without any dependencies node_modules. There is no package.json. Azure looks for package.json and restores the packages before deploying, impacting the cold start time. Ensure that there are no package.json even under a sub-folder.

Time for result

Red box indicates cold start before removing npm dependencies, blue box shows cold start time after removing npm dependencies.

So removing npm dependencies helped us to reduce cold start time from 21 seconds to 3 sec. 3 sec was good enough for our application.

Parting thoughts

  • Removing the npm dependencies makes Azure Function an attractive model to host low traffic PWA’s.
  • If you have a website with significant amount of traffic, Azure Functions is not the option, better to go with Azure web app or other PaaS based options.
  • Azure team has also released a tool Funpack to solve this problem, check this msdn article for more details.

Hope you found this article useful, feel free to leave comments.

--

--