Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Choosing the Right Database

The right tool for the right job

Choosing the right database is one of the most critical decisions in system design. The wrong choice can lead to performance problems, scaling issues, and development headaches.

Diagram
Diagram

Questions to Ask:

  • Is your data structured (tables) or unstructured (documents)?
  • Do you have fixed relationships or flexible structures?
  • Do you need to store nested/hierarchical data?

Diagram

Questions to Ask:

  • Do you need complex JOINs or simple lookups?
  • Are queries mostly by key or by complex conditions?
  • Do you need to traverse relationships?

Diagram

Questions to Ask:

  • How many records do you expect?
  • What’s your expected QPS (queries per second)?
  • Do you need horizontal scaling?

Diagram

Questions to Ask:

  • Do you need ACID transactions?
  • Can you tolerate eventual consistency?
  • Is data accuracy critical or is speed more important?

Use CaseData StructureQuery PatternScaleConsistencyRecommended
E-commerceStructuredComplex JOINsMediumStrongSQL (PostgreSQL)
Social MediaSemi-structuredSimple lookupsLargeEventualDocument DB (MongoDB)
CachingSimpleKey lookupLargeEventualKey-Value (Redis)
Social NetworkRelationshipsGraph queriesLargeEventualGraph DB (Neo4j)
Time-SeriesStructuredColumn queriesLargeEventualColumn-Family (Cassandra)
FinancialStructuredComplex queriesMediumStrongSQL (PostgreSQL)
Content ManagementSemi-structuredDocument queriesMediumEventualDocument DB (MongoDB)
Session StorageSimpleKey lookupLargeEventualKey-Value (Redis)

Understanding how major companies choose databases helps illustrate the decision process:

The Challenge: E-commerce platforms have diverse data needs: structured orders, flexible products, fast sessions, complex search.

The Solution: Amazon uses multiple databases:

  • PostgreSQL: User accounts, orders, payments (ACID transactions, complex queries)
  • DynamoDB: Product catalog, inventory (high scale, simple queries)
  • Elasticsearch: Product search (full-text search, faceted search)
  • Redis: Shopping cart, sessions (fast lookups, temporary data)

Why Multiple Databases?

  • Different data has different requirements
  • Right tool for each job
  • Optimized performance

Example: User shopping flow:

  1. Login → PostgreSQL (user account)
  2. Browse products → DynamoDB (product catalog)
  3. Search → Elasticsearch (product search)
  4. Add to cart → Redis (shopping cart)
  5. Checkout → PostgreSQL (order, payment)

Impact: Optimized for each use case. Fast performance. Handles millions of users.

The Challenge: Social media platforms need flexible content (posts vary) and relationship queries (friends, followers).

The Solution: Facebook uses hybrid approach:

  • MySQL: User accounts, core data (ACID transactions)
  • MongoDB: Posts, comments, content (flexible schema)
  • Neo4j: Friend relationships, recommendations (graph traversals)
  • Cassandra: Messages, notifications (time-series, high scale)

Why Hybrid?

  • Posts have flexible structure → Document DB
  • Friend relationships → Graph DB
  • Messages over time → Column-Family DB

Impact: Handles billions of users. Fast relationship queries. Flexible content storage.

The Challenge: Financial platforms need strong consistency, complex queries, and ACID transactions.

The Solution: Banks use SQL databases:

  • PostgreSQL/Oracle: All financial data (accounts, transactions, balances)
  • Why SQL? ACID transactions, complex queries, strong consistency

Example: Money transfer:

  • Begin transaction
  • Debit account A
  • Credit account B
  • Commit transaction
  • All or nothing (atomicity)

Impact: Zero data loss. Strong consistency. Critical for financial systems.

The Challenge: Content platforms store articles, blog posts, user-generated content with varying structures.

The Solution: Medium uses MongoDB:

  • MongoDB: Articles, posts, user profiles (flexible schema)
  • Why Document DB? Content varies, nested data, no JOINs needed

Example: Article document:

{
"id": 123,
"title": "Article",
"author": {...},
"content": "...",
"tags": [...],
"comments": [...]
}

Impact: Flexible content storage. Fast reads. Scales horizontally.



Diagram

Why SQL?

  • Structured product/order/user data
  • Need complex queries (reports, analytics)
  • ACID transactions for checkout
  • Relationships between entities

Diagram

Why Document DB?

  • Flexible post structure (text, images, videos)
  • Simple queries (get user’s posts)
  • Need to scale horizontally
  • Fast reads more important than complex queries

Diagram

Why Graph DB?

  • Complex relationships (users, products, purchases)
  • Need to traverse relationships
  • “Find similar users” queries
  • Relationship queries are primary use case

Polyglot persistence means using multiple database types in the same system. Different parts use different databases optimized for their needs.

Diagram

Example: E-commerce platform

  • PostgreSQL: Orders, payments, inventory (structured, ACID)
  • MongoDB: Product catalogs, reviews (flexible schema)
  • Redis: Shopping cart, sessions, cache (fast lookups)

How database choice affects your class design:

Database Abstraction
from abc import ABC, abstractmethod
from typing import Optional
class UserRepository(ABC):
"""Abstract repository - database agnostic"""
@abstractmethod
def find_by_id(self, user_id: int) -> Optional['User']:
pass
@abstractmethod
def save(self, user: 'User') -> 'User':
pass
class SQLUserRepository(UserRepository):
"""SQL implementation"""
def __init__(self, db_connection):
self.db = db_connection
def find_by_id(self, user_id: int) -> Optional['User']:
# SQL query
cursor = self.db.execute("SELECT * FROM users WHERE id = ?", (user_id,))
row = cursor.fetchone()
return User.from_row(row) if row else None
def save(self, user: 'User') -> 'User':
# SQL insert/update
self.db.execute(
"INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
(user.id, user.name, user.email)
)
return user
class MongoDBUserRepository(UserRepository):
"""MongoDB implementation"""
def __init__(self, mongo_collection):
self.collection = mongo_collection
def find_by_id(self, user_id: int) -> Optional['User']:
# MongoDB query
doc = self.collection.find_one({"_id": user_id})
return User.from_document(doc) if doc else None
def save(self, user: 'User') -> 'User':
# MongoDB insert/update
self.collection.replace_one(
{"_id": user.id},
user.to_document(),
upsert=True
)
return user


Now that you understand database selection, let’s dive deep into indexing strategies to optimize database performance:

Next up: Database Indexing Strategies — Learn about B-trees, LSM trees, and inverted indexes for designing searchable entities.