A Practical Introduction to AWS Lambda

14 Mar, 2020
  • Share
Post image

In this tutorial I'm writing about what AWS Lambda is, how it works, its pricing model, and how to use AWS API Gateway with Lambda to handle HTTP requests. In the end I'll show how to create a Lambda function to process HTTP requests from from an API Gateway route.

What is the AWS Lambda?

AWS Lambda is a service which allows you to run your code in the cloud in response to some event without provisioning a server. You write some code and tell AWS when to execute it. For example, you can run your code in response to an HTTP request (from the API Gateway service), or process a file when it is uploaded to S3, or run some code when you create a row in DynamoDB, and other use cases. I'm really happy that AWS Lambda provides automatic scaling, it launches as many copies of your code as needed to satisfy the amount of incoming requests (events). This means you can use lambda for web applications without worrying what to do when the traffic grows. Well, you should still worry about the billing, because you pay money according to your usage. Basically, you can start from $0 if there are no events to process (e.g., no visitors), but be cautious, because when your traffic grows your bills grow as well.

Useful Resources

How It Works?

First, you create a function for a supported runtime, like Node.js, Python, Ruby, Java, Go, or .NET. Then, you post this function to the AWS Lambda service which invokes it in response to some event (trigger). When your function is invoked, AWS Lambda spins up an execution context for your function and executes it in this context.

The execution context is a temporary runtime environment initialized with the dependencies of your code (like database connections). AWS Lambda maintains the execution context for some time to be able to reuse it for subsequent invocations of the function.

Initializing the context takes time and resources, so AWS Lambda tries to reuse it for your function as much as it can. However, you shouldn't assume that AWS Lambda automatically reuses the context for subsequent function invocations. Various conditions may cause AWS Lambda to initialize a new context.

Let's have a look at a Lambda function example (for the Node.js runtime):

const db = createConnection();

export const handler = async (event, context) => {
  const posts = await db.getPosts();
  return posts;
};

When invoked, the async handler function receives two arguments from the runtime:

  1. event is the object which contains some data from the invoker.
  2. context is the object which contains the data about the invocation, function, and execution environment.

This function returns a promise which resolves to the value of the posts variable.

I initialized the database connection outside of the handler function to be able to reuse the connection between the function calls, because establishing a connection on each request is costly.

Also, the discussion about using databases in Lambda (or serverless overall) would take another blog post. You don't want to establish a traditional database connection (e.g., MySQL connection) on every function call, because this will exhaust your database server. So, the simplest and quite naive approach is to share a connection between function invocations like in the above example to reduce the load on the database. A more sophisticated approach would be to use Amazon RDS Proxy to handle connections to relational databases. Or, you can look at using databases which work nicely with serverless out of the box, like DynamoDB, FaunaDB, or Amazon Aurora.

Useful Resources

Pricing (as of March, 2020)

You pay only for what you use. So, it's great for running a side project (e.g., a web or a mobile app). You pay nothing until you get to some threashold of code invocations.

You pay for the number of requests of your code and the time it takes for your code to execute, the duration.

It costs approximately $0.20 per 1M requests and $0.0000166667 for every GB-second. The GB-second unit measures how long your code is running on the machine taking into account the machine's memory (RAM). The more memory you choose for running your code the more you pay for a GB-second.

X GB-seconds = machine's memory in gigabytes (e.g., 1GB) * X seconds

Now that we know the prices, let's calculate how much would we pay if our code got 1M requests and each request lasted for half a second, and this code ran on a 512MB machine:

  1. We should convert 512MBs to GBs: 512MB / 1024MB = 0.5GB.
  2. Let's calculate how long all the requests lasted: 1,000,000 requests * 0.5 seconds = 500,000 seconds.
  3. Let's calculate the number of GB-seconds: 500,000 seconds * 0.5GB = 250,000 GB-seconds.
  4. Finally, let's calculate the money:
  • For requests: $0.20 / 1M requests * 1M requests = $0.20
  • For GB-seconds: $0.0000166667 * 250,000 Gb-seconds = approx. $4.17

