AWS RDS, Lambda and Cost Control: A Backend in One Picture
Cloud ComputingLesson 4 of 46 min read
What you'll learn
- Place RDS and Lambda in one backend architecture
- Explain what RDS manages for you and what it does not
- Write and test a Lambda handler locally before deploying
- Set a budget alert and keep a teardown routine to control cost
On this page
A learner spins up a database to follow a tutorial, closes the laptop and forgets about it. Nothing is wrong with the account; the database is simply doing what it was told, around the clock. The two services in this lesson sit at opposite ends of that behaviour: a managed relational database that runs and bills all day, and a function service that runs, and bills, only when something calls it. Seeing them side by side in a ferry-booking site explains both how to architect with them and why cost habits matter.
One picture
Browser or mobile app
| HTTPS
v
[ API Gateway / function URL ]
| event
v
[ Lambda: ferry-hello ] runs only per request
| SQL over a private network
v
[ RDS database ] runs all day, in private subnets
|
automated backups + optional standby in a second zoneA request arrives, the function runs, reads or writes the database and returns an answer. Between requests the function costs nothing, while the database keeps running. Most surprises in a small AWS bill come from the always-on parts.
RDS: a managed database, not a magic one
Amazon RDS runs a standard database engine (PostgreSQL, MySQL, MariaDB, SQL Server, Oracle and others) on an instance that AWS manages for you. AWS handles installing the engine, patching on a schedule, automated backups and, if you enable Multi-AZ, a standby copy in another availability zone that takes over if the primary fails. You still own the schema, the queries, the indexes (see Database Indexing) and the choice of instance size.
# Placeholders throughout. Creating this costs money while it runs.
aws rds create-db-instance \
--db-instance-identifier ferry-db \
--db-instance-class db.t3.micro \
--engine postgres \
--allocated-storage 20 \
--master-username ferryadmin \
--manage-master-user-password \
--db-subnet-group-name ferry-private-subnets \
--vpc-security-group-ids sg-0123456789abcdef0 \
--no-publicly-accessible \
--backup-retention-period 7Three flags carry real security meaning. --no-publicly-accessible keeps the database out of reach of the internet so only your VPC can connect. --manage-master-user-password has RDS generate the admin password and store it in AWS Secrets Manager, so it is not typed into a command or script. The security group should allow the database port only from the web or function security group, as in the previous lesson.
Lambda: code that runs per request
A Lambda function is a piece of code with a handler that AWS runs in response to an event: an HTTP request, a file landing in S3, a schedule. You do not manage servers; you are billed by the number of invocations and the memory-time they use. A function takes an event and a context and returns a result. Below is a small handler. Because it is plain Python, you can test it on your own machine by calling it with a fake event, which is exactly how you should develop before deploying.
import json
def handler(event, context):
params = event.get("queryStringParameters") or {}
name = params.get("name", "traveller")
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"message": f"Welcome aboard, {name}"}),
}
# --- try_local.py ---
from app import handler
print(handler({"queryStringParameters": {"name": "Mira"}}, None))
print(handler({}, None))Output
{'statusCode': 200, 'headers': {'Content-Type': 'application/json'}, 'body': '{"message": "Welcome aboard, Mira"}'}
{'statusCode': 200, 'headers': {'Content-Type': 'application/json'}, 'body': '{"message": "Welcome aboard, traveller"}'}That output is real, from running the handler locally with Python. Packaging it and calling it through AWS uses these commands. The role ARN is a placeholder for the role you would create using the previous lesson's approach; they were not run here. The --cli-binary-format flag is needed by version 2 of the CLI so it accepts raw JSON as the payload.
zip function.zip app.py
aws lambda create-function \
--function-name ferry-hello \
--runtime python3.12 \
--handler app.handler \
--role arn:aws:iam::123456789012:role/ferry-lambda-role \
--zip-file fileb://function.zip
aws lambda invoke \
--function-name ferry-hello \
--cli-binary-format raw-in-base64-out \
--payload '{"queryStringParameters": {"name": "Mira"}}' \
response.jsonLambda has limits that shape design: a function can run for at most 15 minutes, and its default timeout is only 3 seconds, so long jobs belong elsewhere. The first call after a quiet period can be slower, a cold start, as AWS prepares a new execution environment. Many concurrent invocations each open their own database connection, which can overwhelm a small RDS instance, so production setups usually put RDS Proxy between them.
| Question | Lambda fits when | An always-on server (EC2) fits when |
|---|---|---|
| Traffic | Spiky or low, with long idle gaps | Steady and predictable |
| Run time | Short tasks, minutes at most | Long-running or stateful processes |
| Ops effort | You want no servers to patch | You need full control of the machine |
| Cost shape | Pay per request and compute time | Pay for the instance while it runs |
Cost control habits
AWS bills for what exists and what runs, and a learning account can quietly accumulate both. These habits are cheap to adopt on day one.
- Set a budget alert first. A budget sends a notification when actual or forecast spend crosses a threshold. It alerts; it does not stop anything.
- Tag everything with a
Projectkey, then filter Cost Explorer by that tag to see which experiment is expensive. - Keep a teardown list for each project and run it when you finish, not later.
- Check the always-on items weekly: running instances, RDS instances, NAT gateways, load balancers, public IPv4 addresses, unattached volumes and snapshots.
- Use the region you are actually in. Resources forgotten in another region are the usual culprit; look at the bill by region.
{
"BudgetName": "learning-account-monthly",
"BudgetLimit": { "Amount": "10", "Unit": "USD" },
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{ "SubscriptionType": "EMAIL", "Address": "you@example.com" }
]
}
]# 123456789012 is a placeholder account ID.
aws budgets create-budget \
--account-id 123456789012 \
--budget file://budget.json \
--notifications-with-subscribers file://notify.jsonTeardown commands should be read twice before you run them, since deleting is permanent. This list is illustrative: it terminates an instance, deletes the database while keeping a final snapshot, removes the function, and empties and deletes a bucket with --force.
aws ec2 terminate-instances --instance-ids i-0123456789abcdef0
aws rds delete-db-instance --db-instance-identifier ferry-db --final-db-snapshot-identifier ferry-db-final
aws lambda delete-function --function-name ferry-hello
aws s3 rb s3://example-ferry-assets-4821 --forceCommon mistakes
- Assuming a budget will cap spending. Symptom: the alert email arrives after the money is spent. Budgets warn; you act.
- A function that dies at the default timeout. Symptom:
Task timed out after 3.00 secondsin the logs. Raise the timeout, or find out why the work is slow, often a database connection. - A wrong handler string. Symptom:
Runtime.ImportModuleError: Unable to import module 'app', or a handler-not-found error. The handler isfile_name.function_name, and the file must be at the root of the zip. - A VPC-attached function that cannot reach the internet. Symptom: calls to external APIs hang until the timeout. A function in a private subnet needs a NAT gateway or VPC endpoints for outbound access.
- A publicly accessible database with a wide-open port. Symptom: unfamiliar connection attempts in logs. Keep RDS private and allow the port only from known security groups.
How this shows up in projects and interviews
Interviewers ask when you would choose Lambda over EC2, why a database is not a Lambda concern, and how you would stop a surprise bill. Answers that name cold starts, connection limits and the always-on cost of RDS show you have thought past the happy path. On a real team, cost review is part of design: someone will ask what this feature adds to the monthly bill. If you want a wider tour of the cloud ideas behind these services, see Cloud Computing Fundamentals, and if you are applying for cloud roles, check which AWS skills appear in current job listings.
Try it yourself
Write a Lambda handler that accepts an event with a body containing a JSON list of ticket prices and returns the total. Test it locally with three fake events, including an empty list and a missing body. Then write a one-line teardown checklist for a project that used a database, a bucket and an instance.
Show a hint
The event body arrives as a string, so use json.loads. Return a dict with statusCode and a JSON string body, as in the lesson's handler.
Key takeaways
- RDS runs and bills all day; Lambda bills only while it runs, so the database is usually the bigger line item.
- Keep RDS private, use a generated admin password and allow the port only from known security groups.
- Lambda handlers are plain functions you can test locally; mind the timeout, cold starts and connection counts.
- A budget alert warns you but does not stop spending; tags and a teardown list do the real work.
Test yourself
A trick from this lesson's ride: Lambda bills when called; RDS bills while it exists. Four more tricks like it wait along the route, each followed by a question.
Frequently asked questions
What is the difference between AWS Lambda and EC2?
EC2 gives you a virtual server that runs continuously and that you configure and patch. Lambda runs a function only when an event triggers it, scales automatically and bills per invocation and compute time. Choose Lambda for spiky or short tasks and EC2 for steady, long-running or highly customised workloads.
What does Amazon RDS do for me?
RDS runs a relational database engine such as PostgreSQL or MySQL on managed infrastructure, handling installation, patching windows, automated backups and optional Multi-AZ failover. You are still responsible for schema design, queries, indexing and capacity choices. It runs and bills continuously while the instance exists.
How do I avoid surprise AWS bills?
Create a budget alert on day one, tag resources by project, review always-on resources such as databases, NAT gateways and load balancers regularly, and tear down experiments when finished. Remember that budgets only notify you, so act on them. Check current free-tier terms rather than assuming.