What Is NoSQL?#

NoSQL (“Not Only SQL”) databases are non-relational systems designed for specific data models, access patterns, and scale requirements that relational databases handle poorly or inefficiently.

NoSQL isn’t better or worse than SQL — it’s different tradeoffs for different problems.


When to Use NoSQL vs SQL#

Choose SQL (Relational) WhenChoose NoSQL When
Data has clear relationships and schemaSchema varies between records
Complex queries with JOINs neededAccess patterns are simple (get by key)
ACID transactions are criticalEventual consistency is acceptable
Data integrity is paramountHorizontal scalability is priority
You need ad-hoc analytical queriesYou need low-latency at massive scale
Data fits neatly in tablesData is hierarchical, graph-shaped, or time-series

NoSQL Categories#

Document Stores#

Store data as flexible JSON/BSON documents. Each document can have different fields.

Examples: MongoDB, CouchDB, Firestore, Amazon DocumentDB

// MongoDB document — no fixed schema
{
    "_id": "ObjectId('...')",
    "name": "Alice Chen",
    "email": "alice@example.com",
    "addresses": [
        {"type": "home", "city": "Seattle", "zip": "98101"},
        {"type": "work", "city": "Redmond", "zip": "98052"}
    ],
    "orders": 47,
    "tags": ["premium", "early-adopter"]
}

Best for: Content management, user profiles, product catalogs, any data with variable structure.

Tradeoffs: No JOINs (denormalize or embed), eventual consistency, limited transaction support (improving).


Key-Value Stores#

Simplest model: a key maps to a value (string, JSON, binary blob). Extremely fast for simple lookups.

Examples: Redis, Amazon DynamoDB, Memcached, etcd

Key: "session:abc123"     → Value: {"user_id": 42, "expires": "2024-01-15T10:00:00Z"}
Key: "cache:user:42"      → Value: {"name": "Alice", "email": "alice@example.com"}
Key: "rate:192.168.1.1"   → Value: "47"

Best for: Caching, sessions, rate limiting, feature flags, leaderboards, real-time counters.

Tradeoffs: No queries beyond key lookup (some offer secondary indexes), no relationships.


Column-Family Stores#

Organize data by columns rather than rows. Optimized for writing and reading large datasets where you only need certain columns.

Examples: Apache Cassandra, HBase, ScyllaDB

Row Key: "user:42"
    Column Family: "profile"
        name: "Alice"
        email: "alice@example.com"
    Column Family: "activity"
        last_login: "2024-01-15"
        login_count: "347"

Best for: Time-series, event logging, IoT data, high write throughput, data that’s written once and read by time range.

Tradeoffs: Limited query flexibility, no JOINs, eventual consistency, complex data modeling.


Graph Databases#

Store entities (nodes) and their relationships (edges) as first-class citizens. Traverse relationships in constant time.

Examples: Neo4j, Amazon Neptune, ArangoDB, JanusGraph

(Alice)-[:WORKS_IN]->(Engineering)
(Alice)-[:MANAGES]->(Bob)
(Alice)-[:KNOWS]->(Carol)
(Bob)-[:WORKS_ON]->(Project_X)
(Carol)-[:WORKS_ON]->(Project_X)

Best for: Social networks, recommendation engines, fraud detection, knowledge graphs, network analysis.

Tradeoffs: Not suited for bulk analytics, smaller ecosystem, steep learning curve (Cypher/Gremlin query languages).


Time-Series Databases#

Optimized for timestamped data points — metrics, events, IoT readings.

Examples: InfluxDB, TimescaleDB (PostgreSQL extension), Prometheus, Amazon Timestream

measurement: cpu_usage
tags: host=server1, region=us-east
time: 2024-01-15T10:00:00Z, value: 72.5
time: 2024-01-15T10:00:05Z, value: 68.3
time: 2024-01-15T10:00:10Z, value: 74.1

Best for: Monitoring, IoT, financial tick data, analytics over time windows.

Tradeoffs: Not for general-purpose queries, specialized query languages, retention-based storage.


The CAP Theorem#

