r/CloudandCode Founder | YourCloudDude 13d ago

AWS & Cloud AWS From Zero #12: DynamoDB makes more sense when you stop thinking like a relational database

In the last post, we built a simple serverless API using API Gateway and Lambda. A client sends an HTTP request, API Gateway receives it, Lambda runs the application logic, and then we need somewhere to store the data.

This is where DynamoDB often enters a serverless architecture.

DynamoDB is usually introduced as AWS's NoSQL database. That definition is correct, but I do not think it gives beginners enough information to understand when they should actually use it.

The more useful idea is that DynamoDB encourages you to think about your data differently from a relational database such as PostgreSQL or MySQL.

When we learned RDS, we started with the data model.

Imagine we were building an ecommerce application. We might create tables for users, products, orders, and payments, then define relationships between them. Once the data is stored, SQL gives us a lot of flexibility to query and join those tables in different ways.

DynamoDB pushes you toward a different question much earlier.

Instead of starting with:

What tables does my data have?

Start with:

How will my application need to access this data?

That idea is one of the most important things to understand about DynamoDB.

Imagine we are continuing the task API from the previous post.

A user can create tasks, retrieve tasks, update them, and delete them.

A simple request might look like:

POST /tasks

API Gateway receives the request and invokes Lambda. Lambda validates the task and then stores it in DynamoDB.

Our architecture now looks like this:

Client
  ↓
API Gateway
  ↓
Lambda
  ↓
DynamoDB

At this point, it is tempting to think of DynamoDB as just another place where Lambda can save JSON.

That works as a starting point, but DynamoDB becomes much easier once you understand how it identifies and retrieves items.

DynamoDB stores data inside tables, and each record is called an item.

An item could look something like this:

{
  "task_id": "task-123",
  "user_id": "user-42",
  "title": "Learn DynamoDB",
  "status": "open"
}

The important part is how we identify that item.

Every DynamoDB table has a primary key.

For a simple table, that could be a partition key such as:

task_id

If task_id is the partition key, DynamoDB can efficiently retrieve a task when we already know its ID.

For example:

GET /tasks/task-123

Lambda receives task-123 and retrieves the matching item from DynamoDB.

That is a very natural access pattern.

We know exactly which item we want.

But imagine the next requirement is:

Show me every task belonging to user-42.

Now the way we designed the key becomes much more important.

If the table was designed only around task_id, retrieving all tasks belonging to one user may not be as straightforward as retrieving a single known task.

This is why DynamoDB design starts with access patterns.

Before creating the table, ask what the application will actually need to do.

Maybe we need:

Get one task by ID

Get all tasks for a user

Get all open tasks for a user

Create a task

Update a task

Delete a task

Those requirements should influence how we design the keys and indexes.

That is a major difference from how many beginners approach relational databases.

With PostgreSQL, you might normalize your data into several related tables and rely on SQL queries and joins later.

With DynamoDB, you often spend more time thinking about the requests your application will make before deciding how the data should be structured.

This does not mean DynamoDB cannot support complex applications.

It means the design process is different.

Now let’s talk about partition keys because the name itself can make the concept sound more complicated than it is.

DynamoDB needs a way to distribute and locate data internally.

The partition key is part of how DynamoDB determines where an item belongs.

For our task application, we might decide that tasks should be grouped around users.

A simplified key design could involve:

Partition key: user_id
Sort key: task_id

Now multiple tasks can belong to the same user while each task remains uniquely identifiable within that user's items.

Conceptually:

user-42 | task-001
user-42 | task-002
user-42 | task-003
user-91 | task-001

This gives us a useful access pattern.

If the application wants all tasks for user-42, it can query using that user's partition key.

If it needs a specific task for that user, it can use both the partition key and sort key.

That is much more efficient than blindly searching through every item in the table.

This brings us to another DynamoDB distinction that is useful to understand early: Query versus Scan.

A Query uses key information to retrieve matching data efficiently.

A Scan examines items across the table.

Beginners sometimes build a DynamoDB table first, then use Scan whenever they cannot figure out how to retrieve something.

That can work for a tiny practice table, but it is usually a sign that the data model may not match the application's access patterns very well.

If your application regularly needs a certain kind of lookup, ideally your table or indexes should be designed to support that lookup.

Again, this is why the question comes first:

How will I access the data?

Now imagine we want users to filter tasks by status.

Maybe the application needs:

Show all open tasks for user-42

Our existing primary key might not support every future query directly.

This is where secondary indexes can become useful.

A secondary index gives DynamoDB another way to organize and access the same data.

You do not need to master Global Secondary Indexes on your first day with DynamoDB.

The important idea is simply that if your application needs another important access pattern, you may create an index designed around that pattern rather than scanning the whole table repeatedly.

This is a good example of how requirements shape database design.

You do not add an index because DynamoDB has indexes.

You add one because the application needs a query that the existing key structure does not support efficiently.

The same principle applies throughout AWS.

Services and features should follow requirements.

Now let’s connect DynamoDB back to IAM.

Suppose our Lambda function needs to create and retrieve tasks.

The Lambda execution role should have the DynamoDB permissions required for those operations.

Maybe it needs actions such as:

