Challenges of Implementing a Serverless Architecture
When faced with a problem, we first understand it thoroughly to find an appropriate solution. We apply this same approach to each project, creating solutions that adapt to our clients’ needs and allow them to achieve their goals. We call this step the creation of a solution outline.
One of the first processes we implement as part of this step is to design an architecture for the system, where we establish how all the components will work together. For this design, we typically use a given architecture as a reference. Among the architectures we often use is the serverless architecture, which we have used frequently in the last year. However, designing an architecture is not a simple task. Once chosen, it brings with it the challenges of materializing it, along with the learning curve of the team that will be working on the project.
In this article, we will share the challenges we face when implementing a serverless architecture as well as its phases.
Phase I – Resource management and deployment
The main characteristic of this architecture is that everything must be executed with independent and decentralized functions, and thus need to have an explicit plan for management. For small projects, manual management may be sufficient. However, an appropriate strategy is required for larger projects with large teams and extensive resources.
Next, we present a set of tools that allow the creation, elimination, resource counting, documentation (the code used as such) of the infrastructure and deployment of changes effectively:
In our case, we use SAM from AWS. It allows us to share documentation with CloudFormation, another AWS technology. SAM has a long history in the market with a large community and support from the same provider.
In other types of architectures with server-side code, the size of this artifact is not a key factor. This is because most of those architectures have a running executable that handles information requests. This is not the case for Serverless, as we do not have an always-on active server. Instead, we have a function that has a lifecycle when invoked. This lifecycle creates the executable, handles the information request, and finally leaves the runtime space.
Each independent function is called a Lambda Function in the AWS Serverless architecture and contains the following phases in its lifecycle:
-
- Init: Executable initiation.
- Invoke: Execution of the code.
- Shutdown: Infrastructure release.
Consequently, if an artifact is too complex and needs a lot of resources, the creation of the executables takes longer, which affects (or influences) the response time.
To lower the risk of having underperforming Lambda Functions, AWS applies the following size-related restrictions:
-
- 50 MB of compressed items for direct upload.
- 250 MB of uncompressed items, including layers.
- 3 MB on the console editor.
- 512 KB maximum for an individual file.
As the size of the artifacts that make the Lambda Function is so important, there’s reasonable pressure to reduce it. To achieve this reduction, we perform the following tasks:
-
- Identify unused dependencies: Development dependencies, unused dependencies, and dependencies only for making scripts. These dependencies are not part of the runtime.
- Identify dead code: Eliminate all the code that is not being executed in your application.
- Limit yourself to the necessary development: Carry out only the essential implementations for the solution to function. Avoid adding elements with the argument that they could be useful in the future.
- Limit each function to the necessary dependency: Each function (or endpoint) should be limited to using the necessary dependency for its operation.
To ensure correct operation, Serverless has several service limitations, including:
- Maximum execution time of a function:
- Lambda functions: 15 minutes
- API Gateways: 30 seconds
While a function’s maximum execution time is more than sufficient, creating an endpoint for applications and web pages to consume information highlights API Gateways’ 30-second limitation.
To meet these time constraints, there are various alternatives for functions that perform very long-running tasks:
-
- Asynchronous Endpoints: These endpoints do not immediately return information but rather initiate the execution process and allow you to retrieve the information when it is ready using another endpoint. This ensures that your processing time occurs in a function without the API Gateway time limitation.
- Use Message Queues: Send the information to the server code so it can be placed in a queue for pre-processing.
- Use Cache: Pre-process information in a cache to guarantee a fast response time.
- Utilize Webhooks: If you have an integration with an external service, check if it offers webhooks to avoid constant queries. Otherwise, create pre-built storage (similar to the caching technique) to eliminate this response time.
- Maximum size of environment variables: 4 Kilobytes
We tend to use many environment variables to configure our application for different environments (allowing the same codebase to run in various environments). However, in the case of Lambda, the size of the variables we could have is limited. We did not find any elaborate solution in this case, so we only used the strictly necessary variables.
- Payload size: 10 Megabytes
This restriction forced us to adapt our solution to projects handling high-quality multimedia. To overcome this limitation, we leverage an alternative offered by the provider: uploading files directly to their storage service (S3) using presigned URLs.
Since our code is decoupled and each Lambda is an independent artifact, developing an effective way to share code and dependencies was a challenge. To address this, we have several alternatives that should be selected based on the specific needs of our project.
-
- Mono-Repository: The idea is to maintain all code in a single repository, compile using tools like Webpack, or create artifacts for each Lambda.
- Lambda Layer: This proprietary AWS solution involves creating a shared dependency between all Lambdas (or several), which can be reused in different functions. Because it is pre-processed, it also allows us to improve execution time.
- Multiple Repositories: Create different repositories and use them as dependencies in the Lambdas where needed.
In our case, Lambda Layers allowed us to improve response time and easily share dependencies and general code. However, this increased the size of the Lambdas during execution.
As you can see, several factors (such as Lambda size) can affect the entire serverless architecture. By understanding these limitations upfront, we can design a viable solution that works within the constraints of the serverless platform.
Sources:
Lambda quotas:
https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html
Env size:
https://aws.amazon.com/premiumsupport/knowledge-center/lambda-environment-variable-size/
Lambda lifecycle:
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html#runtimes-lifecycle-ib
Lambda layers:
https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
SAM:
https://aws.amazon.com/serverless/sam/
Serverless:
https://www.serverless.com/
Presigned URL:
https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html
Cloudformation:
https://aws.amazon.com/cloudformation/