Serverless computing is an execution model for cloud computing in which provisioning of the server and dynamic allocation of the resources is managed by cloud providers. In this model, applications are written with custom code and run on an ephemeral container (known as FaaS). But despite the name, in reality, serverless applications run on servers. The only difference is, developers don't need to manage these servers, and application owners only have to pay for the execution time and resources being consumed.
Although there are many cloud providers who offer serverless platforms, AWS Lambda has become the most popular. It provides a platform where developers can write business logic as a function and host it on AWS without worrying about availability, scalability, or resilience.
Building Blocks of AWS Lambda
AWS Lambda alone cannot implement the entire flow of a serverless architecture. It needs several other components, which are divided into three parts:
Image 1 - AWS Lambda Building Block Diagram
Event Sources are also known as a trigger. This is the starting point of an AWS serverless flow that invokes the Lambda Function based on an event or a REST API call. Event sources can be of various types:
- API Gateway that invokes a Lambda through a REST API endpoint
- A change in a Database (DynamoDB) record
- A new file upload or update in an existing document of an S3 bucket
- New message pushed to an SQS queue
- CloudWatch Events that trigger a Lambda using a scheduled cron job
This is the core component of an AWS serverless architecture. It allows developers to write business logic and deploy on an AWS infrastructure without worrying about the provisioning of underneath compute required to run the code. It also takes care of several other non-functional requirements discussed below:
A Lambda Function supports both vertical and horizontal scalability. It allows vertical scalability to a maximum of 3 GB of memory, which has to be handled manually. Horizontal scalability is supported via concurrent computing, automatically spinning up additional instances of Lambda instances when the current running instances cannot handle the load. However, this is subject to a Regional limit and shared with all functions running in an AWS Region. Each Function’s concurrency can also be restricted using reserved concurrency features.
Like other AWS services, a Lambda service also follows the shared responsibility model for security. AWS takes ownership of securing the underneath infrastructure where a Lambda Function runs, while the application owner has the responsibility of ensuring the security of the Lambda identity access and data sensitivity.
A Lambda Function provides an execution role that grants it permission to access the other AWS services. This role must be created while creating a Lambda Function that has a default policy AWSLambdaBasicExecutionRole. You can add more policies to provide access to S3, CloudWatchLog, and other services based on the given use cases.
You can also restrict Lambda access to the VPC resources by configuring the subnets.
On top of AWS’ global resilient infrastructure across its AWS Regions and Availability Zones (AZ) on a global scale, Lambda provides a few additional features to support data resiliency and backups:
- Versioning: Lambda provides versioning and alias options while saving Function code. This feature is used to perform blue/green and rolling deployments.
- High Availability: AWS runs Lambda Functions in multiple AZs to ensure it is highly available in case of an AZ failure. If a Function is required to access VPC resources, a developer needs to specify subnets in multiple zones to ensure HA.
- Retry: Lambda is configured to automatically retry for asynchronous invocation failures. For synchronous failures, a calling service needs to handle retries.
- Dead letter queue: For asynchronous invocations, if all retries fail, Lambda drops the requests to a dead letter queue for later processing.
A Lambda Function alone is not sufficient to fulfill the needs of a business use case. A typical application would need many other services like DB, authentication/authorization, queues, and third-party systems to complete a request flow. AWS provides S3 and DynamoDB, two services that sit outside of a VPC and can be accessed by a Function running on a default VPC. However, if it needs to access the resources running in a VPC-like RDS DB, it needs to configure the specific VPC subnets.
New Features Launched for AWS Lambda
Over the years, AWS Lambda’s traction has increased exponentially. Along the way, users have been providing feedback to AWS on the shortcomings and additional features required for their particular use cases. AWS has incorporated this feedback and come up with several features over the years. Let’s talk about a few of the major ones.
As mentioned earlier, AWS manages the Lambda Function scalability on its own. However, it causes significantly high latency while spinning up new Lambda execution environments for instant burst traffic due to additional cold-start time.
This issue has been addressed by introducing a new feature called provisioned concurrency at re:Invent 2019. Provisioned concurrency enables an option to specify how many Lambda execution environments are required while launching the system. For example, if you configure 10, it will warm up 10 instances of the execution environment itself, ensuring a stable start-up time for every environment up to the limit given (here, 10) and reducing latency. As an AWS advanced tier partner, Thundra provided support for provisioned concurrency metrics, mixing it with Thundra Custom Runtime for Java and decreasing the cold start delay by 98%.
In an event-driven architecture, a Lambda Function would need an SQS, SNS, or EventBridge service to communicate with other services. Earlier, developers needed to write logic in a Function to invoke these services and handle failures, retries, and other events. So, AWS announced a new feature called Lambda Destinations, also at re:Invent 2019.
This feature enables a Lambda Function to specify the destination services with which a Function can integrate to pass along a response. It can handle both asynchronous and stream invocations and supports Lambda Function, SNS, SQS, and EventBridge as destination services. Using this feature, developers don't need to write manual logic to invoke these services, and it makes event-driven implementation very easy.
Most enterprises use SaaS platforms for fulfilling CRM, monitoring, and logging needs, and these platforms need to interact with AWS services to perform further processing on the data. To facilitate this integration, AWS launched the EventBridge service in July 2019.
This service routes real-time data from SaaS partners like Thundra, MongoDB, and Zendesk to AWS services. It makes it easy to build applications with event-driven architectures that are loosely coupled. It takes care of event ingestion and delivery through the event bus and also handles security and the handling of transaction errors.
Thundra supports tracing serverless event-driven apps end to end via distributed tracing. It generates rich metrics data and transforms it into actionable insights and can also integrate with EventBridge, allowing customers to remediate performance and security issues. Once alerts are sent to EventBridge, AWS Lambda or any other AWS resource can be triggered to use the event information in order to immediately execute the required response and remediate the issue.
Improved Lambda VPC Networking via Hyperplane
As mentioned earlier in this article, a Lambda Function supports connecting to a dedicated VPC if the Function needs to access the VPC resources. However, it compromises both latency and scalability. This is because, for every invocation, it creates an elastic network interface (ENI) in the VPC, which enables communication between the Lambda Function and customer-owned VPC, adding to the cold-start time.
In addition, Every ENI would need a dedicated IP address, and there is always a limit to the number of IP addresses that can be allocated to a subnet. This means that scalability of invocations depends on the availability of IP addresses.
With the introduction of Hyperplane ENI, network interfaces are now mapped with Hyperplane ENI and connected to a Function through it. This reduces the number of ENIs required, as it can group many invocations (based on a security group and subnet combination) to one ENI.
Common Patterns for Serverless Architecture
There are many patterns that teams use to build serverless architectures via Lambda. Let’s go through some of the most common ones:
This pattern addresses the problem of aggregating the results from different microservices. A Lambda Function can play the role of aggregator, collate the responses from each microservice, and send them back to the client through an API Gateway.
Image 2 - Aggregator Pattern
The Router pattern is all about routing the request to the application meant to handle a particular logic based on given conditions. Having a Function as a Service architecture, we generally have many functions performing different tasks. Using an API Gateway, we can route a particular request based on parameter values, header values, and method types to the Function. In the diagram below, API Gateway is calling only one Lambda Function at a time.
Image 3 - Router Pattern
A Lambda Function has a 15-minute timeout limit. A batch application or ETL task can easily surpass this limit. So, the Fan Out pattern helps to run these tasks in parallel, directly through coding in a Function or via a Step Function. A Step Function is a better option, as it provides a feature called parallel branching, which can run multiple Functions in parallel. It also reduces the overhead of doing parallel choreography manually.
Image 4 - Fan Out Pattern
There are several other patterns, such as streaming, notifications, and event handling, that are used in a Lambda Function to solve common problems.
Limitations of Serverless Architecture
Lambda Function can fit into most application development requirements, but it is really meant for Function as a Service architectures. Because of this, AWS has done a decent job in creating certain limits to ensure that it is used for what it was designed.
Let's go through a few of the limitations you should be aware of while designing applications around Lambda:
- Lambda Function timeout: A Lambda Function has a maximum 15-minute timeout limit, a hard limit that cannot be extended. If your workload needs additional execution time, you should consider breaking the logic into multiple Functions and use a Step Function or Lambda Destinations feature to execute it.
- API Gateway timeout: An API Gateway has a limit of 29 seconds for all of its integration endpoints. Any REST endpoint should not exceed 5-6 seconds for a good user experience.
- Concurrency: Lambda has a concurrency limit of 1,000, meaning all Functions together for an AWS account can have a maximum of 1,000 concurrent execution environments. An enterprise system may breach this limit very quickly, and you can extend it through AWS support.
- Vertical Scaling: Lambda supports a maximum of 3 GB of memory and a dual-core CPU. This cannot be increased, so, be wary before using it for very high CPU-intensive calculations.
- Testing: Testing and debugging a Lambda Function has always been a challenge. There are several frameworks, like AWS SAM and Serverless Framework, that have been evolved to address these issues. Recently, Thundra has come up with a nice solution itself that integrates your IDE with an AWS Lambda environment and helps you to perform remote debugging of code issues in your IDEs, including VScode and IntelliJ.
As serverless has become the de facto architecture for building new applications, AWS Lambda has made it easy for developers to host these applications on AWS. It has also been responding to user feedback by introducing new features every year. Born into serverless, Thundra provides end-to-end management of distributed applications across serverless architectures down to the line level of the runtime code for every function. Thundra also helps live remote debugging on IDE serverless applications with its recent feature addition.