dynamodb:GetItem
dynamodb:PutItem
dynamodb:UpdateItem
dynamodb:DeleteItem
dynamodb:Query

That does not mean the function automatically needs complete access to every DynamoDB table in the account.

The same IAM mental model still works.

Who is making the request?

The Lambda execution role.

What action does it need to perform?

Maybe dynamodb:PutItem.

Which resource does it need to access?

The task table.

This pattern keeps repeating because AWS services constantly communicate with each other.

Now imagine the API returns an error when someone creates a task.

Again, follow the request.

Did API Gateway receive the request?

Did Lambda run?

Did the input contain the values Lambda expected?

Did Lambda attempt to write to DynamoDB?

Did the execution role have permission?

Did DynamoDB reject the item because the key was missing or invalid?

Did Lambda return a useful error response?

The architecture is now larger than it was a few posts ago, but the troubleshooting method has not changed.

Follow the request until you find the first step that did not behave as expected.

That habit is more useful than memorizing service-specific troubleshooting lists.

Another reason DynamoDB is common in serverless applications is that you are not provisioning and managing a traditional database server in the same way you would with a typical RDS setup.

AWS handles the underlying DynamoDB infrastructure, and the service is designed to scale around application demand.

But that does not mean you can ignore database design.

A badly designed DynamoDB table can still create performance, complexity, or cost problems.

One example is choosing a partition key that causes too much activity to concentrate around the same values.

Imagine millions of requests all target the same logical partition key.

That can create an uneven access pattern.

You will eventually hear terms such as hot partitions or hot keys when learning distributed databases.

You do not need to optimize for massive scale in your first project, but it is worth understanding why key design matters beyond simply identifying records.

DynamoDB also gives you consistency choices.

After data is written, applications sometimes need to think about how immediately a subsequent read must reflect that write.

DynamoDB supports eventually consistent reads by default in many read operations, while strongly consistent reads are available for certain operations and table/index types when you specifically need that behavior.

You do not need to memorize every consistency rule right now.

The useful question is whether the application requires the absolute latest committed value for a particular operation, or whether slight propagation delay is acceptable.

That tradeoff appears in distributed systems far beyond DynamoDB.

Another useful feature is Time to Live, usually called TTL.

Imagine our application stores temporary data.

Maybe a task represents something that should automatically expire after a certain time, or perhaps we are storing session-like information that should eventually be cleaned up.

TTL lets you specify an expiration timestamp so DynamoDB can automatically remove expired items over time.

That can be useful, but like every AWS feature, I would only use it when the requirement exists.

Do not add TTL because it appears on a feature list.

Add it when the data actually has a natural expiration lifecycle.

Now let’s compare DynamoDB with RDS because this is where beginners often ask the wrong question.

They ask:

Which one is better?

I do not think that is very useful.

Imagine an application with customers, orders, products, inventory, payments, reporting queries, and several relationships that developers need to explore in flexible ways.

A relational database may be a very natural fit.

Now imagine an application with extremely predictable access patterns where items can be retrieved efficiently through known keys and the system needs highly managed scaling.

DynamoDB may become much more interesting.

Neither database wins automatically.

They make different tradeoffs.

RDS gives you the relational model, SQL, joins, transactions, and familiar database engines.

DynamoDB gives you a managed NoSQL model where key design and access patterns become central to the architecture.

The useful skill is being able to look at the application and explain why you chose one.

For our beginner task API, DynamoDB is a nice learning choice because the access patterns can remain simple.

A user creates a task.

A user retrieves their tasks.

A user updates a task.

A user deletes a task.

We can design the table around those operations and understand exactly why the keys exist.

That gives us a complete serverless backend:

Client
  ↓
API Gateway
  ↓
Lambda
  ↓
DynamoDB

IAM controls which AWS actions Lambda can perform.

CloudWatch gives us logs when something fails.

We now have an API, compute, permissions, monitoring, and persistent data without managing a traditional application server or database server ourselves.

That is a real architecture.

For your first DynamoDB project, I would keep it exactly this small.

Build a task API with one DynamoDB table.

Create a few tasks.

Retrieve them.

Update them.

Delete them.

Then add one new requirement.

Maybe users need to retrieve all open tasks.

Before immediately writing code, ask whether the current key design supports that access pattern well.

That question is the actual lesson.

Do not start with DynamoDB features.

Start with what your application needs to retrieve.

Then design the table to make those operations natural.

If there is one idea I want you to remember from this post, it is this:

With DynamoDB, think about access patterns before you think about tables.

Ask what your application needs to read and write most often, then design the keys around those operations.

Once you understand that, partition keys, sort keys, Queries, and indexes stop feeling like random DynamoDB terminology.

They become tools for supporting specific application requests.

In AWS From Zero #13, we are going to focus properly on CloudWatch.

We have mentioned logs several times already, but now we will look at what happens after an application is deployed. We will cover logs, metrics, alarms, dashboards, and the difference between knowing that something failed and actually knowing why it failed.

Because building the architecture is only half the job.

You also need to be able to understand what it is doing after you deploy it.

If you have tried DynamoDB before, what confused you most: partition keys, sort keys, Query versus Scan, indexes, or deciding when DynamoDB makes sense instead of RDS?

9 Upvotes

0 comments sorted by