---
title: "11 — NoSQL Stores: Key-Value, Document, Wide-Column, and Graph"
uid: nosql-stores
tags: ["document", "nosql", "wide-column", "key-value", "graph", "databases", "roadmap:system-design", "system-design"]
excerpt: "Four NoSQL families — key-value, document, wide-column, graph — each optimized for a different data shape and access pattern. 'NoSQL' is four distinct data models, not one thing."
date: 2026-08-13T03:27:33+0000
source: https://www.aveshina.my.id/en/blog/nosql-stores
---

"Not SQL, so probably MongoDB" was my NoSQL model, and it lumped four different data models into one blob. Writing them down gave each family a distinct job: **the four NoSQL families — key-value, document, wide-column, and graph — each optimize for a different shape of data and access pattern, and the choice between them is driven by the data's structure and how you query it.** [1][2] "NoSQL" is not one thing; it is four distinct data models, and picking the wrong one quietly caps the system's performance.

The framing that landed is to classify each family by the question it answers best. Key-value answers "what is the value for this exact key?" Document answers "give me this whole object, with all its nested fields." Wide-column answers "give me a range of keys, fast, at enormous scale, with sparse data." Graph answers "what is connected to what, and how many hops between them?" [1][2]

## The four families

```figure
<svg viewBox="0 0 740 320" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Four NoSQL families in quadrants. Key-Value: a key maps to a value, O(1) lookup. Document: a nested JSON-like object with heterogeneous fields. Wide-Column: a sparse grid of column families keyed by row key. Graph: nodes connected by relationship edges, optimized for traversals.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- Key-Value -->
    <rect x="20" y="20" width="340" height="130" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="40" y="44" font-size="13" font-weight="700" fill="#1e1b4b">Key-Value</text>
    <text x="40" y="60" font-size="10" fill="#475569">instant lookups · simple models · caches</text>
    <g font-family="ui-monospace, monospace" font-size="11">
      <text x="50" y="90" fill="#1e1b4b">"user:42"   →  {name: "Ave"}</text>
      <text x="50" y="110" fill="#1e1b4b">"user:43"   →  {name: "Lin"}</text>
    </g>
    <text x="50" y="134" font-size="10" font-style="italic" fill="#475569">Redis, Memcached, DynamoDB</text>

    <!-- Document -->
    <rect x="380" y="20" width="340" height="130" rx="10" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="400" y="44" font-size="13" font-weight="700" fill="#052e16">Document</text>
    <text x="400" y="60" font-size="10" fill="#475569">self-contained objects · flexible schema</text>
    <g font-family="ui-monospace, monospace" font-size="10" fill="#052e16">
      <text x="400" y="85">{ id: 42,</text>
      <text x="412" y="100">name: "Ave",</text>
      <text x="412" y="115">tags: ["dev","writer"] }</text>
    </g>
    <text x="400" y="138" font-size="10" font-style="italic" fill="#475569">MongoDB, Couchbase</text>

    <!-- Wide-Column -->
    <rect x="20" y="170" width="340" height="130" rx="10" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="40" y="194" font-size="13" font-weight="700" fill="#422006">Wide-Column</text>
    <text x="40" y="210" font-size="10" fill="#475569">sparse · massive scale · range scans</text>
    <g font-size="10" fill="#422006">
      <text x="50" y="234">row</text>
      <text x="120" y="234">| profile | feed |</text>
      <text x="50" y="252">u1</text>
      <text x="120" y="252">|  ✓     |   ✓  |</text>
      <text x="50" y="270">u2</text>
      <text x="120" y="270">|  ✓     |   —  |</text>
    </g>
    <text x="50" y="292" font-size="10" font-style="italic" fill="#475569">Cassandra, HBase, Bigtable</text>

    <!-- Graph -->
    <rect x="380" y="170" width="340" height="130" rx="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="400" y="194" font-size="13" font-weight="700" fill="#500724">Graph</text>
    <text x="400" y="210" font-size="10" fill="#475569">relationships · many-to-many · traversals</text>
    <g>
      <circle cx="430" cy="240" r="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
      <circle cx="500" cy="260" r="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
      <circle cx="560" cy="230" r="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
      <line x1="438" y1="246" x2="492" y2="256" stroke="#db2777" stroke-width="1.5"/>
      <line x1="508" y1="256" x2="554" y2="236" stroke="#db2777" stroke-width="1.5"/>
      <line x1="438" y1="234" x2="554" y2="234" stroke="#db2777" stroke-width="1.5"/>
      <text x="430" y="276" font-size="9" fill="#500724" text-anchor="middle">node</text>
    </g>
    <text x="400" y="292" font-size="10" font-style="italic" fill="#475569">Neo4j, Dgraph</text>
  </g>
</svg>
```

