The serverless advantage

Posted January 23, 2021 by David Goemans ‐ 8 min read

Passing savings onto customers with AWS Serverless

Introduction

When building a new application, one key consideration is costs. When starting the Spot HR codebase, our main goal was to keep costs down as low as possible, so that our pricing model could be extremely competitive. Of course this should not be done at the cost of performance but rather built around your customers' usage patterns. At Spot HR, we made the choice to build on the AWS stack. Namely, Lambdas, DynamoDB, and S3. This post is going to cover a few of our decisions and give some tips on cutting costs. Note that this will be AWS oriented, but I believe that Google and Azure have similar offerings. AWS

Why this stack?

The main thing of importance here are the lambdas. Running serverless saves costs on several fronts. Because there’s less infrastructure to manage, there’s less development costs and less people with specific expertise required. While it’s important to understand what’s going on under the hood when building a lambda, it saves a lot of headspace to not have to actively think about while developing. It also only costs money when your code is invoked, and depending on your application, that can vary wildly, but is generally less than having a machine running 24 hours a day.

Most apps don’t need to run 24/7. In many large organizations, a common cost saving measure is to shut down their cloud servers (often EC2 instances) after business hours. At companies I’ve worked for in the past, this sort of technique has saved thousands of dollars a month for the organization. Lambdas take that a step further, by only executing your code on demand. Of course a major trade-off is the limitations on how large requests/responses are, but a well architected application can be built to avoid those trade offs. Lambdas generally stay “warm” for a few minutes after an invocation, which allows for faster subsequent invocations. Sure, if you have enough traffic, you could end up in a situation where your lambdas are awake 24/7, and cost as much as an EC2 instance. But that’s a luxury position which many apps would love to achieve. Why pay for it when you aren’t there yet? And why pass that cost on to your customers?

DynamoDB On Demand

For data storage, Spot HR uses DynamoDB. This is Amazon’s cloud no-sql (which now has sql) database. DynamoDB is generally great for processing a lot of records fast, which makes it good for heavy load applications. Surprisingly, their lowest price (and performance) tiers don’t cost much to run. The problem is that they do heavily throttle reads/writes when using provisioned capacity billing at lower cost levels. While still scaling up your business, you might not expect much traffic, so it makes sense to switch from provisioned capacity billing to on-demand (also known as pay-per-request) billing.

Sample pulumi code
const orgTable = new aws.dynamodb.Table('my-table', {
  hashKey: 'id',
  billingMode: 'PAY_PER_REQUEST',
  pointInTimeRecovery: {
    enabled: true
  },
  attributes: [
    {name: 'id', type: 'S'},
  ]
});

This will no longer throttle usage, but bill you only for what requests come through. What this means, is that you don’t pay anything if there are no reads/writes. You still pay for storage and backup, but these things have a generous free tier limit which you are unlikely to hit before you’re generating some revenue. Of course once you scale up, you definitely want to turn that throttling back on, or at least build in some queues to make sure that you don’t have large pricing spikes.

Large DynamoDB requests can be very expensive. Scanning millions of entries starts to cost significant money, so make sure you query specific things, limit your query sizes and paginate responses. Be smart about it, return the needed stuff first, and do as much filtering as you can. Use indices to make your queries more efficient, and learn a bit about the underlying structure and design practices for DynamoDB so that you can structure your tables accordingly.

S3 and Lambdas

One of the biggest limitations of lambdas is the maximum size of requests and responses. They are very strict with their limits, and a lot of the process around content types makes it hard for a developer to move binary/non-text data around.

Lambda response size limits
lambda costs

Even if you are storing your user files and images in S3 storage, getting them to S3 and pulling them out becomes a challenge. So how can you keep using lambdas and also move user files/images around? Luckily S3 provides mechanisms to generate direct URLs for accessing files and images that don’t require going through your lambda. To use this, you’d want to store the key of the file you’re looking for, and then when the user requests the URL to display, your lambda can convert the key to a pre-signed S3 URL.

AWS Node.js sample
import { S3 } from 'aws-sdk';

const s3Client = new S3({ signatureVersion: 'v4' });

const url = await s3Client.getSignedUrlPromise('getObject', {
    Key: 'object-key',
    Bucket: 'my-bucket',
  }).promise();

Note that you can configure several properties, including an expiry time, for this url. While this isn’t a post diving into security, it is highly recommended that your URL expires as quickly as possible and the object is only accessible to the people who should see it. In cases such as logos and images on several pages, you might even want to expire it extremely quickly and cache it on your client application. S3 charges you per get/put request to a bucket, so the less requests, the less it costs!

What to avoid?

As mentioned, the major advantage to lambdas is that they only run your code when invoked. So logically, it’s important to avoid anything that runs your code all the time. This includes EC2 instances and ECS/EKS services/tasks/clusters/etc. While it is incredibly tempting to dockerize your application, and that definitely comes in handy in on-prem situations, I would avoid doing it in the cloud for new applications. It’s extremely useful to be able to ship your machine, but you really shouldn’t have to (outside of development environments).

docker

Scheduled tasks

Although we don’t run any at Spot HR, an argument often presented for using EC2/ECS instances over lambdas is that you can run scheduled tasks. Maybe you need a daily runner to scan something, or an hourly poll to an external API.

The good news is that you don’t need to use EC2/ECS for this. Amazon have a service called EventBridge which allows you to schedule Lambda invocations, events to SQS queues, SNS topics, and many other services, in a cron style scheduler. You can even use the SDK to create scheduled jobs programatically, so if you need to create events per customer, that’s totally possible. Using expensive EC2 instances for this is no longer a valid excuse.

Other costs

We use many services in our stack. Outside of AWS, many fantastic services like Pulumi and github have a free tier if you’re willing to sacrifice some convenience.

Within AWS, we also use SES for emails, API Gateway, SQS for queues and S3/Cloudfront for hosting the frontend. Until your business is well established, the costs of running these services is negligible, and should not even reflect on your billing.

We currently have no CI/CD costs, as we’re using github actions for this and their free tier is extremely generous.

Our biggest cost during the MVP development: the monthly Route53 hosted zone cost ($0.50 before tax). I’m not kidding, without active customers, our system costs a total of $0.50 per month to run.

General tips

Use the Cost Explorer to analyze your spend on a regular basis. This should give you a good overview of what services are costing you money.

Tag your resources. This allows you to track down what costs what in your usage. But make sure to enable your Cost Allocation Tags in the Billing Console.

Setup billing alarms to notify you when you approach certain thresholds. For example, during MVP development we setup a $5 and a $10 monthly budget alarm. Neither of which were ever triggered.

Summary

At Spot HR, the goal is to make HR software affordable for small businesses. We firmly believe that paying a per employee cost doesn’t work for startups. We also know that HR software isn’t accessed every day by employees. It’s a vital part of your organization, but it’s not the most heavily used. Because of this, we’ve built Spot HR using some of the above principals to cut costs, which we pass on to our customers. The key take aways here are:

  • Consider building your software around your customers usage patterns
  • Be willing to sacrifice some convenience for large cost cuts (within limits)
  • Use lambdas over EC2/ECS instances
  • Paginate your db queries
  • Use S3 presigned Urls for images/files
  • Use SQS Queues to slow things down if needed
  • Try not to use excessive amounts of hosted zones
  • Use a CI/CD with a generous free tier (like github actions)
  • Analyze your costs often and set alarms