When to use serverless architecture... and when not to
Serverless architecture promises to abstract away the underlying architecture and provide a more cost-effective model where you only pay for what you use. Concerns such as scalability and availability are baked into the underlying infrastructure.
Any service that offers to take care of the infrastructure can be described as “serverless”, be it a message queue, containerised service, database, or workflow. That said, “serverless architecture” is synonymous with Amazon's Lambda service, which allows you to build applications from individually deployable functions. Each major cloud vendor offers its own take on this function-based model, with Azure Functions and Google Cloud Functions providing roughly similar features.
In each case, the promise is that engineers can focus on delivering functionality, unencumbered by infrastructure concerns or a cost model that requires any up-front-investment. However, this serverless promise of abstracting away the infrastructure needs a little unpacking as the benefits are a little more nuanced than they may first appear.
An imperfect abstraction
The serverless promise offers a separation of concerns that can be a real productivity boost for engineers. In theory, they only need to focus on how code is structured, as opposed to the infrastructure that it runs on.
Despite this, it's not true to say that you can just write code and ship it without consequence. You still need to consider the demands of test and deployment automation, as well as concerns such as logging and monitoring. Serverless does not provide a perfect abstraction, and there will be times when you need to optimise your implementation based on an underlying understanding of your compute and memory requirements.
Given that the serverless provider takes care of the data centre, network, servers, and operating systems, it can be easy to overlook the security of individual function implementations. These are still vulnerable to a range of attack vectors which include broken authentication, injection risks, insecure dependencies, visible secrets, and over privileged access. They need to be secured with the same diligence as any other style of cloud architecture.
Paying for what you use
There is also the promise that you only pay for what you use, but this can be a double-edged sword. Serverless applications usually invoke other cloud services, such as API gateways, message queues, and data stores. It can be difficult for engineers to predict how much an application will really cost until after it goes into production.
Given that serverless environments will happily scale to serve inefficient and poorly optimised applications, it can be easy to make expensive mistakes. There's no discipline of having to tune processing to a finite set of resources in a serverless application, which can lead to unnecessary resource usage and cost.
The serverless promise of eternal scalability comes up against limits somewhere down the line. Not all your downstream services will have the same scaling characteristics unless you really can guarantee elasticity across the entire stack. Even if you eliminate these, you may eventually come up against budgetary constraints as the application scales well beyond your ability to pay for it.
Ease of development
Writing and shipping serverless functions is incredibly straightforward compared to the more onerous business of building services or monoliths. This support for rapid development and easy prototyping is one of the bigger benefits associated with serverless development, though they do have a tendency towards sprawl. Adopting a serverless architecture does not absolve you of the need to carefully plan - and document - your code organisation.
Given there is so little overhead involved in adding a new function, the risk is that engineers tend to do so all the time. There can be a temptation to address every new problem with a new function. You need very careful discipline to ensure that you don't build numerous different implementations of the same feature. It's easy to find yourself with a complex web of code that nobody remembers writing.
When does it make sense to use serverless functions?
Given this more nuanced view of the potential benefits of a serverless, what scenarios may benefit from it? As ever, it depends. With this kind of decision, you are making multiple trade-offs between speed of development, flexibility, scalability, and resilience.
That said, serverless architecture does have a few “sweet spots”.
1. Small applications that are likely to stay that way
If you have a relatively simple app with a predictably small level of traffic, then the “scale to zero” aspect of serverless can be very cost effective. You have 24\7 availability, the ability to scale to any spikes in demand, and almost no on-going infrastructure to maintain. The only catch is that costs can escalate as your user base grows, so you need to be sure that your small application will stay small.
2. Unpredictable peaks and troughs
Serverless architectures are good at handling the unknown. If you have unpredictable peaks and troughs, then a serverless architecture will guarantee that your application will meet them without the risk of over-provisioning unused infrastructure. There is, of course, the risk of runaway scale that can lead to some eye-watering bills, so it's worth considering some form of load shedding or rate limiting to ensure load does not pass a reasonable threshold.
3. Background processing tasks
Background tasks that you normally run on a schedule or in response to events can be a good fit for serverless functions. This includes web hooks where applications send automated event messages using HTTP. Care should be taken to ensure that these are not long running tasks as serverless functions are usually only designed to support short-lived execution (see “long running tasks” below).
When serverless should NOT be used
There are some cases where serverless architecture really doesn't provide a good fit.
1. Predictable (and high) traffic
If you have an application with both high and predictable demand, then you can optimise your architecture to ensure the right level of availability while minimising costs. It may be more cost effective to run a bunch of containerised services and add a few instances when the application is busy. There could be other solutions around buffering or caching to manage load in a different way. Once you know what problem you are solving, you can bring a more targeted set of solutions into play.
2. Long-running tasks
Most serverless platforms are designed to handle short-lived execution. At the time of writing, AWS Lambda has a maximum timeout of fifteen minutes for any function. This is better than it used to be, but it rules Lambdas out for any potentially long-running operation. Other cloud providers offer similar restrictions, with Azure functions having a ten-minute maximum on their pay-as-you-go plan and Google Cloud Functions limiting event-driven execution to nine minutes.
If you have any operation that might conceivably take more than a few minutes to execute, then serverless functions may not be the wisest choice. An operation might be well within the threshold to begin with, but you'll find that execution times may creep up over time in response to growing data volumes or declining performance of dependent systems. A sensible set of retry policies for external dependencies can also drive execution time up on a bad day.
3. When you need more control
Abstracting away the underlying infrastructure may sound attractive, but it does limit the control you have over the processing context. Serverless applications are not immune to performance problems, and your options for addressing them may be limited given that most of the operating context is not available to you.
This makes serverless architecture less appropriate where you have high memory or compute requirements. You can adjust the memory available to a function, but this is something of a blunt instrument compared to the pod scaling options available in something like Kubernetes.
Serverless architecture may also be inappropriate for those cases where performance and response times are the only thing that matters. Serverless applications tend to be associated with long “cold startup” times, i.e. the amount of time it takes to get an environment up and running when a function is invoked for the first time. Even after startup, performance can be sluggish if you have large functions or expensive external dependencies.
There are strategies you can adopt to mitigate cold startup times, such as maintaining a pool of “pre-warmed” functions or using Lambda's provisioned concurrency feature to reserve some active capacity. Performance issues can also be mitigated by tweaking memory size or code footprint. This is all some distance from promises of transparent infrastructure, limitless scalability, and guaranteed availability.
4. When you aren't sure of your requirements
The structure of serverless applications can be difficult to control and they can tend to sprawl over time. If you are taking an iterative and evolutionary approach to designing your application, then you may be better off building a monolithic architecture to begin with. This will make it easier to re-organise your code in response to your growing understanding of the requirements.
After all, it's much easier to decompose a monolith than it is to try and marshal a prematurely decomposed set of serverless functions. It may be best to wait until you have a better appreciation of the complexity of an application and its scaling characteristics before decomposing to a serverless architecture.