As I'm currently writing the official "Spring Boot on Azure Functions" documentation on the Azure documentation website, I published a sample application and received quite a lot of comments about the performance and associated cost that we can expect from such a setup.
As a result, I've done some quite extensive tests, which I will try to summarize here. Please note that the pricing model of Azure Functions is very important here, so if you want the most cost-efficient solution, you should first study this model in detail, and plan your application deployment accordingly.
I will update this blog post once my "Spring Boot on Azure Functions" documentation is published on the official documentation website.
It has different pricing models, including some "premium" ones, but the classical use-case here is to replace a simple application by a set of functions, in order to gain the associated operations costs.
As we are talking about Spring Boot, this blog post will focus on Web applications, but please note that Azure Functions is not limited to Web applications : they are just one kind of Azure Functions, that happen to be triggered by an HTTP call. Several other types of events can trigger an Azure Function, typically if you are doing batch processing or IoT.
Spring Cloud Function is an official Pivotal project, that aims to run Spring Boot on serverless providers. It includes a specific Azure provider, so you can easily run a Spring Boot application as an Azure Function.
In order to host functions without any up-front cost, serverless providers put those functions to "sleep" when they are not used. When a function is called, the serverless provider then needs wake up that function: it finds a server available to run it, mounts its filesystem, and then executes that function.
This inherently takes time, and there are usually two approaches to go around that issue :
- Some people use some kind of cron job to wake up their function at regular interval: this is rather hacky (as you never know when the function will become inactive) and doesn't really follow the philosophy of serverless functions.
- If you are using Azure, buy a "premium" version of Azure Functions, where you can have "always warm" functions. This will cost more money, of course, and remove most of the problem.
Now, how long is a "cold start"? There is no official number here, and Azure Functions is constantly improving, so it's hard to tell, but it will take a few seconds. Also, launching a simple Spring Boot application like my sample application will take about 4 seconds, and that does not depend on the cloud provider.
Here are the logs of the sample application starting:
Switching to another JVM framework probably won't help much here: you can maybe win a couple of seconds by removing Spring, but a "cold start" will always take a few seconds globally.
Please note that there is no official metrics telling when an application becomes inactive, but you can find on various blogs that it takes currently about 20 minutes of inactivity.
You can eliminate most cold start issues if you buy a "premium" instance of Azure Functions :
- Those instances are much more powerful, resulting in a "cold start" that will easily be under 5 seconds (including less than 2 seconds for Spring Boot to start).
- They also provide "pre-warmed" instances which will remove most of the issue anyway, as there will always be active instances ready to serve requests.
Once the "cold start" issue is passed, as we can see from the graph below, most of our requests take less than 100 ms :
This 100 ms mark isn't taken here by random: this is the minimum execution time that will be billed to you, per function execution. So it is very important to stay under that execution time, but from a cost perspective it doesn't make much interest to go below 100 ms. This is why it probably doesn't make sense to tune anything here.
From the graph below, our application consistently use less than 1 Gb of RAM:
From my experience, you can run complex Spring Boot application with less than 512 Mb, and if you tune them well you can probably go under 256 Mb. As Azure Functions bills you by slices of 128Mb, this is probably where you can save up on your bill, rather than work on the execution time. Still, it's not possible to set up
JAVA_OPTS on normal Azure Functions (you need to use a "premium" plan to benefit from this option), so if you are using a JVM this is very hard to tune.
Please note that all calculations are done at the time of this blog post's publication, using the official pricing documentation.
Let's do calculations for a "low traffic" function. By "low traffic", we mean our application will serve 1 request every second, during half of the day. So this is typical of a business application with a few users. Also, as "cold starts" unofficially occur when an application is inactive more than 20 minutes, this should not happen very often with such an application, and we can thus ignore them for this calculation.
This cost is split into two parts, first the consumption part :
- 60*60*24*30*0.5 = 1,290,000 executions
- As execution time is (most of the time) less than 100ms, this transforms into 129,000 seconds
- As we saw in the graph we use way less than 1 Gb of RAM, so we can suppose it will take less than 7 slides of 128 mb (or 896 mb). So this transforms our previous calculation in 129,000*896/1024 = 112,875 Gb/s
- As this is well under the 400,000 Gb/s we have for free, there is no risk this would cost anything
Then, the execution cost part:
- Again, 60*60*24*30*0.5 = 1,290,000 executions
- As we have 1,000,000 free executions, this leaves 290,000 to pay for
- As execution cost is $0,2 per 1,000,000 execution, this is going to cost a few cents
As a result this "low traffic" option will be free for us. Unless we have a big spike in traffic, we have a very limited risk that this costs more than a few cents.
We're going to take the same calculation, but this time we have 10 requests every second, still during half of the day.
Here is the consumption part :
- 10*60*60*24*30*0.5 = 12,900,000 executions
- As execution time is less than 100ms, this transforms into 1,290,000 seconds
- Which makes 1,290,000*896/1024 = 1,128,750 Gb/s
- As we still have 400,000 Gb/s for free, we would pay in total for 728,750 Gb/s
- So final consumption price would be 0,000016*728,750 = $11,66
And for the execution part :
- Again, 10*60*60*24*30*0.5 = 12,900,000 executions
- As we have 1,000,000 free executions, this leaves 11,900,000 to pay for
- As execution cost is $0,2 per 1,000,000 execution, this is $2,38
As a result, the total price for this "medium traffic" function is about $14 per month.
And now we're going to do a "high traffic" function, with 50 requests every second, still during half of the day. Be careful here, because there's a trick: unless other serverless providers, Azure Functions can run your functions in parallel. So, for 50 requests per second, we are pretty sure that Azure Function will execute several functions in parallel on the same instance. This means we will have less "cold start" issues on Azure, which is of course great news.
Here is the consumption part :
- 50*60*60*24*30*0.5 = 64,800,000 executions
- As execution time is less than 100ms, this transforms into 6,480,000 seconds
- Which makes 1,290,000*896/1024 = 5,670,000 Gb/s
- As we still have 400,000 Gb/s for free, we would pay in total for 5,270,000 Gb/s
- So final consumption price would be 0,000016*5,270,000 = $84,32
And for the execution part :
- Again, 50*60*60*24*30*0.5 = 64,800,000 executions
- As we have 1,000,000 free executions, this leaves 63,800,000 to pay for
- As execution cost is $0,2 per 1,000,000 execution, this is $12,76
As a result, the total price for this "high traffic" function is about $97 per month.
Spring Boot works awesomely well on Azure Functions! There are just a few things to remember :
- Cold starts can be very annoying, but they will only occur if you have very low traffic. If that's important to you, you can also avoid them completely by buying a "premium" plan.
- Execution time is very important to keep costs down, and Spring Boot (and the JVM!) should be fast enough to guarantee a good pricing here.
- Memory is a bigger issue, and probably will constantly be an issue for all JVM-based frameworks, as it's not possible to set up
JAVA_OPTSoutside of a "premium plan".
- Prices are extremely low, even for applications with 10 to 50 requests per second, and thus have quite a big number of concurrent users.