A function is the primary concept in Azure Functions. However, in terms of and configuration and deployment the primary concept is the Azure Function App. The Function App is the runtime host that contains one or more functions. A Function App is the runtime configuration, application settings and runtime host shared by individual functions that collectively make up the function app’s runtime components.
The organization of your function apps directly affect the performance, maintainability, and cost of solutions that take advantage of function apps.
There really are no hard and fast rules about deciding how to organize your functions into function apps, but this post is about some of the things I personally take into account when I build my own function apps in no particular order.
1… First and foremost, aim strong cohesion and loose coupling
Cohesion means that the functions belong together. This one is easy! A function app with a grab bag of unrelated functions is a bad idea. With consumption based plans there is a negligible difference between one function app with five functions or five function apps with containing a single function. In either case you are charged based on invocations. However, it is possible for two functions to be logically cohesive but still choose to deploy the functions as separate function apps for reasons related to runtime performance or scalability needs.
Coupling is the degree to which the functions in a function app depend on each other and also the degree to which a function depends on the runtime host. In both cases the goal is to have none because that means we can deploy a function wherever we want and move it around easily and not just between function apps but also other types of runtime environment including AWS Lambda and traditional servers. However, if functions are highly cohesive, it makes sense to accept some coupling for shared management and configuration. There is no generic advantage to a grab-bag function, but there are disadvantages to isolating related functions into different function apps.
2… Organize according to usage patterns and performance profiles
Azure function invocation is based on triggers. When something happens, the event trigger fires the function. This could be from a popular application calling a web service with a user who is eagerly waiting for a reply or it could be an event type that happens frequently but not continuously that runs a function that takes minutes to complete and uses significant resources.
The Scale Controller monitors the rate of events and determines whether to scale in or out. Because these two functions have completely different runtime characteristics the result is unlikely to be optimal. Consider that, if the combination of unrelated performance profiles is difficult for software, how bad is it for the poor human!
I feel pretty strongly that Application Insights is a must have addition to any function app. One thing I like about it is how useful it is out of the box.
Mixing things that are fast with things that are slow makes it much less useful because are those spikes expected because the slow thing is happening or is it because the thing that should be fast is slow?
3… Don’t Deploy Functions Together Unless You Can Accept the Requirement to Version Them Together
If you bundle functions together into a function app, always version and test the functions as a set. Build them together and deploy them together.
Imagine that you have multiple functions in a function app and you are using precompiled functions. In your implementation, each of your functions is implemented in its own .NET class library. You use NuGet and some function class libraries have shared dependencies that are indirect and could be different versions. In a normal .NET executable or like a console application or a web site. The final program folder when everything is built has an app.config or web.config file that specifies how to handle conflicts via a .NET feature known as Assembly Binding Redirection.
Your function app might be broken from the start or work fine at first, but stop working after updating a NuGet package in one of the function assemblies with an exception –
“Could not load file or assembly ‘…’ or one of its dependencies. The system cannot find the file specified.”
“Exception has been thrown by the target of an invocation. …: Method not found: …”
The dots in both cases are whichever of the conflicting assemblies loads second because of the conflict.
The easiest solution to this problem is to not have a conflict by resolving the conflict to a single version. If this can’t be done, moving the functions into their own function apps.
4… If you require a traditional app service plan, use a traditional app service plan
A consumption plan is not always the best fit for every scenario. If you need a job that takes more than 10 minutes or a web service that should always be available in loaded instances (so as to never make a user wait for loading and scaling) use an App Service Plan instead of a consumption plan. In most cases it will not be worth the time to try to find ways to work around the known limits of the consumption plan. If you have significant free capacity, consider hosting other compute functionality in this app service plan, but remember tip #2.
5… If you need auto-scaling or you don’t have a requirement that prevents it, use a consumption plan
It is probably waaaay cheaper and more likely to be optimal than whatever manual tuning you try.