5.3. Using Lambda to Run Code without Worrying about Infrastructure

5.3.1. Introduction

  • Amazon EC2 allows users to manage virtual machines rather than physical servers.
  • AWS Lambda, launched in 2014, builds on EC2 by completely removing the user's responsibility for managing underlying infrastructure.
  • Users simply need to provide the code to run; AWS handles server provisioning and maintenance.

5.3.2. Lambda Abstracts Away Infrastructure Management, but It Doesn't Come without Trade-Offs

  • Users who are unfamiliar with Lambda may find this chapter crucial, as its functionality can be non-intuitive.
  • Lambda's on-demand provisioning of infrastructure introduces specific trade-offs.
  • The chapter will explore how Lambda operates and provide measures to mitigate its limitations.
5.3.2.1. Micro-Containers Running Your Code in the Background
  • Importance of understanding that "serverless" does not imply the absence of servers; they are simply abstracted from developers.
  • When a request is made to a Lambda function, AWS can either:
    1. Provision a new micro-container for the code to execute.
    2. Reuse an existing container that is not currently processing another request.
  • The first option incurs a delay due to the need for on-demand resource allocation.
5.3.2.2. Assigning Resources on-Demand Takes Time - What's Happening in a Cold Start
  • The bootstrapping process that prepares Lambda's environment for execution is referred to as a "cold start."
  • The cold start process includes:
    1. Downloading the function's code.
    2. Starting a new micro-container.
    3. Running the global initialization code.
    4. Executing the handler code.
  • The global initialization code is kept in memory as long as the micro-container remains provisioned.
  • Traditional container approaches (e.g., Fargate tasks with ECS) tend to have faster response times compared to Lambda due to the high contribution of cold starts in Lambda for the slowest requests.
5.3.2.3. A Micro-Container Can Only Serve One Request at a Time
  • Each Lambda micro-container can only handle one request simultaneously. If all containers are busy, a cold start will occur for new incoming requests.
  • In a scenario with five Lambda micro-containers, the initial requests were handled without reuse, leading to the sixth request triggering the reuse of a container only after the previous one finished.
5.3.2.4. Global Code Is Kept in Memory and Executed with High Memory and Compute Resources
  • Code executed outside of the handler method, known as bootstrap code, can create global variables that persist across multiple executions of the same micro-container.
  • Global initialization code is executed using a high memory configuration and high vCPUs for the initial 10 seconds at no charge.
  • To extend the lifespan of global context, regularly invoke the function with warm-up requests, although AWS ultimately will de-provision the environment after a certain timeframe regardless of invocation frequency.
5.3.2.5. Your Functions Can Be Invoked Synchronously and Asynchronously
  • Lambda functions can be invoked in two manners:
    • Synchronous (blocking): The execution must finish before returning a response. An example is a function triggered by an API Gateway that queries DynamoDB.
    • Asynchronous: The function is triggered, but the caller receives an immediate return. An example is S3 triggering a Lambda function for thumbnail generation after file upload.
  • Depending on whether you want to handle results immediately or not, the invocation type should be adjusted when invoking functions from other locations, like another Lambda function.

5.3.3. What's Necessary to Configure to Run Your Lambda Functions

  • When creating a Lambda function, users must define several environment properties, some of which can be changed later, while others are fixed after creation.
5.3.3.1. Choosing the Lambda Runtime and CPU Architecture
  • Lambda supports various runtimes such as Node.js, Python, Java, Ruby, and Go.
  • Users can deploy functions as ZIP files or Docker containers and can also bring their own runtime by using the "provided" option in a function's deployment package.
  • Users can select either x86 or ARM/Graviton2 processor architectures, with the Graviton2 offering up to 34% better price performance.
  • Cold start times vary by runtime; for example, Python and Node.js perform better than Java in general.
5.3.3.2. Finding the Perfect Memory Size Which Also Results in a Corresponding Number of VCPUs
  • The allocated memory size affects both the memory and vCPUs available for the Lambda function, which in turn influences computational speed.
  • Users are billed based on GB seconds, meaning higher memory settings incur higher costs, but they can also reduce execution time due to having more vCPUs.
5.3.3.3. Timeouts - A Hard Execution Time Limit for Your Function
  • Lambda functions are limited to a maximum execution time of 15 minutes, with timeouts defined by users.
  • When the timeout is reached, execution is forcibly terminated, leading to errors if the invocation was synchronous.
5.3.3.4. Execution Roles & Permissions - Attaching Permissions to Run Your Functions
  • The execution role assigned to a Lambda function establishes the permissions it holds during execution, such as accessing S3 buckets or CloudWatch logs.
  • It is a best practice to limit the execution role to the least privilege required for intended functionality.
5.3.3.5. Environment Variables For Passing Configurations To Your Functions
  • Environment variables are key-value pairs provided to Lambda functions, which can include custom entries or reserved variables like AWSREGION or XAMZNTRACEID.
  • They enable dynamic configuration without the need to hardcode variables into function code, facilitating easier deployment across different environments.
5.3.3.6. VPC Integration - Accessing Protected Resources within VPCs and Controlling Network Activity
  • Lambda functions can access resources inside a VPC with VPC attachment; this is necessary for services like ElastiCache.
  • VPC attachment allows for enhanced security as it limits outbound traffic but can increase cold start times and incur additional costs for data transfer.
5.3.3.7. Natively Invoke Lambda via Different AWS Services via Triggers
  • Lambda is integrated with several services, allowing functions to be triggered by specific events, such as API Gateway requests or S3 object lifecycle events.
  • This functionality is crucial for event-driven architectures that provide resilience against outages or errors.
5.3.3.8. Triggering Follow-Ups for Successful or Unsuccessful Invocations of Functions via Destinations
  • Destinations allow users to configure responses to successful and unsuccessful function executions.
  • For instance, failed invocations can be redirected to an SQS Dead-Letter-Queue for later analysis and reprocessing.
