← SkillForge / Articles / Data Modeling Fundamentals
SkillForge Article · AWS Data Engineering

Data Modeling & Database Fundamentals

From raw data to wisdom — relational theory, columnar internals, NoSQL paradigms, graph databases, and choosing the right model for the job.

📅 May 2026 📓 From handwritten study notes 📖 DDIA by Martin Kleppmann

1. Data Hierarchy

Wisdom
Knowledge
Information
Data

Data is raw facts. Information is data with context — organized through data models using SQL and query languages. Knowledge emerges when information connects and patterns appear. Wisdom is applying knowledge to make sound decisions.

Database = Storage + Query Engine + Metadata. The Query Engine's job is to translate what the user asks for into operations the underlying storage understands. This is true whether the storage is row-oriented, columnar, document-based, or a graph.
Amazon S3 alone stores approximately 350 million files — illustrating the sheer volume that modern systems must organize, index, and serve.

2. Relational Foundations & ACID

Edgar Codd (1970) introduced the relational data model: data organized into relations (tables) with rows and columns. Each column attribute must have a unique name and every value in a column must be the same type — no implicit conversions ("65" - 5 + 10 is not valid).

ACID Guarantees

ACID guarantees ensure reliable concurrent database operations:

PropertyGuaranteeWhat it prevents
AtomicityTransactions are all-or-nothing — no partial updatesHalf-written records, orphaned state
ConsistencyData must follow defined rules (keys, types, constraints)Invalid data entering the system
IsolationConcurrent operations behave as if executed one at a timeDirty reads, phantom reads, write conflicts
DurabilityOnce committed, data persists even after failureData loss on crash or power failure
✦ Isolation Levels: Databases implement isolation at different strengths. From weakest to strongest: Read Uncommitted → Read Committed → Repeatable Read → Serializable. Stronger isolation = safer but slower. PostgreSQL defaults to Read Committed. Most interviews test this.
Python tooling: psycopg2 is the Python library for PostgreSQL with full transaction support. Jupyter's %%sql cell magic supports simple queries but does not support transactions — use psycopg2 for granular control.

3. Normalization (1NF → 3NF)

Normalization is the process of organizing tables into "normal forms" to reduce redundancy and inconsistencies. Anomalies result from poor schema design — CRUD (Create, Read, Update, Delete) operations suffer when data is denormalized incorrectly.

Create Anomaly: The structure of the table forces you to supply information you don't have yet.
Update Anomaly: The same data stored in multiple rows means updating one copy but missing another.
Delete Anomaly: Deleting a record inadvertently removes other valuable data stored in the same row.

First Normal Form (1NF)

Each cell in a table contains a single value — no arrays, no comma-separated lists, no nested structures. When a schema fails 1NF: queries become harder, optimization features (indexes) are much harder to build and use, and constraints become weaker.

Second Normal Form (2NF)

Satisfies 1NF, plus: define a clear primary key AND ensure all columns depend on the entire key, not just part of it. Relevant when using composite primary keys (two or more columns forming the key). Natural primary key: a single column like Name that identifies the record. Composite primary key: multiple columns together (e.g., StudentID + CourseID).

Third Normal Form (3NF)

Satisfies 1NF and 2NF, plus: no non-key column should depend on another non-key column (no transitive dependencies).

Benefits of Normalization

  • No data duplication
  • Clear dependencies between tables
  • Fewer update errors and anomalies
  • Well-defined keys with indexes
  • Efficient OLTP writes

When to Denormalize

  • OLAP / analytical queries need fast reads
  • Star and Snowflake schemas for data warehouses
  • Read-heavy workloads where JOINs are expensive
  • Reporting and BI dashboards
  • Materialized views and pre-aggregated tables
✦ BCNF & 4NF: Beyond 3NF, Boyce-Codd Normal Form (BCNF) handles edge cases where a non-trivial functional dependency's determinant isn't a superkey. Fourth Normal Form (4NF) eliminates multi-valued dependencies. In practice, most production schemas target 3NF for OLTP and deliberately denormalize for OLAP.

4. OLTP vs OLAP

OLTP — Row-Oriented

Each row inserted independently in sequence. Fast individual inserts and reads without impacting the rest of the table.

  • Row-by-row storage on disk
  • Handles concurrent transactions effectively
  • Normalized schema (3NF)
  • PostgreSQL, MySQL, Aurora, SQL Server

