---
title: "NoSQL Databases"
description: "Key terms across the four NoSQL data models: document, key-value, wide-column and graph. Covers partitioning and sharding, consistency models, CAP and PACELC, denormalisation, single-table design, and the managed AWS services for each model."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/glossary/post/nosql-databases
---

# NoSQL Databases

Choosing a NoSQL database is not one decision, it is two: which data model, then which consistency behaviour. Getting the first wrong is expensive because in most of these stores the access pattern is baked into the key design, and changing it means a migration rather than an index.

Worth saying plainly before the terms: a single PostgreSQL instance handles far more than most applications will ever need, and gives you joins, constraints and real transactions for nothing. "NoSQL scales" is not a reason on its own. The good reasons are a specific access pattern that a relational model serves badly, or a write volume no single primary can absorb.

_The four models, their AWS services, and the shared trade-off_

Two terms below are worth reading first because everything else leans on them. **Partition key** is the most consequential design decision in a key-value or wide-column store, since it decides both where data lives and which queries are possible. And **hot partition** is what happens when you get it wrong, which is a failure that no amount of provisioned capacity fixes.

## Terms

### NoSQL (Foundations)

A family of databases that do not use the relational model as their primary structure. The name is misleading in two ways: several of them speak a SQL-like query language, and the meaningful difference is not the query syntax but that they trade joins, ad-hoc querying and multi-row transactions for horizontal scale and flexible shape.

**Example:** The four common models are key-value, document, wide-column and graph. They have less in common with each other than any of them has with PostgreSQL.

### Key-value store (Models)

The simplest model. A key maps to an opaque value, and the only efficient operation is lookup by that key. Extremely fast and predictable, with no way to query the value's contents.

**Example:** DynamoDB, Redis, ElastiCache. Session storage, feature flags and caches fit naturally.

### Document store (Models)

Stores self-describing documents, usually JSON or BSON, and can index and query fields inside them including nested ones. Documents in a collection need not share a shape.

**Example:** MongoDB, Amazon DocumentDB, Couchbase. A good fit when an entity is read and written as a whole unit.

```
// one document holds what would be three relational tables
{
  "_id": "order-1001",
  "customer": {"id": "c-7", "name": "Ada"},
  "items": [
    {"sku": "A1", "qty": 2, "price": 9.99},
    {"sku": "B4", "qty": 1, "price": 24.50}
  ],
  "total": 44.48
}
```

### Wide-column store (Models)

Rows live in partitions, and within a partition rows are ordered by clustering columns. Each row can hold a different set of columns. Optimised for writing a lot and reading ranges within one partition.

**Example:** Cassandra, Amazon Keyspaces, HBase. Time-series and event logs suit it because "all events for device X between two timestamps" is one contiguous read.

### Graph database (Models)

Stores nodes and edges as first-class things, so traversing a relationship is a pointer hop rather than a join. Query cost scales with the neighbourhood you walk rather than the size of the dataset.

**Example:** Neo4j, Amazon Neptune. "Friends of friends who bought this product" is a short traversal and a punishing SQL query.

### Schema-on-read (Foundations)

The database accepts documents without validating a shape, so structure is interpreted when the data is read. This does not remove the schema, it moves it into your application code where nothing enforces it.

**Example:** Two years of documents where `phone` is sometimes a string, sometimes an object, and sometimes absent. The database was fine with all three.

### Partition key (Partitioning)

The attribute whose hash decides which physical partition a row lives on. It determines both where data is stored and which queries can be served efficiently, which makes it the most consequential design decision in a key-value or wide-column store.

**Example:** In DynamoDB you can only query efficiently by partition key. Choosing `status` as a partition key gives you about five possible values and therefore about five hot partitions.

### Sort key (clustering key) (Partitioning)

A second key component that orders items within a partition, enabling range queries and prefix matches inside that partition.

**Example:** Partition key `userId`, sort key `createdAt` lets you fetch one user's most recent orders in a single query without scanning.

```
# all orders for a user in a date range: one query, no scan
table.query(
    KeyConditionExpression=Key("pk").eq("USER#7")
    & Key("sk").between("ORDER#2026-01", "ORDER#2026-06")
)
```

### Hot partition (Partitioning)

