Image Source: Pixabay
What is Serverless Debugging?
Serverless computing is an event-driven application design and deployment paradigm that powers distributed computing resources to provide scalable cloud services. In traditional application deployment, computing resources are a fixed resource, but in a serverless IT deployment, customers only pay for actual usage, and when resources are not used they are immediately shut down.
Serverless has changed the way applications are designed and built, as well as the debugging process:
- For serverless applications, logging is the most common method of debugging issues. It is important to record everything the service does so that developers understand the behavior of the application.
- Serverless workflows often have more components, leading to more single points of failure. The main difference between debugging container-based microservices applications and serverless applications is that you cannot run all of these components locally.
- By default, serverless applications do not provide remote debugging options.
- Serverless applications operate across processes, physical machines and networks. Malfunctions can occur in any part of the environment, most of which is outside the developer or operator’s control.
- It is difficult to determine whether compatibility or interfaces are broken, due to loose dependencies between components.
In the rest of this article, you will find tips for debugging and improving reliability of applications in three popular cloud services:
- AWS Lambda
- Google Cloud Functions
- Azure Functions
AWS Lambda Debugging Tips
AWS Lambda is an on-demand serverless IT service that provides developers with an event-based, fully managed, cloud-based system to run their code. Below are guidelines for avoiding issues and improving performance in Lambda applications.
Perform a load test on a Lambda function to determine the best time-out value. Analyze the execution time of each function, to better identify service-dependent issues that may increase the number of instances of the function running in parallel (known as concurrency). This is especially important if your Lambda function is making network calls to resources that cannot handle high scale.
Reuse elements in the runtime environment to improve performance of your functions and avoid errors. Perform initialization activities, like importing client SDK and connecting to database, outside the function handler. Prefer to cache static files in the /tmp directory, so that every additional instance of the same function can reuse them. This reduces the time each function runs, saves costs and improves performance.
Avoid Recursive Code
Do not use recursive code in Lambda functions (recursive code means the function calls itself over and over until a condition is met). This can cause an unexpected number of function calls and an increase in cost. If you have a running function with recursive code, you may find it difficult to get rid of running instances. To do this, set reserved concurrency to zero, avoiding requests from being routed to the function, and correct the code.
Watch Function Metrics
Instead of creating or updating metrics yourself in your Lambda function code, use AWS Lambda function metrics and CloudWatch alarms. This is a more efficient way to track the health of your lambda function and catch issues during the development process. For example, you can set an alarm based on the expected duration of a call, to identify requests that are taking longer than expected and investigate the issue.
Use Amazon X-Ray
It is difficult to perform monitoring and debugging of serverless functions without dedicated tools. As part of the AWS monitoring ecosystem, Amazon provides X-Ray, a distributed tracing system. X-Ray provides visibility over serverless applications and the underlying services, helping you identify the root cause of issues, both in production and development environments. X-Ray shows a map of serverless application components, which can be extremely useful for understanding application flows and where problems occur.
Google Cloud Functions Debugging Tips
Google Cloud Functions is a lightweight IT solution that enables developers to create standalone, single-use functions to respond to events in the cloud without managing a server or runtime environment. Below are a few tips for troubleshooting and improving reliability of Google Functions.
Write Idempotent Functions
When called more than once, a function should always produce the same result. This makes debugging easier because if code fails midway, you can retry an invocation on a new instance of the function.
Ensure HTTP Functions Send an HTTP Response
If a function is triggered by HTTP, don’t forget to send an HTTP response. Otherwise, the function will continue running until timeout and waste resources. Timeout may result in unpredictable behavior or freeze subsequent calls, and should be avoided if possible.
Avoid Background Activities
A background activity is something that occurs after the end of a function. When the function returns a result, or uses callback in a Node.js background function, the code will not have access to the CPU and will not run.
You can usually detect background activity by looking through logs and finding activity after the invocation is complete. If there are asynchronous tasks in your application, you may discover background activity deep in your code. You must make sure all asynchronous operations finish before the function exits.
Reuse Objects Using Global Objects
Google Functions does not guarantee that the state of a function will be maintained when it is invoked again in the future. However, functions typically reuse the execution environment of previous functions, so global variables do survive, at least for several invocations.
This allows you to cache computationally-expensive objects, and avoid having to call them again every time a function is called. Moving these elements from the main function to the global scope can significantly improve performance.
Azure Functions Debugging Tips
Azure Function is a serverless IT service on Microsoft Azure. Here are some ways to debug and troubleshoot your Azure Functions apps.
Avoid Long Running Functions
Large, long-running tasks can cause unexpected timeouts. When possible, break down larger features into smaller, short running functions that work together to complete a task.
For example, webhooks may require confirmation within a certain time – a webhook will often require immediate response. The payload of the HTTP trigger can be queued for processing in the queue trigger function. This way you defer the work and provide a fast response.
Write Idempotent, Stateless Functions
Whenever possible, your functions should be idempotent (always returning the same result) and stateless (not assuming a prior state). Associate required state information with a data source. This is especially recommended for timer triggers.
For example, if you need to run a function once per hour, ensure that it can run at any time during the hour and deliver the same response, and also handle the case where the function will not be needed at all during an hour. Ensure that if a previous run did not finish running successfully, the next invocation picks up from where it left off.
Defensive functions are written with the assumption that an exception can happen at any time. Functions should be able to continue from the previous point of failure on the next run.
Depending on the complexity of the system, problems such as low quality of service, network outages, and quota limits may arise while a function is running. Functions need to be prepared for the worst, and any of these issues should not interrupt the runtime of your application.
This article described the basics of serverless debugging and provided tips and best practices for debugging three cloud-based serverless platforms. These tips included:
- Load testing your functions
- Reusing the environment and declaring global variables
- Avoiding recursive code
- Watching metrics
- Avoiding background activities
- Writing idempotent and stateless functions
- Avoiding long running functions
- Writing defensive functions that can fail and resume on the next invocation
Hopefully, this will help you improve the reliability of your serverless apps and spend less time debugging issues.