5.3.3.9. Code Signing to Ensure the Integrity of Deployment Packages
  • AWS Signer allows users to create signing profiles to ensure that only unaltered code is deployed. This ensures the integrity of the deployment package.
5.3.3.10. Using Unique Pointers to Functions via Aliases and Versioning
  • Users can manage different versions of their functions and use aliases to point to specific versions.
  • Using aliases simplifies invoking functions with event source mappings without needing to update each time a new version is published.

5.3.4. Reserved and Provisioned Concurrency to Guarantee Capacities and Reduce Cold Starts

  • Reserved and provisioned concurrency options are available to ensure performance by managing the availability of function instances.
5.3.4.1. Reserved Concurrency for Guaranteeing a Function's Concurrency Capacity
  • The default concurrent execution limit for a Lambda function is typically 1000; reserved concurrency limits the maximum parallel executions for specific functions.
  • Functions without declared reserved concurrency will consume any available unreserved capacity, which can lead to throttling under certain conditions.
5.3.4.2. Provisioned Concurrency for Reducing Cold Starts
  • Provisioned concurrency allows users to keep instances pre-warmed to counteract cold starts, at the cost of higher pricing and longer deployment times.

5.3.5. Layers Enable You to Externalize Your Dependencies

  • Lambda Layers allow for the inclusion of dependencies without the need to package them with each function, which reduces deployment size and simplifies updates.
  • Layer sizes are limited to 50 MB zipped and 250 MB unzipped including all dependencies.

5.3.6. Monitoring Your Functions with CloudWatch to Detect Issues

  • AWS Lambda integrates with CloudWatch by default, providing several metrics for monitoring function performance, including:
    • Invocations
    • Duration
    • Error Count and Success Rate
    • Throttles
    • Iterator Age
    • Async Delivery Failures
    • Concurrent Executions
  • Sufficient IAM permissions are needed to write logs to CloudWatch.

5.3.7. Going into Practice - Creating Our First Serverless Project

  • The practical section entails four steps for creating a small Lambda project.
5.3.7.1. Creating a Simple Node.js Function
  • Step-by-step guide for creating a simple Node.js Lambda function via the AWS Lambda console, defining the function name and runtime, etc.
  • The editor allows for testing and deploying code directly from the console.
5.3.7.2. Adding External Dependencies
  • Use npm to add Axios as a dependency.
  • Code will be modified to perform HTTP requests to reveal the function's external IP address.
5.3.7.3. Externalizing Dependencies into a Layer
  • Extract dependencies into a Lambda Layer to manage size limitations and improve update speeds.
  • Quick setup involves creating the correct folder structure, compressing the necessary files, and uploading the layer.
5.3.7.4. Invoking Another Lambda Function
  • Create a second Lambda function and provide necessary permissions for invocation.
  • Update the first function to invoke the second function synchronously and then asynchronously, demonstrating the impact of invocation types on execution time.

5.3.8. Exposing Your Function to the Internet with Function URLs

  • AWS Lambda can generate unique Function URLs for direct HTTP access, facilitating function exposure without additional infrastructure demands.

5.3.9. Attaching Shared Network Storage with EFS

  • EFS provides persistent storage options beyond the ephemeral /tmp directory, requiring VPC attachment and incurring costs for storage and data transfer.

5.3.10. Running Code as Close as Possible to Clients with Lambda@Edge

  • Lambda functions can also run at the edge via CloudFront, offering lower latencies for specific applications despite reduced capabilities.

5.3.11. Lambda Is Charged Based on Memory Settings, Execution Times, and Ephemeral Storage

  • Pricing is based on GB-seconds, with tiers allowing for a certain number of free requests and compute time each month.
  • Different memory settings affect billing and execution times.

5.3.12. Recursion Protection to Avoid Exploding Costs on Accidents

  • Lambda implements a feature to detect and stop recursion to prevent unintended charges, leveraging AWS X-Ray for tracking.

5.3.13. Speeding Up Cold Starts with SnapStart

  • SnapStart captures the initialized state of Java functions, reducing startup latencies by storing snapshots for rapid execution environment launches.

5.3.14. Lambda Comes with Hard and Soft Quotas and Limits

  • Important quotas include maximum concurrency, storage for uploaded functions, memory limits, timeout limits, layers per function, and sizes for invocation payloads and deployment packages.

5.3.15. A Deep Dive into Great Lambda Use Cases

  • Various applications for AWS Lambda, including:
    • Generating thumbnails or processing images/videos.
    • Creating backups and synchronizing data across accounts.
    • Scraping web pages and updating data in real time.

5.3.16. Tips and Tricks for the Real World

  • Recommended best practices for using AWS Lambda effectively.
    • Keep functions stateless and idempotent.
    • Use CloudWatch Alarms for monitoring metrics.
    • Choose the right database solutions considering Lambda’s temporary resources.
    • Use AWS Step Functions for orchestrating workflows.
    • Use layers for shared dependencies.
    • Keep code environment-independent via environment variables.
    • Build resilient, event-driven architectures.
    • Write structured logs for easier processing.
5.3.16.1. The Best Practices to Reduce Cold Start Times
  • Strategies for reducing cold starts, including minimizing dependencies, using warm-up requests, bootstrapping efficiently, optimizing memory size, and monitoring performance using CloudWatch.

5.3.17. How to Determine If Lambda and the Serverless Approach Are the Right Fit

  • Questions to assess the suitability of serverless architectures such as whether the service needs a central state, how frequently requests are received, and service scaling needs.

5.3.18. Final Words

  • Lambda offers an agile way to build applications without underlying infrastructure concerns and is ideal for learning AWS.