So, the total cost would be: $0.20 + $4.17 = $4.37

And, good news, AWS Lambda has a free usage tier. The first 1M requests per month and the first 400,000 GB-seconds per month are free.

Useful Resources

AWS API Gateway

Earlier I've mentioned that a Lambda function is invoked in response to an event. One of such events may be an HTTP request from AWS API Gateway.

By combining Lambda functions with API Gateway we can build a scalable API. API Gateway is a service which allows you to receive HTTP and WebSocket requests and route them for processing to some backend endpoint, like a Lambda function, or another AWS service like EC2, or some other existing HTTP endpoint.

You can choose one of the following options to build your API:

  • REST API. This option offers various features for building RESTful APIs.
  • HTTP API. This option is cheaper than the REST API, but it offers only the API proxy functionality.
  • WebSocket API. This option offers persistent connections over the WebSocket protocol. You can use it to build chat apps and real-time dashboards.
  • REST API Private. It's a REST API which is accessible only from a VPC.

API Gateway is a paid service. The pricing differs based on the service you choose: HTTP API, REST API, or WebSocket API. Please check the pricing page for more details.

Useful resources

Creating the Lambda Function

I assume you already have an AWS account.

Go to the AWS management console, choose a region and search for Lambda.

Open the Lambda dashboard and click Create function and fill in the function name (e.g., "hello-world") and choose the runtime (I chose Node.js 12.x):

lambda-create-basic-information

Click Create function.

Once AWS finishes creating your function it should redirect you to your function's dashboard. Here you can edit the code and various settings, like the function's role, the execution environment's memory, and other settings. You can edit your function's permissions to allow it to access other AWS services.

This is how the initial code of my function looks like:

exports.handler = async (event) => {
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

I'll leave it like this.

Creating the API Route

Search for the API Gateway service in the AWS Console.

Go to the service dashboard and click Create API. Select HTTP API and click Build. Fill in the API name (e.g., "hello-world") and click Next.

You'll get to the Configure routes step. We'll add a route later. Click Next. Then, you'll get to the Define stages step. Keep the defaults and click Next. Finally, you'll get to the Review and create step. Review your selections and click Create.

Once AWS creates your API, it should redirect you to your brand new API's dashboard.

Now, let's create a route. In the sidebar, click Routes. On the Routes page, click Create. Choose GET for the method and enter "/hello-world" for the path:

api-route-and-method

Click Create.

Now let's attach the lambda function we created earlier to this route. Select the GET method under the "/hello-world" route and click Attach integration:

api-routes-hello-world-get

Then, click Create and attach an integration. In the Integration with section, choose Lambda function. In the integration details section select the Lambda function you created earlier ("hello-world"). And click Create.

So far you've created the route and attached your "hello-world" lambda to this route. Now to be able to call this route you should deploy the API. Your changes to the API won't get online until you deploy them.

Before you deploy your API for the first time, you should create the stage you'd like to deploy to. A stage is a named reference to a particular deployment of your API. You can create multiple stages, like dev, test, prod, beta, etc. For example, you can demo a new version of your API to a client by deploying it to a stage called beta.

So, let's create the stage for production naming it prod. In the sidebar, click Stages, click Create. Enter prod for the name and click Create.

Finally, in the upper right corner click Deploy. Select prod for the stage and describe the changes somehow (e.g, "Initial deployment"). And click Deploy to stage.

In the Stage details block, you'll find the Invoke URL. This is the base URL of the API you deployed:

api-stage-details

Open this URL in the browser and add the path of the route you've created earlier ("/hello-world"):

api-deployed-hello-world-response

The endpoint should display the reponse of your "hello-world" lambda function.

Finally, do not forget to remove both the example function and the API you've created by following this post to keep your account neat.

So, that's it for now. We created a simple API endpoint which is handled by a Lambda function.

In the next blog post I'll explain how to create this setup using the Serverless framework.

Useful Resources

Pssst...

You know React already? Wanna learn how to use TypeScript with it?

Get this detailed online course and learn TypeScript and how to use it in React applications.

Course thumb image