- **Key-value store.** Generally allows O(1) reads and writes — a lookup takes about the same time no matter how many items are stored — and is often backed by memory or SSD. Keys can be maintained in sorted order for efficient range retrieval. High performance, ideal for simple data models or rapidly-changing data like an in-memory cache [1]. The trade-off: only a limited set of operations is offered, so any additional query complexity is pushed to the application layer.
- **Document store.** Centered on documents (JSON, XML, binary) where each document holds all the information for a given object. Documents can have completely different fields from one another, and they are organized by collections, tags, metadata, or directories. The store provides an API or query language that queries by the document's internal structure [2]. Good for self-contained objects with evolving schemas.
- **Wide-column store.** The basic unit is a column (a name/value pair). Columns group into column families (analogous to a SQL table), and super column families further group them. Each value carries a timestamp for versioning and conflict resolution. You access columns by row key, and keys are kept in sorted order for efficient selective range retrieval [3]. Google's Bigtable was the first; HBase and Cassandra followed. Optimized for massive scale with sparse data.
- **Graph database.** Each node is a record and each arc is a relationship between two nodes. Optimized to represent complex relationships — many foreign keys, many-to-many — with high performance for data models where the _connections_ are the point, like a social network [4]. Relatively new, with a smaller tooling ecosystem; some are accessed only via REST.

## How I choose

The decision is driven by two questions: what is the shape of one record, and how do I query it? If a record is a flat blob I look up by an exact key, key-value is the right shape and the fastest option. If a record is a rich, self-contained object with nested fields that evolve independently, document is the fit. If the data is enormous, sparse, and I need range scans across keys, wide-column is built for exactly that. If the relationships between records are themselves the query ("friends of friends," "shortest path," "who influences whom"), graph is the only family that answers those queries without catastrophic joins.

The mistake I try to avoid is reaching for a document store for _everything_ because it is the most familiar NoSQL flavor. A document store used as a poor man's key-value is overkill; a document store asked to traverse relationships becomes a slow join engine. Each family has a shape it was designed for, and staying inside that shape is what keeps the system fast as it grows.

## How I use this

When I model a new dataset, I name its shape out loud first — flat lookup, nested object, sparse-at-scale, or relationship-heavy — and that name points at a family before I think about specific products. For caches and session state, key-value (Redis). For content with evolving schemas (blog posts, product catalogs), document. For write-heavy time-series or enormous sparse datasets (event logs, user-activity feeds at scale), wide-column. For anything where "who is connected to whom" is the core query (recommendations, social graphs), graph. The discipline of naming the shape stops me from defaulting to the one NoSQL product I happen to know.

## References

[1] "Key–value database," Wikipedia. [Online]. Available: [https://en.wikipedia.org/wiki/Key%E2%80%93value_database](https://en.wikipedia.org/wiki/Key%E2%80%93value_database)

[2] "Document-oriented database," Wikipedia. [Online]. Available: [https://en.wikipedia.org/wiki/Document-oriented_database](https://en.wikipedia.org/wiki/Document-oriented_database)

[3] F. Chang et al., "Bigtable: a distributed storage system for structured data," 2006. [Online]. Available: [https://www.read.seas.harvard.edu/~kohler/class/cs239-w08/chang06bigtable.pdf](https://www.read.seas.harvard.edu/~kohler/class/cs239-w08/chang06bigtable.pdf)

[4] "Graph database," Wikipedia. [Online]. Available: [https://en.wikipedia.org/wiki/Graph_database](https://en.wikipedia.org/wiki/Graph_database)

[5] "Introduction to NoSQL," YouTube, 2019. [Video]. Available: [https://www.youtube.com/watch?v=qI_g07C_Q5I](https://www.youtube.com/watch?v=qI_g07C_Q5I)

```quiz
Q: You need instant key-based lookups for a rapidly-changing in-memory cache. The natural NoSQL family is…
- key-value
- graph
correct: 0
explain: Key-value stores offer near-instant reads and writes and are the standard for caches (Redis, Memcached). Graph is for relationship traversals.

Q: A wide-column store's main strength is…
- flexible, evolving per-document schemas
- massive scale with sparse data and efficient range scans across keys
correct: 1
explain: Wide-column stores (Cassandra, HBase, Bigtable) keep keys in lexicographic order for efficient range retrieval and handle sparse data at enormous scale.

Q: Which family is optimized for queries like "friends of friends" or shortest-path?
- document
- graph
correct: 1
explain: Graph databases represent each node as a record and each arc as a relationship, optimized for traversing many-to-many connections — exactly the queries that cripple other models with joins.

Q: A document store differs from a key-value store mainly because…
- documents can be queried by their internal structure, and two documents can have different fields
- document stores are always faster than key-value stores
correct: 0
explain: Documents are self-contained objects queryable by their nested structure, and documents in the same collection can have heterogeneous fields. Key-value stores offer only lookup by exact key.
```
