NoSQL Databases - Document, Key-Value, and Column-Family Stores
Learning Objectives: By the end of this lesson, you will be able to:
- Understand the motivation behind NoSQL databases and the problems they solve
- Design data models for document databases like MongoDB
- Implement caching and session management with key-value stores like Redis
- Understand column-family databases for high-throughput write workloads
- Choose the appropriate NoSQL solution for specific use cases
1. Welcome and Introduction (5 minutes)
Icebreaker Activity
Imagine you’re building Twitter from scratch. Millions of users posting tweets, following others, and scrolling through feeds—all happening simultaneously. Your traditional relational database is struggling: adding columns to the users table for new features requires hours of downtime, and JOIN operations across millions of rows are taking seconds instead of milliseconds. What if you could store each user’s data as a flexible document? What if tweets could be cached in memory for instant access?
This is the world of NoSQL—databases designed for the scale and flexibility that modern applications demand.
Lesson Overview
- Duration: 120 minutes
- Prerequisites: Understanding of database fundamentals, familiarity with JSON
- Materials Needed: Docker, MongoDB, Redis, basic programming knowledge
2. Learning Objectives and Session Outline (5 minutes)
What We’ll Cover Today:
- NoSQL Origins - Why NoSQL emerged and what problems it solves
- Document Databases - MongoDB, CouchDB, and flexible schemas
- Key-Value Stores - Redis, DynamoDB, and in-memory caching
- Column-Family Databases - Cassandra, HBase, and write optimization
- Data Modeling Patterns - How to design for NoSQL databases
- Trade-offs and Selection - When to use which type
Success Criteria:
By the end of this session, you should be able to design data models for different NoSQL database types and understand when to choose each paradigm.
3. Foundation Knowledge (15-20 minutes)
Core Concepts
The NoSQL Movement
NoSQL (originally “No SQL,” now “Not Only SQL”) emerged in the late 2000s as a response to the limitations of relational databases for certain workloads:
Driving Factors:
- Web Scale: Social media, e-commerce, and global applications needed to handle millions of concurrent users
- Big Data: Data volumes exceeded what single machines could handle
- Agile Development: Rigid schemas slowed down rapid iteration
- Cloud Computing: Distributed systems needed databases designed for horizontal scaling
Real-World Analogy: Think of relational databases as traditional filing cabinets with strict folder structures—great for organized offices but terrible when you’re moving fast and each document is different. NoSQL databases are like flexible storage bins where you can throw in different items and find them quickly using smart labeling systems.
Why This Matters: Modern applications often have requirements that don’t fit well with traditional relational models. Understanding NoSQL options allows you to choose the right tool for specific problems rather than forcing everything into one paradigm.
CAP Theorem
The CAP theorem states that distributed databases can only guarantee two of three properties simultaneously:
- Consistency: All nodes see the same data at the same time
- Availability: Every request receives a response (success or failure)
- Partition Tolerance: The system continues operating despite network failures
Implications:
- CP Systems: Consistent and partition-tolerant, but may become unavailable during partitions (MongoDB, HBase)
- AP Systems: Available and partition-tolerant, but may show inconsistent data temporarily (Cassandra, DynamoDB)
- CA Systems: Consistent and available, but can’t handle partitions (traditional RDBMS—effectively not distributed)
Key Terminology
- Document: A self-contained data unit, typically JSON/BSON format
- Collection: A group of documents (analogous to a table)
- Key-Value Pair: Data stored as a key with an associated value
- Column Family: A group of related columns stored together
- Eventual Consistency: Data will become consistent over time after updates
- Sharding: Distributing data across multiple servers
- Replica Set: Copies of data for redundancy and read scaling
- TTL (Time to Live): Automatic expiration of data
- Denormalization: Intentionally duplicating data for query efficiency
4. Detailed Explanation and Examples (25-30 minutes)
Document Databases
Document databases store data as semi-structured documents, typically in JSON or BSON format. Each document is self-contained and can have a different structure.
Key Characteristics
- Flexible Schema: Documents in the same collection can have different fields
- Nested Data: Support for embedded documents and arrays
- Rich Queries: Query by any field, including nested fields
- Horizontal Scaling: Built-in sharding support
MongoDB: The Most Popular Document Database
Document Structure:
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"username": "johndoe",
"email": "john@example.com",
"profile": {
"firstName": "John",
"lastName": "Doe",
"age": 30,
"interests": ["coding", "gaming", "reading"]
},
"posts": [
{
"title": "My First Post",
"content": "Hello world!",
"tags": ["intro", "personal"],
"createdAt": ISODate("2024-01-15T10:00:00Z")
}
],
"createdAt": ISODate("2024-01-01T00:00:00Z"),
"lastLogin": ISODate("2024-01-20T14:30:00Z")
}
When to Use Document Databases:
- Content management systems
- Product catalogs with varying attributes
- User profiles and personalization
- Real-time analytics and logging
- Mobile application backends
Key-Value Stores
Key-value stores are the simplest NoSQL databases—they store data as key-value pairs with extremely fast read/write operations.
Key Characteristics
- Simple Data Model: Just keys and values
- Blazing Fast: Often in-memory, sub-millisecond operations
- Perfect for Caching: Reduce load on primary databases
- Limited Querying: Can only access by key
Redis: The Swiss Army Knife of Key-Value Stores
Redis extends the basic key-value model with rich data structures:
Data Structures:
- Strings: Simple key-value pairs
- Lists: Ordered collections (queues, stacks)
- Sets: Unordered unique collections
- Sorted Sets: Ordered by score (leaderboards)
- Hashes: Field-value maps (object storage)
- Streams: Append-only log (event streaming)
When to Use Key-Value Stores:
- Session storage and authentication tokens
- Caching frequently accessed data
- Real-time leaderboards and counters
- Rate limiting and throttling
- Message queues and pub/sub
- Feature flags and configuration
Column-Family Databases
Column-family databases store data in columns rather than rows, optimized for write-heavy workloads and analytical queries.
Key Characteristics
- Column-Oriented Storage: Data in the same column stored together
- Sparse Data Friendly: Empty columns don’t consume space
- High Write Throughput: Optimized for sequential writes
- Linear Scalability: Easy horizontal scaling
Apache Cassandra: Distributed Excellence
Data Model:
Keyspace: ecommerce
└── Table: orders
└── Partition Key: customer_id
└── Clustering Key: order_date
└── Columns: order_id, status, total, items
When to Use Column-Family Databases:
- Time-series data (IoT, metrics)
- Write-heavy workloads
- Logging and audit trails
- Messaging applications
- Recommendation engines
Common Misconceptions
-
Misconception: “NoSQL means no schema” Reality: NoSQL databases have schemas, just more flexible ones. Many support schema validation.
-
Misconception: “NoSQL is always faster than SQL” Reality: Performance depends on the use case. Relational databases can outperform NoSQL for complex queries with joins.
-
Misconception: “NoSQL can’t do transactions” Reality: Many NoSQL databases support transactions (MongoDB ACID transactions, DynamoDB transactions).
-
Misconception: “NoSQL databases don’t need design” Reality: Poor data modeling in NoSQL can lead to worse problems than in SQL databases.
5. Live Coding Demonstration (20-25 minutes)
Example 1: MongoDB CRUD Operations
// Connect to MongoDB
use ecommerce;
// Create - Insert documents
db.products.insertOne({
name: "Wireless Headphones",
brand: "TechAudio",
price: 79.99,
category: "Electronics",
specs: {
battery_life: "30 hours",
connectivity: ["Bluetooth 5.0", "3.5mm jack"],
weight: "250g"
},
tags: ["wireless", "audio", "bluetooth"],
inventory: {
warehouse_a: 150,
warehouse_b: 75
},
reviews: [],
createdAt: new Date()
});
// Insert many documents
db.products.insertMany([
{
name: "Gaming Mouse",
brand: "ProGamer",
price: 49.99,
category: "Electronics",
specs: { dpi: 16000, buttons: 8, rgb: true },
tags: ["gaming", "mouse", "rgb"],
inventory: { warehouse_a: 200 },
createdAt: new Date()
},
{
name: "Mechanical Keyboard",
brand: "TypeMaster",
price: 129.99,
category: "Electronics",
specs: { switches: "Cherry MX Blue", layout: "TKL", backlight: "RGB" },
tags: ["gaming", "keyboard", "mechanical"],
inventory: { warehouse_a: 100, warehouse_b: 50 },
createdAt: new Date()
}
]);
// Read - Find documents
// Find all electronics
db.products.find({ category: "Electronics" });
// Find with price range
db.products.find({
price: { $gte: 50, $lte: 100 }
});
// Query nested fields
db.products.find({
"specs.rgb": true
});
// Query arrays
db.products.find({
tags: "gaming"
});
// Projection (select specific fields)
db.products.find(
{ category: "Electronics" },
{ name: 1, price: 1, _id: 0 }
);
// Update - Modify documents
// Update one document
db.products.updateOne(
{ name: "Wireless Headphones" },
{
$set: { price: 69.99 },
$push: {
reviews: {
user: "john_doe",
rating: 5,
comment: "Great sound quality!",
date: new Date()
}
}
}
);
// Update many
db.products.updateMany(
{ category: "Electronics" },
{ $set: { onSale: true } }
);
// Increment values
db.products.updateOne(
{ name: "Gaming Mouse" },
{ $inc: { "inventory.warehouse_a": -1 } }
);
// Delete - Remove documents
db.products.deleteOne({ name: "Old Product" });
db.products.deleteMany({ inventory: { $exists: false } });
// Aggregation Pipeline
db.products.aggregate([
// Stage 1: Filter
{ $match: { category: "Electronics" } },
// Stage 2: Add computed fields
{ $addFields: {
totalInventory: {
$add: [
{ $ifNull: ["$inventory.warehouse_a", 0] },
{ $ifNull: ["$inventory.warehouse_b", 0] }
]
}
}},
// Stage 3: Group by brand
{ $group: {
_id: "$brand",
productCount: { $sum: 1 },
avgPrice: { $avg: "$price" },
totalInventory: { $sum: "$totalInventory" }
}},
// Stage 4: Sort
{ $sort: { productCount: -1 } }
]);
// Create indexes
db.products.createIndex({ name: 1 });
db.products.createIndex({ category: 1, price: 1 });
db.products.createIndex({ tags: 1 });
db.products.createIndex({ "specs.rgb": 1 });
Example 2: Redis Operations
# String operations
SET user:1001:session "eyJhbGciOiJIUzI1NiIsInR..."
GET user:1001:session
SETEX user:1001:session 3600 "token..." # Expires in 1 hour
# Working with counters
SET page:views:homepage 0
INCR page:views:homepage
INCRBY page:views:homepage 10
GET page:views:homepage
# Hash operations (object storage)
HSET user:1001 name "John Doe" email "john@example.com" age 30
HGET user:1001 name
HGETALL user:1001
HINCRBY user:1001 age 1
# List operations (queues)
LPUSH tasks:queue "task:process_order:123"
LPUSH tasks:queue "task:send_email:456"
RPOP tasks:queue # Get from other end (FIFO queue)
LRANGE tasks:queue 0 -1 # Get all items
# Set operations (unique collections)
SADD user:1001:interests "coding" "gaming" "reading"
SMEMBERS user:1001:interests
SISMEMBER user:1001:interests "coding" # Check membership
SADD user:1002:interests "gaming" "music"
SINTER user:1001:interests user:1002:interests # Common interests
# Sorted Set (leaderboard)
ZADD leaderboard 1000 "player:alice"
ZADD leaderboard 1500 "player:bob"
ZADD leaderboard 800 "player:charlie"
ZINCRBY leaderboard 200 "player:alice" # Alice scores 200 more
ZRANGE leaderboard 0 -1 WITHSCORES # Lowest to highest
ZREVRANGE leaderboard 0 2 WITHSCORES # Top 3 players
# TTL and expiration
SET session:token:abc123 "user:1001"
EXPIRE session:token:abc123 1800 # 30 minutes
TTL session:token:abc123 # Check remaining time
# Pub/Sub messaging
SUBSCRIBE notifications:user:1001
PUBLISH notifications:user:1001 "New message received!"
# Transactions (MULTI/EXEC)
MULTI
DECRBY account:1001:balance 100
INCRBY account:1002:balance 100
EXEC
Example 3: Cassandra Data Modeling
-- Create keyspace
CREATE KEYSPACE ecommerce
WITH REPLICATION = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 3
};
USE ecommerce;
-- Orders by customer (optimized for customer lookup)
CREATE TABLE orders_by_customer (
customer_id UUID,
order_date TIMESTAMP,
order_id UUID,
status TEXT,
total DECIMAL,
items LIST<FROZEN<map<TEXT, TEXT>>>,
PRIMARY KEY ((customer_id), order_date, order_id)
) WITH CLUSTERING ORDER BY (order_date DESC);
-- Orders by date (optimized for date range queries)
CREATE TABLE orders_by_date (
order_date DATE,
order_id UUID,
customer_id UUID,
status TEXT,
total DECIMAL,
PRIMARY KEY ((order_date), order_id)
);
-- Product inventory by warehouse
CREATE TABLE inventory_by_warehouse (
warehouse_id TEXT,
product_id UUID,
quantity INT,
last_updated TIMESTAMP,
PRIMARY KEY ((warehouse_id), product_id)
);
-- Insert data
INSERT INTO orders_by_customer (
customer_id,
order_date,
order_id,
status,
total,
items
) VALUES (
uuid(),
toTimestamp(now()),
uuid(),
'shipped',
199.99,
[{'product': 'Widget', 'qty': '2', 'price': '99.99'}]
);
-- Query patterns
-- Get customer's recent orders
SELECT * FROM orders_by_customer
WHERE customer_id = some_uuid
AND order_date > '2024-01-01'
LIMIT 10;
-- Get orders for a specific date
SELECT * FROM orders_by_date
WHERE order_date = '2024-01-15';
-- Counter tables for analytics
CREATE TABLE page_views (
page_path TEXT,
view_date DATE,
view_count COUNTER,
PRIMARY KEY ((page_path), view_date)
);
UPDATE page_views
SET view_count = view_count + 1
WHERE page_path = '/home' AND view_date = toDate(now());
Example 4: Document Database Schema Design Patterns
// Pattern 1: Embedding (Denormalization)
// Good for: Data that's always accessed together
{
"_id": ObjectId("..."),
"order_id": "ORD-12345",
"customer": {
"name": "John Doe",
"email": "john@example.com",
"address": {
"street": "123 Main St",
"city": "New York",
"zip": "10001"
}
},
"items": [
{ "product": "Widget", "quantity": 2, "price": 29.99 },
{ "product": "Gadget", "quantity": 1, "price": 49.99 }
],
"total": 109.97,
"status": "shipped"
}
// Pattern 2: Referencing (Normalization)
// Good for: Large subdocuments, frequently updated data, many-to-many
// User document
{
"_id": ObjectId("user123"),
"username": "johndoe",
"email": "john@example.com"
}
// Order document (references user)
{
"_id": ObjectId("..."),
"user_id": ObjectId("user123"), // Reference
"items": [
{ "product_id": ObjectId("prod456"), "quantity": 2 }
],
"total": 109.97
}
// Pattern 3: Bucket Pattern (for time-series)
// Good for: Sensor data, logs, metrics
{
"_id": ObjectId("..."),
"sensor_id": "sensor-001",
"date": ISODate("2024-01-15"),
"readings": [
{ "time": ISODate("2024-01-15T00:00:00Z"), "temp": 22.5, "humidity": 45 },
{ "time": ISODate("2024-01-15T00:01:00Z"), "temp": 22.6, "humidity": 44 },
// ... up to ~200 readings per document
],
"summary": {
"avg_temp": 22.5,
"min_temp": 21.0,
"max_temp": 24.0
}
}
// Pattern 4: Polymorphic Pattern
// Good for: Different types sharing a collection
// Product collection with different product types
{
"_id": ObjectId("..."),
"type": "book",
"name": "Clean Code",
"price": 39.99,
"author": "Robert Martin",
"isbn": "978-0132350884",
"pages": 464
}
{
"_id": ObjectId("..."),
"type": "electronics",
"name": "Bluetooth Speaker",
"price": 79.99,
"brand": "JBL",
"battery_life": "12 hours",
"waterproof": true
}
// Pattern 5: Computed Pattern
// Good for: Expensive calculations, analytics
{
"_id": ObjectId("..."),
"product_id": "PROD-001",
"reviews": [/* many reviews */],
"review_count": 1523, // Computed
"average_rating": 4.7, // Computed
"rating_distribution": { // Computed
"5": 890,
"4": 412,
"3": 156,
"2": 43,
"1": 22
},
"last_computed": ISODate("2024-01-15T00:00:00Z")
}
6. Hands-On Practice (30-40 minutes)
Exercise 1: Design a Social Media Backend (15 minutes)
Objective: Design MongoDB collections for a Twitter-like application
Requirements:
- Users with profiles and followers
- Tweets with likes, retweets, and replies
- Timeline generation for each user
- Trending hashtags tracking
Solution:
// Users collection
{
"_id": ObjectId("user123"),
"username": "johndoe",
"email": "john@example.com",
"profile": {
"displayName": "John Doe",
"bio": "Software developer",
"avatar": "https://...",
"location": "NYC"
},
"stats": {
"followers": 1234,
"following": 567,
"tweets": 890
},
"createdAt": ISODate("...")
}
// Follows collection (for many-to-many)
{
"_id": ObjectId("..."),
"follower_id": ObjectId("user123"),
"following_id": ObjectId("user456"),
"createdAt": ISODate("...")
}
// Tweets collection
{
"_id": ObjectId("tweet789"),
"author_id": ObjectId("user123"),
"author": { // Denormalized for display
"username": "johndoe",
"displayName": "John Doe",
"avatar": "https://..."
},
"content": "Hello world! #firsttweet",
"hashtags": ["firsttweet"],
"mentions": [],
"media": [],
"stats": {
"likes": 42,
"retweets": 10,
"replies": 5,
"views": 1000
},
"retweet_of": null, // If this is a retweet
"reply_to": null, // If this is a reply
"createdAt": ISODate("...")
}
// Interactions collection (for tracking likes/retweets)
{
"_id": ObjectId("..."),
"user_id": ObjectId("user456"),
"tweet_id": ObjectId("tweet789"),
"type": "like", // or "retweet"
"createdAt": ISODate("...")
}
// Timeline collection (fan-out on write)
{
"_id": ObjectId("..."),
"user_id": ObjectId("user456"),
"tweet_id": ObjectId("tweet789"),
"author_id": ObjectId("user123"),
"createdAt": ISODate("...") // Tweet creation time for sorting
}
// Trending hashtags (with time windows)
{
"_id": ObjectId("..."),
"hashtag": "firsttweet",
"window": ISODate("2024-01-15T10:00:00Z"), // Hourly window
"count": 1523,
"region": "US"
}
// Indexes
db.tweets.createIndex({ author_id: 1, createdAt: -1 });
db.tweets.createIndex({ hashtags: 1, createdAt: -1 });
db.timeline.createIndex({ user_id: 1, createdAt: -1 });
db.follows.createIndex({ follower_id: 1 });
db.follows.createIndex({ following_id: 1 });
db.interactions.createIndex({ tweet_id: 1, type: 1 });
db.interactions.createIndex({ user_id: 1, tweet_id: 1 }, { unique: true });
Exercise 2: Redis Caching Layer (10 minutes)
Objective: Design a caching strategy using Redis
Challenge: Implement caching for:
- User sessions (expire after 30 minutes of inactivity)
- Product details (cached for 5 minutes)
- Shopping cart (persist until checkout)
Solution:
# User sessions with sliding expiration
# Set session
HSET session:abc123 user_id "1001" username "johndoe" role "user"
EXPIRE session:abc123 1800 # 30 minutes
# On each request, refresh TTL
GET session:abc123
EXPIRE session:abc123 1800 # Reset to 30 minutes
# Check session
EXISTS session:abc123
TTL session:abc123
# Product caching
# Store product as JSON string
SET product:PROD-001 '{"id":"PROD-001","name":"Widget","price":29.99}' EX 300
# Or use Hash for individual field access
HSET product:PROD-001 name "Widget" price "29.99" stock "150"
EXPIRE product:PROD-001 300 # 5 minutes
# Cache invalidation on product update
DEL product:PROD-001
# Shopping cart (no expiration, persist until checkout)
# Add item
HSET cart:user:1001 "PROD-001" '{"quantity":2,"price":29.99}'
HSET cart:user:1001 "PROD-002" '{"quantity":1,"price":49.99}'
# Get entire cart
HGETALL cart:user:1001
# Update quantity
HSET cart:user:1001 "PROD-001" '{"quantity":3,"price":29.99}'
# Remove item
HDEL cart:user:1001 "PROD-001"
# Get cart item count
HLEN cart:user:1001
# Clear cart after checkout
DEL cart:user:1001
# Rate limiting pattern
# Allow 100 requests per minute
SET ratelimit:user:1001:minute "0" EX 60 NX
INCR ratelimit:user:1001:minute
# Check if over limit
GET ratelimit:user:1001:minute
Exercise 3: Cassandra Time-Series Design (15 minutes)
Objective: Design tables for an IoT monitoring system
Requirements:
- Store sensor readings (temperature, humidity, pressure)
- Query by sensor for time ranges
- Query all sensors for a specific time
- Keep 90 days of data
Solution:
-- Keyspace with replication
CREATE KEYSPACE iot_monitoring
WITH REPLICATION = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 3
};
USE iot_monitoring;
-- Readings by sensor (primary query pattern)
CREATE TABLE readings_by_sensor (
sensor_id TEXT,
date DATE,
timestamp TIMESTAMP,
temperature DOUBLE,
humidity DOUBLE,
pressure DOUBLE,
PRIMARY KEY ((sensor_id, date), timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC)
AND default_time_to_live = 7776000; -- 90 days in seconds
-- Readings by time (for cross-sensor queries)
CREATE TABLE readings_by_time (
time_bucket TEXT, -- e.g., "2024-01-15-10" (hourly buckets)
sensor_id TEXT,
timestamp TIMESTAMP,
temperature DOUBLE,
humidity DOUBLE,
pressure DOUBLE,
PRIMARY KEY ((time_bucket), timestamp, sensor_id)
) WITH CLUSTERING ORDER BY (timestamp DESC)
AND default_time_to_live = 7776000;
-- Sensor metadata
CREATE TABLE sensors (
sensor_id TEXT PRIMARY KEY,
name TEXT,
location TEXT,
type TEXT,
installed_at TIMESTAMP,
status TEXT
);
-- Alerts table
CREATE TABLE alerts_by_sensor (
sensor_id TEXT,
alert_date DATE,
alert_time TIMESTAMP,
alert_id UUID,
severity TEXT,
metric TEXT,
value DOUBLE,
threshold DOUBLE,
message TEXT,
acknowledged BOOLEAN,
PRIMARY KEY ((sensor_id, alert_date), alert_time, alert_id)
) WITH CLUSTERING ORDER BY (alert_time DESC);
-- Insert reading (application writes to both tables)
INSERT INTO readings_by_sensor (
sensor_id, date, timestamp, temperature, humidity, pressure
) VALUES (
'sensor-001',
'2024-01-15',
'2024-01-15T10:30:00Z',
22.5,
45.0,
1013.25
);
INSERT INTO readings_by_time (
time_bucket, sensor_id, timestamp, temperature, humidity, pressure
) VALUES (
'2024-01-15-10',
'sensor-001',
'2024-01-15T10:30:00Z',
22.5,
45.0,
1013.25
);
-- Query: Get sensor readings for last 24 hours
SELECT * FROM readings_by_sensor
WHERE sensor_id = 'sensor-001'
AND date = '2024-01-15'
AND timestamp > '2024-01-14T10:30:00Z';
-- Query: Get all sensors for a specific hour
SELECT * FROM readings_by_time
WHERE time_bucket = '2024-01-15-10';
-- Aggregation query (in application or with Spark)
SELECT sensor_id,
AVG(temperature) as avg_temp,
MAX(temperature) as max_temp,
MIN(temperature) as min_temp
FROM readings_by_sensor
WHERE sensor_id = 'sensor-001'
AND date = '2024-01-15'
GROUP BY sensor_id;
7. Common Pitfalls and Troubleshooting (10 minutes)
Frequent Mistakes
-
Problem: Treating document databases like relational databases Cause: Using too many references and joins instead of embedding Solution: Design around query patterns; embed data that’s read together
-
Problem: Unbounded array growth in MongoDB Cause: Pushing to arrays without limits Solution: Use the bucket pattern or limit array sizes; archive old data
-
Problem: Hot partitions in Cassandra Cause: Poor partition key choice causing uneven data distribution Solution: Design partition keys to distribute data evenly; add time components if needed
-
Problem: Redis memory issues Cause: No TTL on cached data, keys never expire Solution: Always set TTL for cache data; monitor memory usage; use eviction policies
Debugging Tips
- MongoDB: Use
explain()to analyze query performance - Redis: Use
MEMORY USAGE keyandINFO memoryfor diagnostics - Cassandra: Use
TRACING ONto see query execution details - All: Monitor connection pools and timeouts in production
8. Best Practices and Professional Tips (5 minutes)
Industry Standards
- Design for queries: Know your access patterns before designing schemas
- Plan for growth: Consider data growth and query performance at scale
- Monitor everything: Track query latencies, memory, and connection counts
- Use connection pooling: Don’t create new connections for each request
- Implement retry logic: Handle transient failures gracefully
Database Selection Guide
| Use Case | Document | Key-Value | Column-Family |
|---|---|---|---|
| User Profiles | ✅ Best | ⚠️ Limited | ❌ Overkill |
| Caching | ⚠️ Overkill | ✅ Best | ❌ Not suited |
| Time-Series | ⚠️ Possible | ❌ Not suited | ✅ Best |
| Product Catalog | ✅ Best | ❌ Not suited | ⚠️ Possible |
| Session Storage | ⚠️ Possible | ✅ Best | ❌ Not suited |
| Write-Heavy Logs | ⚠️ Possible | ❌ Not suited | ✅ Best |
Next Steps in Your Learning Journey
- Advanced MongoDB: Aggregation pipeline, change streams, transactions
- Redis Cluster: High availability and horizontal scaling
- Cassandra Operations: Repair, compaction, backup strategies
- Polyglot Persistence: Combining multiple databases effectively
9. Summary and Review (5 minutes)
Key Takeaways
- Document databases (MongoDB) excel at flexible schemas and hierarchical data
- Key-value stores (Redis) provide sub-millisecond access for simple data patterns
- Column-family databases (Cassandra) handle high write throughput and time-series data
- Data modeling in NoSQL requires thinking about query patterns first, not data relationships
Quick Review Questions
-
When would you embed vs. reference in MongoDB? Answer: Embed for data accessed together, reference for large/frequently updated subdocuments or many-to-many relationships
-
What Redis data structure would you use for a leaderboard? Answer: Sorted Set (ZADD/ZRANGE) because it maintains order by score and supports efficient ranking queries
-
Why does Cassandra require you to design tables around queries? Answer: Because data is partitioned across nodes; you can only efficiently query by partition key, so each query pattern needs its own table
For Instructors
Teaching Tips
- Use live demos with Docker containers for each database
- Compare the same use case implemented in different database types
- Emphasize the importance of query pattern analysis
- Show production monitoring tools and metrics
Assessment Ideas
- Data modeling exercises for specific scenarios
- Query writing challenges across different databases
- Architecture design incorporating multiple databases
- Performance comparison analysis
Adaptation Notes
- For beginners: Focus on MongoDB and Redis before introducing Cassandra
- For advanced students: Include cluster setup, replication, and consistency tuning
- Online delivery: Provide Docker Compose files for easy environment setup
- Time constraints: Document databases can be covered in 45 minutes; include one NoSQL type per session