OLAP — Column-Oriented

Data stored by column. Aggregating an entire column is fast because it reads only that column without touching others.

  • Columnar storage on disk
  • Fast aggregations (SUM, AVG, COUNT)
  • Drawback: adding a new row requires opening all columns
  • Redshift, BigQuery, Snowflake, DuckDB
Hot Data: Latest data requiring immediate access. Understanding data temperature (hot/warm/cold) drives storage tiering — hot in memory or SSD, warm on disk, cold in archive (S3 Glacier).
Benchmarking: TPC-C for transactional (OLTP) workloads, TPC-H for analytical (OLAP) queries. Standard datasets for comparing database performance.

5. Star & Snowflake Schemas

OLAP databases use denormalized schemas optimized for analytical queries. The two dominant patterns:

Star Schema

Central fact table (transactions, events) surrounded by dimension tables (who, what, when, where). Dimensions connect directly to the fact table — one level of JOIN. Simple, fast, preferred for most data warehouses.

Snowflake Schema

Like Star, but dimension tables are further normalized into sub-dimensions. More JOINs required, more storage-efficient, harder to query. Used when dimension tables are very large or shared across fact tables.

✦ Slowly Changing Dimensions (SCD): Dimensions change over time (customer moves, product price changes). SCD Type 1: overwrite old value. SCD Type 2: add new row with version dates (most common in warehouses). SCD Type 3: add column for previous value. This is heavily tested in data engineering interviews.

6. Columnar Storage Internals

Traditional vs True Column-Store

Traditional OLAP (Oracle, SQL Server, older warehouses): logically designed for analytical queries (aggregations, GROUP BY, slice/dice) but physically row-oriented on disk.
True Column-Store (Redshift, BigQuery, Snowflake, DuckDB, Vertica, ClickHouse): physically store each column on separate contiguous segments on disk. Columns compress extremely well. Vectorized execution engines process column batches with SIMD CPU instructions.

How Columnar Query Execution Works (Bitmap Scanning)

-- Executing: SELECT * FROM orders WHERE status='shipped' AND region='US'

Step 1: Scan status column → produce position bitmap
        [0, 0, 1, 0, 1, 1, 0, 0, 0, 0]  -- 1 = matches 'shipped'

Step 2: Scan region column → produce position bitmap
        [1, 0, 1, 1, 0, 1, 0, 1, 0, 0]  -- 1 = matches 'US'

Step 3: AND bitmaps together → combined position bitmap
        [0, 0, 1, 0, 0, 1, 0, 0, 0, 0]  -- positions 3 and 6 match both

Step 4: Use bitmap to fetch only matching rows from remaining columns
        -- Read only 2 rows instead of 10 — massive I/O savings

Apache Parquet — Columnar but Row-Grouped

-- Parquet file structure: row-groups containing column chunks

Row Group 1 (rows 0–99,999):
  ├── Column Chunk: order_id    (100K values, compressed)
  ├── Column Chunk: status      (100K values, compressed)
  └── Column Chunk: amount      (100K values, compressed)

Row Group 2 (rows 100K–199,999):
  ├── Column Chunk: order_id    (100K values, compressed)
  ├── Column Chunk: status      (100K values, compressed)
  └── Column Chunk: amount      (100K values, compressed)
✦ Compression in columnar: Columns of the same type compress dramatically better than mixed rows. Run-Length Encoding (RLE) for repeated values, Dictionary Encoding for low-cardinality columns, and Delta Encoding for sequential numbers. Redshift achieves 3–4x compression ratios. This is why columnar storage is so cost-effective for analytics.

7. OLAP Database Landscape

MPP (Massively Parallel)

  • Vertica
  • Greenplum
  • Teradata

Distribute queries across many nodes. Best for large enterprise warehouses.

Real-time OLAP

  • ClickHouse
  • Apache Druid
  • Apache Pinot

Sub-second analytics on streaming data. Used for dashboards and monitoring.

Cloud Data Warehouses

  • Amazon Redshift
  • Snowflake
  • Google BigQuery

Managed, elastic, pay-per-query. Dominant in modern data stacks.

8. DuckDB vs PostgreSQL

DuckDB

  • Not built for fast simultaneous writes
  • Stores data temporarily in volatile memory
  • Must configure persistent settings to write to disk
  • Limited to single-writer scenarios
  • Doesn't support concurrent writes
  • Ideal: analytics, Jupyter notebooks, local OLAP

