# Document Store

> Holds documents instead of rows. No joins and no connections to pool: one flat rate of reads and writes, and each query needs its own index.

The Document Store holds documents instead of rows, as MongoDB, Firestore and DynamoDB do.

## When to place it

- Place it when the schema keeps changing and features ship slowly because of it. A Feature that changes the data shape is the reason to own one, and the Team backlog shows it: its Dev Hours drop the moment the Store serves.
- Place it when the work is documents read and written by key: a profile, a cart, a settings page. It unlocks with [Document databases](https://wiki.playquorum.dev/research/document-db.md) in Series A, for 3,500 ◇.
- Skip it for reports. A report works, but badly, so link a [Data Warehouse](https://wiki.playquorum.dev/components/warehouse.md) instead, or leave reports on the Database, which still holds the money.
- Keep the money on the [Database](https://wiki.playquorum.dev/components/database.md) and the documents here.

## How it works

It trades joins and transactions for developer velocity, and it is the mirror image of the [Database](https://wiki.playquorum.dev/components/database.md): cheaper to change, worse at questions that span more than one record.

It serves `q-read` and `q-write` on documents, answers a checkout's `q-tx` and a report's `q-scan` too, and sends nothing on. It has one flat rate of **3,000 ops a second** at its first size, doubled at each of its eight sizes. A read or a write is one op. A join is three. Past its rate the excess is dropped, like any flat-capacity Node.

It differs from a [relational table](https://wiki.playquorum.dev/components/database.md) in three ways:

- **No joins.** A query that spans several documents is a scan of the collection, at [three times the ops](#a-join-costs-three-ops). A checkout that writes several documents is not atomic unless multi-document transactions are on, and with them every write waits for the rest, so its latency rises by half.
- **No connections to pool.** An App Server opens no pool it has to share through a [Connection Pooler](https://wiki.playquorum.dev/components/pooler.md), so a spike of App Servers cannot exhaust it the way PostgreSQL's 100 connections are exhausted. It has no buffer cache and no IOPS volume either, so no disk limit applies.
- **No schema to migrate.** A Feature that changes the data shape costs **25% fewer Dev Hours**, because there is no migration to write, run, or lock a table for. Every other Feature costs **10% more**, because with no joins each access pattern it adds is its own collection and index to write and read. The [Team](https://wiki.playquorum.dev/team.md) panel charges the total.

### A join costs three ops

A Feature that adds reads suits a Document Store, which answers them at one op each from its own flat rate. It cannot cheaply ask two documents about each other:

| What it answers | Ops it costs | Why |
| --- | --- | --- |
| A read of one document | 1 | A key lookup, or an index lookup on one collection. |
| A write of one document | 1 | One document is atomic. |
| A checkout (`q-tx`) | 3 | It reads the cart, writes the order and writes the receipt, in the app. |
| A report (`q-scan`) | 3 | It scans the whole collection, because there is no join to narrow it. |

A hot shard from incident [Hot key](https://wiki.playquorum.dev/incidents/hot-key.md) divides its rate, the same way it divides a [Wide-column Store](https://wiki.playquorum.dev/components/wide.md)'s.

## Options

- **Multi-document transactions**: they make a checkout atomic across documents, and every write waits for the rest, so its latency rises by half.

## How it fails

- **The join.** A checkout spread over documents loses atomicity without multi-document transactions: a retry can charge the card twice, and the card shows the duplicate. Turn them on, and pay half again the write latency.
- **The report.** A `q-scan` through a Document Store is a collection scan at three ops. It also holds the Collection's slots while it runs.
- **Hot keys.** One shard's key takes the traffic, and the Node's flat rate is the first thing that bends.
- **A Document Store is not a Database.** Nothing joins, nothing is transactional by default, and there is no point-in-time recovery to research. A Design that keeps its money here loses a charge now and then.

### [Instance failure](https://wiki.playquorum.dev/incidents/instance.md)

- **Signs:** The Document Store card goes grey and its callout reads "The host under this VM died. It still bills." Requests sent to it are stranded.
- **Do now:** Select the Node and click **Replace**. It costs Cash and comes back cold.
- **Prevent:** Run a second Node of the Component, so one is still serving while the other is replaced.

### [Zone outage](https://wiki.playquorum.dev/incidents/zone.md)

- **Signs:** Every Node in one zone goes grey at once, the Document Store with them.
- **Do now:** Nothing brings the zone back sooner. **Fail over** promotes a **Standby**, and this Component keeps none, so it ends on its own after at most 120 s.
- **Prevent:** Research [Multiple zones](https://wiki.playquorum.dev/research/multi-az.md) and keep a copy of the Component in a second zone.

### [Region outage](https://wiki.playquorum.dev/incidents/region.md)

- **Signs:** One region's board goes grey and the Document Store in it goes down.
- **Do now:** Nothing speeds it up. **Fail over** moves only Nodes that hold no data, so this one stays down until the incident ends: 180 sim seconds in the catalog, 60 once the error budget counts. A store spread across regions needs nothing from you.
- **Prevent:** Research [Multiple regions](https://wiki.playquorum.dev/research/multi-region.md), keep a copy in a second region, and add [GeoDNS](https://wiki.playquorum.dev/research/geo-dns.md) to send users there.

### [Operator typo](https://wiki.playquorum.dev/incidents/operator-typo.md)

- **Signs:** Every Document Store Node in one region goes down at once.
- **Do now:** Place a new Document Store in that region and link it. The incident ends once one Node of that Component is up again.
- **Prevent:** Research [Infrastructure as code](https://wiki.playquorum.dev/research/infra-as-code.md), so a plan shows what a change removes before it runs.

## Tips

- Link the App Server to both the Database and the Document Store, and each takes the kinds it accepts.
- Put a [Cache](https://wiki.playquorum.dev/components/cache.md) in front. Its hits never reach the Store at all, and the Store has no buffer cache, so its reads do not compete with one.

## Rules

- Each further copy costs 25% more than the one before.
- Selling it refunds 50% of what its last copy cost.
- It has 8 sizes. Each step doubles its capacity and its Burn.
- A new copy starts cold and is slow until it warms up.

## Works with

[Database](https://wiki.playquorum.dev/components/database.md), [Connection Pooler](https://wiki.playquorum.dev/components/pooler.md), [Cache](https://wiki.playquorum.dev/components/cache.md), [Data Warehouse](https://wiki.playquorum.dev/components/warehouse.md), [Wide-column Store](https://wiki.playquorum.dev/components/wide.md)

## Engineering terms

- [Hot key](https://wiki.playquorum.dev/glossary/hot-key.md)
- [Sharding](https://wiki.playquorum.dev/glossary/sharding.md)

## Facts

Document Store
- **Code:** doc
- **Real product:** MongoDB, Firestore
- **Group:** Data
- **Unlocked by:** [Document databases](https://wiki.playquorum.dev/research/document-db.md)
- **Era:** [Series A](https://wiki.playquorum.dev/eras/series-a.md)
- **Place:** $260 first copy
- **Burn:** $0.20/s
- **Sizes:** 8
- **Overhead:** 3 ms
- **Takes:** Read query, Write query, Transaction, Scan
- **Sends:** —
- **Cold start:** Yes

## See also

- [Document databases](https://wiki.playquorum.dev/research/document-db.md)
- [Components](https://wiki.playquorum.dev/components.md)

Source: https://wiki.playquorum.dev/components/doc
