All posts
§03 · Writing26.05.204 min read

Why I Abandoned Relational Databases for DynamoDB

Every database course I've taken started with normalization. Third normal form. No redundant data. Foreign keys everywhere. Join tables for many-to-many relationships.

For krealalejo.dev, I threw most of that out.

Not because normalization is wrong — it's the right model for plenty of problems. But it's not the right model for every problem, and recognizing the difference is part of what good engineering actually looks like.

Why Not RDS

The honest answer is cost. An RDS instance in AWS runs 24 hours a day whether you're querying it or not. The smallest option (db.t3.micro) costs around €15/month on its own. For a personal portfolio that gets a few dozen visitors a day, that's money paid entirely for idle compute.

DynamoDB on the On-Demand billing model costs nothing when it's not being used. No instance to manage, no patching, no storage fees below 25 GB. For a project at this scale, the bill is literally zero.

But cost alone isn't enough reason to use a different database. You should use the right tool. DynamoDB is the right tool here because the access patterns are simple and well-defined.

Thinking in Access Patterns

The shift from relational to DynamoDB requires a different starting point.

With SQL, you design entities first. Normalize, then figure out the queries. The database is flexible enough to handle queries you haven't thought of yet via joins and indexes.

With DynamoDB, you design access patterns first. What queries will this application actually make? Define those precisely, then model the table to serve them efficiently. The schema follows from the queries, not the other way around.

For krealalejo.dev, the access patterns are:

  • Get all published blog posts (sorted by date, for the blog listing)
  • Get a single blog post by slug (for the blog detail page)
  • Get all CV data (experience, education, skills)
  • Get all project metadata (for the portfolio)
  • Store a contact lead

These patterns are read-heavy, predictable, and don't require arbitrary joins. DynamoDB is well-suited for exactly this.

Single-Table Design

All of those entities — blog posts, CV entries, project metadata, leads — live in a single DynamoDB table. Not separate tables. One table.

The key structure uses a composite primary key: a Partition Key (PK) and Sort Key (SK). A blog post has PK = POST#the-anti-vercel-manifesto and SK = METADATA. A CV experience entry has PK = EXPERIENCE#<id> and SK = METADATA. The same SK = METADATA convention applies across all entity types — the SK distinguishes the main record from potential future projections on the same partition.

For queries that need to list items by type (all posts, all experience entries), there's a Global Secondary Index: GSI1PK = TYPE#POST lets you retrieve all blog posts in a single query without scanning the full table. GSI1PK = TYPE#EXPERIENCE does the same for CV entries.

The design is deliberately minimal. I modeled only the access patterns I actually have, not the ones I might need someday.

What I'd Change

The learning curve is real. Relational modeling is intuitive because the structure mirrors how humans think about entities and relationships. DynamoDB modeling requires thinking backwards from queries to structure, which takes time to internalize.

If I built this again, I'd invest more time upfront in access pattern analysis before writing a single line of code. I did that, but not thoroughly enough — I had to revise the key design once when I added the CV section.

The lesson: DynamoDB rewards planning and punishes late changes. Define your access patterns in writing before you touch the console.


The best database for your project is the one that matches your access patterns at your scale and budget — not the one you've always used.