PostgreSQL

  • Handles concurrent transactions effectively
  • Supports concurrent connections for reads and writes
  • Slightly faster for loading data into tables
  • Full ACID with MVCC
  • Ideal: production OLTP, multi-user apps

9. NoSQL & Document Stores

NoSQL = Not Only SQL (not "no relational databases"). The structure of data keeps shifting — documents, key-value pairs, flexible record structures. Categories:

Document Stores

Rich structure, sorting, filtering, indexing. Records look like JSON objects.

MongoDB, DynamoDB, Firestore, DocumentDB

Key-Value Stores

Minimal structure — a pile of boxes. Fast simple lookups but no sorting or filtering support.

Redis, DynamoDB (KV mode), Memcached

MongoDB

"Humongous database" — built for massive scale. No schema changes needed, no table extensions. Uses MQL (MongoDB Query Language). Internally stores data as BSON (binary JSON); for users it behaves like JSON.

MongoDBRelational DB
CollectionTable
DocumentRow
FieldColumn
MongoDB supports: indexes, query subsets of data, and schema validation rules.

Amazon DynamoDB

Purpose-built for unpredictable, massive scale — designed to support Amazon's holiday traffic spikes. Single-digit ms performance at any scale. Access patterns are more constrained than MongoDB — must design partition/sort keys carefully.

Apache Cassandra

Built for Facebook's inbox search serving millions of users. If you don't enforce some structure, it becomes extremely complex to filter and aggregate data. Amazon Keyspaces provides managed Cassandra using CQL for high-throughput workloads.

10. Document CRUD with Code

Create (Insert)

# MongoDB
db.customers.insert_one(data_dict)

# DynamoDB
db.Table("Customers").put_item(Item=data_dict)

# Firestore
db.collection("Customers").add(data_dict)

Read (Query)

filter_expr = {"email": "example@email.com"}

# MongoDB
db.customers.find_one(filter_expr)

# DynamoDB
db.Table("Customers").get_item(Key=filter_expr)

# Firestore
db.collection("Customers").where("email", "==", "example@email.com").get()

Update

filter_expr = {"email": "old@email.com"}
new_values  = {"email": "new@email.com"}

# MongoDB — does NOT overwrite whole doc, only updates specified attributes
db.customers.update_one(filter_expr, {"$set": new_values})

# DynamoDB — uses expression syntax, distinguishable via placeholders
db.Table("Customers").update_item(
    Key=filter_expr,
    UpdateExpression="SET email = :new_email",
    ExpressionAttributeValues={":new_email": "new@email.com"}
)

# Firestore
result = db.collection("Customers").where("email", "==", "old").get()[0].reference
result.update(new_values)

Delete

filter_expr = {"user_id": "user123"}

# MongoDB — can delete based on ANY filter
db.customers.delete_one(filter_expr)

# DynamoDB — can ONLY delete based on primary key
db.Table("Customers").delete_item(Key=filter_expr)

# Firestore
result = db.collection("Customers").where("user_id", "==", "user123").get()[0].reference
result.delete()
Critical difference: MongoDB can delete items based on any filter, but DynamoDB can only delete items based on primary key. This constraint shapes how you design DynamoDB access patterns.

11. MQL Operators & Aggregation Pipeline

MQL Update Operators

In MQL, operators always start with $ (dollar sign):

OperatorActionExample
$setChange or add a field{"$set": {"status": "active"}}
$unsetRemove a field{"$unset": {"temp_field": ""}}
$pushAppend to an array{"$push": {"tags": "new_tag"}}
$incIncrement a number{"$inc": {"login_count": 1}}
$pullRemove from an array{"$pull": {"tags": "old_tag"}}

Aggregation Pipeline

MongoDB's aggregation pipeline is essentially an ETL pipeline for performing advanced data analysis and manipulation on collections:

# An ETL pipeline inside MongoDB
db.orders.aggregate([
    {"$lookup": {...}},   # JOIN-like operator across collections (Extract)
    {"$match": {...}},    # Filter documents                    (Extract)
    {"$group": {          # Aggregate: $sum, $avg, $count       (Transform)
        "_id": "$category",
        "total": {"$sum": "$amount"}
    }},
    {"$sort": {...}}      # Order results                       (Load)
])

Joins Across Document Databases