In a distributed system, you can only guarantee two out of three:

PropertyMeaning
ConsistencyEvery read returns the most recent write
AvailabilityEvery request gets a response (even if stale)
Partition ToleranceSystem continues despite network splits between nodes

Since network partitions are unavoidable in distributed systems, the real choice is:

  • CP (Consistency + Partition Tolerance): Returns error or timeout during partition rather than stale data. Examples: MongoDB (primary reads), HBase, etcd.
  • AP (Availability + Partition Tolerance): Always responds, but data might be stale during partition. Examples: Cassandra, DynamoDB, CouchDB.
flowchart TD
    CAP["CAP Theorem"]
    CP["CP Systems<br/>(Consistent + Partition Tolerant)<br/>MongoDB, HBase, etcd"]
    AP["AP Systems<br/>(Available + Partition Tolerant)<br/>Cassandra, DynamoDB, CouchDB"]
    CA["CA Systems<br/>(Consistent + Available)<br/>Traditional RDBMS (single node)"]
    
    CAP --> CP
    CAP --> AP
    CAP --> CA

DynamoDB (Key-Value / Document Hybrid)#

AWS’s flagship NoSQL database — common in cloud-native applications:

# Single-table design pattern
{
    "PK": "USER#42",
    "SK": "PROFILE",
    "name": "Alice",
    "email": "alice@example.com"
}
{
    "PK": "USER#42",
    "SK": "ORDER#2024-001",
    "total": 99.99,
    "status": "shipped"
}
FeatureDynamoDBPostgreSQL
ScaleAutomatic, unlimitedVertical (or complex sharding)
PricingPer request or provisioned capacityPer instance hour
QueriesBy key only (+ GSI)Any column, JOINs, subqueries
SchemaFlexible per itemFixed per table
TransactionsLimited (25 items max)Full ACID

Redis (In-Memory Key-Value)#

Ultra-fast data structure store — commonly used alongside a primary database:

Data StructureUse CaseCommands
StringsCache, countersSET, GET, INCR
HashesObjects, sessionsHSET, HGET, HGETALL
ListsQueues, feedsLPUSH, RPOP, LRANGE
SetsTags, unique itemsSADD, SMEMBERS, SINTER
Sorted SetsLeaderboards, rankingsZADD, ZRANGE, ZRANK
StreamsEvent logsXADD, XREAD
# Caching pattern
SET cache:user:42 '{"name":"Alice"}' EX 3600    # Expires in 1 hour
GET cache:user:42

# Rate limiting
INCR rate:192.168.1.1
EXPIRE rate:192.168.1.1 60                       # 60-second window

Polyglot Persistence#

Modern systems often use multiple databases, each for its strength:

flowchart LR
    APP["Application"]
    PG["PostgreSQL<br/>(source of truth)"]
    REDIS["Redis<br/>(cache + sessions)"]
    ES["Elasticsearch<br/>(full-text search)"]
    S3["S3<br/>(file storage)"]
    
    APP --> PG
    APP --> REDIS
    APP --> ES
    APP --> S3
ConcernDatabaseWhy
User data, orders, transactionsPostgreSQLACID, relationships, complex queries
Sessions, cachingRedisSub-millisecond latency
Full-text searchElasticsearchInverted indexes, fuzzy matching
Files, imagesS3 / object storageCheap, scalable blob storage
Metrics, monitoringInfluxDB / TimescaleDBTime-series optimized
RecommendationsNeo4jGraph traversal

Key Takeaways#

  1. NoSQL is not a replacement for SQL — it’s an alternative for specific access patterns and scale requirements
  2. Start with PostgreSQL unless you have a measured reason for NoSQL — it handles most workloads well
  3. CAP theorem forces a choice: during network issues, do you prefer consistency or availability?
  4. DynamoDB is excellent for known access patterns at scale — terrible for ad-hoc queries
  5. Redis is almost always used alongside another database, not as the primary store
  6. Polyglot persistence is the norm in production — use each database for its strengths
  7. Schema flexibility in document stores sounds freeing but requires discipline — schema-on-read means validation moves to application code