Execution context in AWS Lambda

AWS Lambda is a serverless computing service that lets you run code without provisioning or managing servers. It executes your code in response to events like HTTP requests, modifications to records in databases, changes to files in S3 buckets, and more.

Execution Context in AWS Lambda:

An execution context is an environment in which the Lambda function runs. It includes everything required to run the function, like system libraries, third-party libraries, initialization code, and the function code. Once AWS Lambda initializes an execution context for a function, it can be reused for multiple invocations of that function, which reduces the overhead of setting up a new context.

Lambda scrubs the memory before it is assigned to an execution environment. Execution environments are run on hardware virtualized virtual machines (MicroVMs) which are dedicated to a single AWS account. Execution environments are never shared across functions and MicroVMs are never shared across AWS accounts. This is the isolation model for the Lambda service:

Key points:

  1. Container Reuse: When a Lambda function is invoked, AWS might reuse the execution context to serve a subsequent request, if it's available.

  2. Initialization Code: Any code outside the function handler is considered initialization code. This runs once per execution context. Useful for setting up database connections, SDK clients, or other resources.

  3. Temporary Disk Space: /tmp space of 10240MB (10GB) is available, which is retained across invocations in the same context.

  4. Variables Retention: Any variable declared outside the function handler might retain its value between multiple invocations if they're served from the same execution context.

Lambda execution environment lifecycle:

The execution environment also provides lifecycle support for the function's runtime and any external extensions associated with your function.

The function's runtime communicates with Lambda using the Runtime API. Extensions communicate with Lambda using the Extensions API. Extensions can also receive log messages and other telemetry from the function by using the Telemetry API.

When you create your Lambda function, you specify configuration information, such as the amount of memory available and the maximum execution time allowed for your function. Lambda uses this information to set up the execution environment.

The function's runtime and each external extension are processes that run within the execution environment. Permissions, resources, credentials, and environment variables are shared between the function and the extensions.

Each phase starts with an event that Lambda sends to the runtime and to all registered extensions.

The runtime and each extension indicate completion by sending a Next API request. Lambda freezes the execution environment when the runtime and each extension have completed and there are no pending events.

Best Practices:

  1. Initialization Optimization: Move heavy lifting code, such as SDK initialization and database connection setups, outside the function handler for better reuse.

  2. Connection Management: Reuse database connections across invocations. Be aware of connection limits and ensure you're not creating a new connection every time.

  3. State Management: Since execution contexts can be reused, avoid relying on a stored state inside one.

  4. Cleanup: If you're writing files to /tmp, clean them up if you're concerned about accumulating data across invocations.

  5. Monitoring: Monitor your function with AWS CloudWatch to understand the frequency of cold starts (new execution context initializations).

Code Examples:

  1. Initialization outside the handler:
import boto3

# Initialization outside the handler
client = boto3.client('s3')

def lambda_handler(event, context):
    response = client.list_buckets()
    return {"body": response['Buckets']}
  1. Database connection reuse: (This is a hypothetical example; real-world usage might require better error handling and management.)
import psycopg2

connection = None

def get_connection():
    global connection
    if connection is None:
        connection = psycopg2.connect(host="your_host", database="your_db", user="your_user", password="your_pass")
    return connection

def lambda_handler(event, context):
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT version();")
    record = cursor.fetchone()
    return {"body": record}
  1. Using /tmp space:
def lambda_handler(event, context):
    with open('/tmp/tempfile.txt', 'w') as f:
        f.write('Hello, World!')

    # ... process the file ...

    return {"body": "File created and processed"}

Always remember to clean up resources and watch for potential pitfalls when relying on the execution context reuse, especially when it comes to state and connections.

References:

  1. Lambda execution environment

  2. Understanding the Lambda execution environment

  3. AWS Lambda Now Supports Up to 10 GB Ephemeral Storage