DatabaseJOIN Support
Relational (SQL)Native JOIN — first-class feature
MongoDB$lookup in aggregation pipeline — JOIN-like capability
DynamoDB & FirestoreNo support — application must handle with custom logic

12. Schema Evolution in Document DBs

Document databases have no DDL (no CREATE TABLE, no ALTER TABLE). You simply start inserting documents. The document shape becomes the de facto structure of the collection.

Adding new fields: Insert new documents with new structure instead of altering existing ones.
Gradual evolution: The application handles mixed document versions — old documents with 5 fields coexist with new documents with 8 fields.
Document databases use CRUD as the migration tool — changes are ongoing and require careful testing and validation.
Trade-off: This flexibility trades strict database-level control for speed, making application design responsible for maintaining consistency. Validation is a design decision, not a database guarantee.

MongoDB Schema Validation

MongoDB does support optional schema validation at the collection level — rules defined during collection creation validate documents on insert/update. This provides a middle ground between fully rigid relational schemas and zero enforcement.

# Define validation rules when creating a collection
db.create_collection("customers", validator={
    "$jsonSchema": {
        "required": ["name", "email"],
        "properties": {
            "name":  {"bsonType": "string"},
            "email": {"bsonType": "string", "pattern": "@"}
        }
    }
})

13. ACID Guarantees in NoSQL

NoSQL databases implement ACID differently — scoped differently, made optional, and optimized for their specific design and workload patterns.

PropertyMongoDBDynamoDBCassandra
AtomicityAt document levelAt item levelAt partition/row level
ConsistencyDeveloper responsibility to update dependenciesEventual or strong (configurable)Tunable consistency (ONE, QUORUM, ALL)
IsolationDocument-level; multi-doc via snapshot isolationItem-levelPartition-level
DurabilityDocument and transaction level; horizontal replication and shardingItem-level; automatic replication across AZsPartition-level; configurable replication factor
✦ CAP Theorem: Any distributed database can guarantee only 2 of 3: Consistency, Availability, Partition tolerance. MongoDB is CP (consistency + partition tolerance — may reject writes during network partition). Cassandra is AP (availability + partition tolerance — serves stale data during partition). DynamoDB offers tunable consistency per-read. This drives every NoSQL architecture decision.

14. Embedding vs Referencing

MongoDB inverts the relational pattern:

WorkloadRelational DBMongoDB
OLTPNormalized (prevent anomalies)Denormalized — Embedding
OLAPDenormalized (Star/Snowflake)Normalized — Referencing

Embedding (Denormalized)

Store related data inside a single document. One read retrieves everything. Best for data accessed together frequently.

customer = {
  _id: 1,
  name: "Alice",
  address: {        ← embedded
    city: "Phoenix",
    zip: "85001"
  }
}

Referencing (Normalized)

Store a reference ID linking to another collection. Requires additional query. Best for many-to-many or frequently updated related data.

customer = {
  _id: 1,
  name: "Alice",
  address_id: 42    ← reference
}
MongoDB's philosophy: Leave the decision to the user based on application needs — frequency of reads vs writes determines whether to use embedding or referencing. There's no single right answer.

15. CRUD Anomalies in NoSQL

Create Anomaly: Less database enforcement = more application responsibility. Partial data accepted — the app must handle missing values.
Update Anomaly: Multiple similar records require precise filtering. No schema enforcement means the app must know exactly which documents to update.
Delete Anomaly: Related data remains orphaned. No automatic cascading deletes — the application must clean up dependencies.
Overall principle: In NoSQL, the database pushes responsibility to the application layer. Validation is a design decision, not a database guarantee. This is the fundamental trade-off for schema flexibility.

16. Graph Databases

When the most important thing about data is the relationships between items — the connections — graph databases are the natural fit. Connections are primary, not secondary. Graph databases treat connections between entities as first-class citizens.

Three questions a graph database answers naturally:

Who is connected to whom?
How are these entities connected?
What paths exist across a network?

Origin & Use Cases

Neo4j was built by a team trying to build an ECM (Enterprise Content Management) system — for storing, organizing, and retrieving large amounts of interconnected content (documents, references, permissions, pages). They realized the relationships mattered more than the data itself. Graph databases provide full ACID guarantees.

Discovery & Intelligence

  • Knowledge Bases
  • Recommendation Engines
  • Semantic Search Engines