A partition receiving a disproportionate share of traffic because the partition key has low cardinality or skewed access. One node saturates while the rest of the cluster idles, so total provisioned capacity is irrelevant.

**Example:** Using the current date as a partition key means every write today lands on one partition. Adding a random suffix (write sharding) spreads it at the cost of needing several queries to read a day back.

### Sharding vs replication (Partitioning)

Sharding splits different data across nodes, so it adds capacity. Replication copies the same data to several nodes, so it adds availability and read throughput. They solve different problems and most systems use both.

**Example:** A Cassandra cluster shards by partition key and replicates each partition three times.

### Consistent hashing (Partitioning)

A hashing scheme that maps keys onto a ring of nodes so that adding or removing a node only moves the keys adjacent to it, rather than remapping everything.

**Example:** It is what lets a cluster be resized without a full reshuffle. Naive `hash(key) % nodeCount` remaps almost every key when the node count changes.

### Eventual consistency (Consistency)

A read may return a stale value, but in the absence of new writes all replicas converge. The staleness window is usually milliseconds and is not bounded by any guarantee.

**Example:** DynamoDB eventually consistent reads cost half a strongly consistent read. That is the trade priced explicitly.

### Strong consistency (Consistency)

A read always reflects the most recent committed write. In a distributed store this requires coordination, which costs latency and reduces availability during a partition.

**Example:** DynamoDB offers strongly consistent reads on a table, but not on a global secondary index, which is a limitation people discover late.

### Read-your-own-writes (Consistency)

A guarantee that a client which just wrote a value will see it on a subsequent read, even if other clients might not yet. Weaker than strong consistency and often exactly what a user interface needs.

**Example:** A user updates their profile and immediately reloads the page. Without this guarantee they see the old name and assume the save failed.

### CAP theorem (Consistency)

During a network partition a distributed store can preserve consistency or availability, not both. The common framing as "pick two of three" is misleading. Partition tolerance is not optional in a distributed system, so the real choice is what to do when one occurs.

**Example:** "CA" describes a single-node database. Once data spans machines, partitions happen whether or not your design acknowledges them.

### PACELC (Consistency)

An extension of CAP that covers the normal case. If there is a Partition, choose Availability or Consistency; Else, in healthy operation, choose Latency or Consistency. This describes the trade you feel daily, which CAP does not.

**Example:** DynamoDB in its default mode is PA/EL: available under partition, and low-latency over strictly consistent when healthy.

### Quorum (Consistency)

Requiring acknowledgement from a majority of replicas. With replication factor N, writes to W and reads from R replicas give strong consistency whenever R + W > N.

**Example:** N=3, W=2, R=2 satisfies 2+2>3, so a read always overlaps at least one replica that saw the latest write. N=3, W=1, R=1 does not.

### Tunable consistency (Consistency)

Choosing the consistency level per operation rather than per database, so a critical read can be strict while a cheap one is not.

**Example:** Cassandra levels include ONE, QUORUM, LOCAL_QUORUM and ALL. Analytics might read at ONE; a balance check reads at QUORUM.

### Denormalisation (Modelling)

Deliberately duplicating data so a read needs one request instead of several. In NoSQL this is the default design approach rather than an optimisation, because there is no join to fall back on.

**Example:** Storing the author's name on every post means renaming an author requires updating every post. That write cost is accepted in exchange for a single-request read.

### Single-table design (Modelling)

A DynamoDB pattern that stores several entity types in one table, using generic key names and prefixed values so that related items share a partition and can be fetched together in one query.

**Example:** A `USER#7` partition holding the profile item plus that user's order items, so one query returns everything needed for a page.

```
pk          sk                  type     data
USER#7      PROFILE             user     {"name":"Ada"}
USER#7      ORDER#2026-03-01    order    {"total":44.48}
USER#7      ORDER#2026-04-11    order    {"total":12.00}
# one query on pk=USER#7 returns the profile AND the orders
```

### Adjacency list pattern (Modelling)

Modelling many-to-many relationships in a key-value store by writing an item per relationship in both directions, so either side can be queried efficiently.

**Example:** For "students in a course" and "courses for a student", write both `COURSE#1 / STUDENT#7` and `STUDENT#7 / COURSE#1`.

