series · 9 articles · 20k words
Serverless on AWS
Nine articles on AWS Lambda and the services around it: your first function, DynamoDB and SQS, every configuration setting explained, cost tuning with measurements, and what breaks at a million users an hour.
Serverless removes the servers and leaves you with a different set of things to understand: what you are billed for, what happens when two thousand copies of your function start at once, and which piece of the system falls over first when they do.
That last question has the same answer almost every time, and it is not Lambda.
The database breaks before the compute does
Lambda scales to thousands of concurrent executions in seconds. Your database does not. A thousand concurrent Lambdas opening a connection each is a thousand connections, and a Postgres instance configured for two hundred will start refusing them long before Lambda hits any limit of its own.
This is the central lesson of the million users an hour article, and it is why the two data articles in this series use DynamoDB — an HTTP API with no connection pool to exhaust — rather than a relational database. When you do need SQL behind Lambda, the fix is a proxy that pools connections on your behalf, or the RDS Data API, which is HTTPS for the same reason.
The general shape: anything with a connection limit, a rate limit, or a lock is your real ceiling. Lambda’s job in an incident is usually to be the thing that was working.
More memory is usually cheaper
Lambda has one performance dial. Memory. CPU is allocated in proportion to it, so “give it more memory” is really “give it more CPU”, and because you are billed for milliseconds × memory, a function that runs twice as fast at double the memory costs exactly the same — and any speedup better than 2× makes it cheaper.
The cost article measures this instead of asserting it, and it names the specific number worth remembering: 1,769 MB is where a function gets one full vCPU. Below that you have a fraction of a core, which is why a single-threaded function often improves steadily up to that line and then stops. Past it, you are paying for cores your code may not use.
Two more from the same article: arm64 (Graviton) is about 20% cheaper and it is worth knowing exactly what that discount covers, and the sensible way to pick a memory setting is Power Tuning — run the function at several sizes and read the graph — rather than guessing and rounding up.
The configuration article is the companion: every setting on the page, what it does, and when to change it. Timeout set to reality rather than the maximum. Reserved and provisioned concurrency, which sound similar and do opposite things. Environment variables for config and never for secrets. VPC configuration only when you genuinely need it, because it is not free.
Queues, and the trap in them
The moment traffic is spiky, the answer is to stop doing the work synchronously. Put a queue between the thing that arrives and the thing that processes it, and a spike becomes a backlog instead of a failure — that is the entire argument of the SQS architecture article, worked through on a video platform where uploads arrive in bursts and transcoding takes minutes.
The trap is the visibility timeout. Receiving a message does not remove it from the queue; it hides it for a while. If you do not delete it before the timeout expires, it comes back and is processed again. The SQS article walks straight into this at step 2 — send a message, receive it, watch it reappear — and then fixes it, which is a much better way to learn it than reading the parameter description.
Its companion trap is retries you did not ask for. An asynchronous Lambda invocation retries twice on failure, quietly. Without a dead letter queue, a poison message is retried, dropped, and gone. Configure the DLQ before you need it, not after.
Cold starts matter less than they used to, and still matter
A cold start is the time to spin up a new execution environment before your code runs. It is a real cost when traffic is spiky and effectively invisible when it is steady, because warm environments get reused.
Language choice moves the number more than anything you configure — a Go binary starts in a fraction of the time a heavy JVM or a large Python dependency tree does, which is one reason most of the code in this series is Go. Provisioned concurrency removes cold starts and bills you for the privilege whether requests arrive or not. SnapStart has moved on since it launched and the cost article covers where it stands now.
The right order is: measure whether cold starts are actually hurting you, trim the deployment package and the imports, and only then pay for provisioned concurrency.
How to read this
Never written a Lambda: start with Build and Deploy a Go Lambda Function or, if you are a Python shop, the Boto3 pipeline — that one is also a cost story, about a bill that got large for an avoidable reason.
Already running Lambdas in production: read the configuration guide and cost tuning back to back. Most teams have money and latency sitting in those two pages.
Designing something new: the million users an hour article is the capacity arithmetic, and the two YouTube-system articles are the same design worked twice — once without a queue and once with — which makes the argument for event-driven architecture concrete instead of abstract.
What you need
An AWS account and the CLI configured. Go 1.21+ for most of the code, Python for the Boto3 article. Everything stays inside the free tier if you follow the cleanup steps, and every article has one.
All 9 articles
Your first function
Two ways in, depending on your language. Both build a real handler, break it, and fix it rather than pasting a finished one.
- Build and Deploy a Go Lambda Function A hands-on guide to building your first AWS Lambda function with Go: start with a basic handler, hit real errors, fix them, then add API Gateway step by step.
- Boto3 + AWS Lambda: A Production Serverless Pipeline Build a serverless data pipeline for high-volume daily events with Boto3 and Lambda: cold start tuning, error handling, DLQ setup, and cost lessons.
The two services you will reach for next
A Lambda on its own does almost nothing. It needs somewhere to put state and something to buffer work — in practice that means DynamoDB and SQS.
- Go + DynamoDB: Build a Simple CRUD App Build a Go CRUD app with DynamoDB from scratch: start with raw attribute maps, hit the verbosity wall, then upgrade to structs with Put, Get, Query, and Delete.
- Go + SQS: Build a Message Queue Processor Build a Go app that sends and processes SQS messages: start with one message, hit the visibility timeout trap, add batching, and build a proper polling loop.
Settings, and the bill
Every Lambda setting explained in plain English, then the measured version of the one that matters. Read both before you tune anything.
- AWS Lambda Configuration Explained: What to Care About (and Why) A plain-English guide to every AWS Lambda setting: memory, timeout, concurrency, architecture, IAM, VPC and environment variables, and when to change each.
- Tuning AWS Lambda for Cost: The Memory Myth, Measured Cut AWS Lambda cost: what you are actually billed for, why more memory is rarely cheaper, the exact 20% Graviton discount, and the 1,769 MB one-vCPU line.
At scale
What actually happens when the traffic arrives: the capacity arithmetic, the component that fails first, and a full event-driven system worked through end to end.
- Handling a Million Users an Hour with AWS Lambda (Think Like a System Designer) Design an AWS Lambda system for a million users an hour: capacity math, concurrency, cold starts, language choice, and the database bottleneck behind it.
- Building a YouTube-like System with AWS Lambda and S3 Learn how to build a serverless YouTube-like video platform with AWS Lambda and S3, implementing upload, processing, and delivery with an event-driven design.
- Why YouTube-Scale Systems Need SQS: Architecture Notes Why adding Amazon SQS to a YouTube-like platform boosts scalability: a queuing layer between S3 uploads and Lambda for resilience and fault tolerance.