Security & Operations

  • Fraud Detection & Transaction Analysis
  • Access Control & Permissions
  • Social & Communication Networks
  • Network Topology

Graph Data Model

ConceptGraph DBRelational DBDocument DB
EntityNodeRowDocument
AttributePropertyColumnField
ClassificationLabelTableCollection
ConnectionRelationship (Edge)JOINEmbedded / $lookup

ACID in Graph Databases

PropertyGraph DB Implementation
AtomicityAtomic at individual operations. Multi-step: BEGIN → create node → create relationship → COMMIT
ConsistencyRules optional but can be enforced via GQL schema constraints
IsolationIndividual updates isolated; group transactions safely handled; conflicts managed without corrupting graph
DurabilityWritten to disk. Horizontal scaling via clusters and replication

Intermediate Nodes & Normalization

When a relationship carries too many properties, create an intermediate node instead. This is the graph equivalent of normalization — determining the right level of detail per entity.

GQL Validation (2024 ISO standard): Define node types, allowed relationships, and required properties at schema level. Database constraints provide runtime validation checks on write operations.

Neo4j Python Driver: CREATE vs MERGE

# CREATE — always creates new (duplicates if run twice)
driver.execute_query("CREATE (c:Customer {name: $name})", name="Alice")

# MERGE — find-or-create (idempotent, preferred)
driver.execute_query("""
    MERGE (c:Customer {id: $id})
    ON CREATE SET c.name = $name, c.created = datetime()
    ON MATCH SET c.updated = datetime()
    RETURN c
""", id="C001", name="Alice")

# driver.execute_query() — handles session internally (newer API)
# session.run() — requires explicit with driver.session() block
# RETURN is optional — use for confirmation, debugging, accessing IDs

# Relationships — create with nodes or separately
driver.execute_query("""
    MATCH (c:Customer {id: $cid}), (e:Email {address: $email})
    MERGE (c)-[:HAS_EMAIL]->(e)
""", cid="C001", email="alice@example.com")

Graph Database Engines

🔵 Neo4j Open Source
🟠 Amazon Neptune AWS
🔵 Spanner Graph Google Cloud
🟣 Azure Cosmos DB Azure
In Python, networkx provides graph structures and algorithms without a database — useful for prototyping and analysis.

17. Cloud Database Services

📄 MongoDB Atlas Replication, Sharding, Autoscale
📄 Amazon DocumentDB Managed MongoDB-compatible
⚡ Amazon DynamoDB Managed Document + KV
📊 Amazon Keyspaces Managed Cassandra (CQL)
🔴 Amazon Redshift Cloud DW, columnar
❄️ Snowflake Multi-cloud DW
🔵 Google BigQuery Serverless analytics

AWS Authentication Pattern (boto3)

import boto3, json
from botocore.exceptions import ClientError

# STS Session
session = boto3.Session(
    aws_access_key_id="...",
    aws_secret_access_key="...",
    aws_session_token="...",
    region_name="us-east-1"
)
sts_client = session.client("sts")
identity = sts_client.get_caller_identity()

# Secrets Manager — store DB credentials
secretsmanager = boto3.client("secretsmanager")
try:
    response = secretsmanager.create_secret(
        Name="rds-postgres-credentials",
        SecretString=json.dumps(secret_dict)
    )
except ClientError as e:
    print(e)

The Data Model — Final Thought

A data model is a structured representation connecting human understanding (logical) with machine storage and retrieval (physical).

Polyglot Persistence: The practice of using multiple database technologies within a single system to meet varying storage needs. An e-commerce platform might use PostgreSQL for orders, Redis for sessions, Elasticsearch for search, and Neo4j for recommendations — each chosen for its strength.
Core truth: No single model can have everything. It can only contain a detailed representation of some part of the data. The art of data modeling is choosing which aspects to optimize for and which trade-offs to accept.
📘
Designing Data-Intensive Applications
by Martin Kleppmann — The definitive guide to storage engines, replication, partitioning, batch/stream processing, and the trade-offs behind every database decision in this article.
🛠️
Complete working project implementing relational (PostgreSQL), document (MongoDB), and graph (Neo4j) databases with Python. Covers schema design, CRUD operations, aggregation pipelines, and graph traversals using a unified customer data model.

🎯 Topics to Explore Next

Based on Data Engineering certifications and interview patterns, these topics aren't yet covered in your notes: