Serverless Optimizations

Although Cdev provides a general purpose cloud deployment framework, we are also focused on improving the serverless development experience. Although Serverless can refer to a wide range of different services, this section is focused on our optimizations around Serverless Compute platforms like Aws Lambda.


Serverless Function Parsing

One of the biggest problems when migrating to a Serverless Compute platform is having to think about how the change in the underlying platform creates problems like cold starts.

Cdev introduces a set of parsing tools that attempt to understand the dependencies of a Serverless function, so that it can deploy only the needed dependencies of the function. To understand Cdev’s parsing technology it helps to start with an example and understand all the parts that can add time to Cold Start. In the example, there are three handler functions that each interact with a different external AWS service, but they are part of the same backend and make sense to be kept in the same file.


    
import boto3 dynamodb_client = boto3.client("dynamodb") s3_client = boto3.client("s3") sqs_client = boto3.client("sqs") def MyHandler1(event, context): # This function connects to a dynamodb table to look # up information and return it to the user # info = dynamodb_client.lookup(event.get("user_id")) # Other code # # # return info def MyHandler2(event, context): # This function gets an object from s3 and returns it to the user object = s3_client.get_object(event.get("item_key")) # Other Code # # # return object def MyHandler3(event, context): # This function creates an item in an sqs queue that triggers downstream tasks sqs_client.put_item(event.get("sqs_event")) return {}

When a python module is loaded, all the code in the top level namespace is executed, which means for our function, it executes all three boto3.client calls. These extra global statements adds unnecessary time to each function’s Cold Start because each function only uses a single external AWS service, but a connection is made to each of the external AWS services. Connections to external systems add extra latency because the initial creation of the connection require rounds trips between the systems before communication can start.


By analyzing the syntax tree representation of the code, Cdev is able to understand what parts of the global namespace are needed for each individual function. With this understanding, we can parse each individual handler function along with the needed global statements into a separate file. These newly created files can be used as the deployment final artifact.

For the above example, the final deployment artifacts using Cdev would be:

myhandler1.py

    
import boto3 dynamodb_client = boto3.client("dynamodb") def MyHandler1(event, context): # This function connects to a dynamodb table to look # up information and return it to the user # info = dynamodb_client.lookup(event.get("user_id")) # Other code # # # return info

myhandler2.py

    
import boto3 s3_client = boto3.client("s3") def MyHandler2(event, context): # This function gets an object from s3 and returns it to the user object = s3_client.get_object(event.get("item_key")) # Other Code # # # return object

myhandler3.py

    
import boto3 sqs_client = boto3.client("sqs") def MyHandler3(event, context): # This function creates an item in an sqs queue that triggers downstream tasks sqs_client.put_item(event.get("sqs_event")) return {}


Using the intermediate files as the deployment artifacts also allows for fine grain tracking of changes to each individual function. Without this optomization, updates to a global statement would cause an update to all function handlers within the file.

Limits of Function Parsing
Using the syntax tree to understand a function’s dependencies covers a large majority of situations, but it is technically not able to handle all possible situations. To understand how Cdev provides tools to fill these gaps check out the API documentation for Lambda function annotations