### Secondary index (GSI and LSI) (Modelling)

An alternative key structure enabling queries the primary key cannot serve. A local secondary index shares the partition key and adds a different sort key; a global secondary index uses an entirely different partition key and is maintained asynchronously.

**Example:** A GSI is eventually consistent and consumes its own write capacity, so every write to the base table costs extra. They are not free indexes.

### Write amplification (Modelling)

One logical write causing several physical writes, because of secondary indexes, replication, denormalised copies, or log-structured storage compaction.

**Example:** A table with three GSIs costs roughly four writes per logical write. This is where a DynamoDB bill quietly diverges from an estimate.

### Item collection (Modelling)

All items sharing a partition key. Because they live on the same partition, they can be read in one query, which is the mechanism single-table design relies on.

**Example:** In DynamoDB an item collection with a local secondary index is capped at 10 GB, a limit that only appears once a single tenant grows large.

### Scan vs query (Operations)

A query uses the key structure to read only matching items. A scan reads every item in the table and filters afterwards, so it costs capacity proportional to table size regardless of how few items match.

**Example:** A filter expression on a scan does not reduce cost; it discards items after they have been read and paid for. Finding a scan in a hot path is usually a modelling bug.

### Conditional write (Operations)

A write applied only if a condition on the current item holds, evaluated atomically by the database. The basis for optimistic locking and idempotency in a store with no transactions.

**Example:** `attribute_not_exists(pk)` makes an insert idempotent. The second attempt fails instead of overwriting.

```
table.put_item(
    Item={"pk": key, "state": "processing"},
    ConditionExpression="attribute_not_exists(pk)",
)
# ConditionalCheckFailedException means someone else got there first
```

### Change stream (Operations)

An ordered log of modifications to a table, consumable by other systems. Turns a database into an event source without dual writes.

**Example:** DynamoDB Streams triggering a Lambda to update a search index. MongoDB calls the equivalent change streams.

### TTL (time to live) (Operations)

A per-item expiry attribute the database uses to delete data automatically in the background, at no request cost.

**Example:** DynamoDB TTL deletes are not immediate; items can linger for up to 48 hours after expiry, so filter on the timestamp in queries rather than assuming absence.

### Last-writer-wins (Conflict handling)

A conflict resolution rule where the write with the latest timestamp overwrites the other. Simple, and it silently discards one of the two updates.

**Example:** The default for DynamoDB global tables. Acceptable for a display name, not for a balance.

### Vector clock (Conflict handling)

Per-node counters attached to a value so that concurrent updates can be detected rather than silently ordered by timestamp. The database surfaces siblings and the application decides.

**Example:** Riak exposed this directly. It is more correct than last-writer-wins and pushes real work into the client.

### CRDT (Conflict handling)

A conflict-free replicated data type, structured so that concurrent operations commute and therefore converge without coordination or conflict resolution.

**Example:** A counter using atomic increments rather than read-modify-write: two nodes each adding 1 always end at +2, whatever order the updates arrive in.

### Compaction (Storage internals)

The background process in a log-structured store that merges files and discards superseded or deleted records. Reclaims space and keeps read amplification down, at the cost of periodic I/O.

**Example:** A Cassandra node showing latency spikes on a schedule is usually compacting. Tombstones accumulating faster than compaction clears them is its own well-known failure mode.

### LSM tree (Storage internals)

A log-structured merge tree. Writes go to an in-memory table and a sequential log, then flush to sorted files that later merge. Makes writes fast and sequential, at the cost of a read possibly checking several files.

**Example:** The storage engine behind Cassandra, RocksDB and DynamoDB. It is the structural reason these stores absorb write-heavy workloads well.

### Tombstone (Storage internals)

A marker recording that a row was deleted, needed because the data may still exist in older files on other replicas. Deletes therefore add data before they remove it.

**Example:** Reading a partition with a very large number of tombstones can be slower than reading live data, and is a classic cause of Cassandra query timeouts.

### Bloom filter (Storage internals)

A compact probabilistic structure answering "is this key definitely absent, or possibly present". Lets a read skip files that cannot contain the key.

**Example:** False positives cost an unnecessary file read; false negatives are impossible. That asymmetry is what makes it safe.
