Knowledge Guide
HomeSystem DesignData Partitioning

hard Secondary Indexes in Partitioned Systems: local vs global

The primary key decides sharding — a secondary index needs its own decision

Partitioning splits data by the primary key: partition = f(id) tells you exactly where any row lives. But a query on a non-key attribute — find every car where color = red — can't use that function at all, because color was never part of the placement decision. A secondary index has to be built and, critically, placed somewhere across the partitions, and there are exactly two ways to place it: index each partition's own documents where they already sit (local), or partition the index itself by the indexed value (global). Which one you pick flips which side of every query — read or write — is fast, and which one fans out.

Local document-partitioned index: each partition holds its own docs and its own color index, so a write is one atomic partition but a read must scatter-gather all partitions. Global term-partitioned index: the index is partitioned by term, so a read hits one index partition, but a write fans out to the doc partition plus every touched index partition, asynchronously.
Local document-partitioned index: each partition holds its own docs and its own color index, so a write is one atomic partition but a read must scatter-gather all partitions. Global term-partitioned index: the index is partitioned by term, so a read hits one index partition, but a write fans out to the doc partition plus every touched index partition, asynchronously.

Two placement schemes, mechanism-first

Traced example: color=red over 6 partitions

Take 6 partitions P0…P5. Two of them, P1 and P4, happen to hold documents with color=red — "happen to," because documents are placed by their own id, with no relationship to color. Now run the query color=red and a write of one new red car under each scheme.

Traced example over 6 partitions. Local index: reading color=red asks all 6 partitions and merges, tail latency is the slowest of 6; writing a new red car lands in 1 partition atomically. Global index: the index is re-partitioned by term into IX0, IX1, IX2, with IX2 owning red; reading color=red asks only IX2; writing a new red car touches P3 (the doc partition) and IX2 (the term partition), asynchronously, with no cross-partition atomicity.
Traced example over 6 partitions. Local index: reading color=red asks all 6 partitions and merges, tail latency is the slowest of 6; writing a new red car lands in 1 partition atomically. Global index: the index is re-partitioned by term into IX0, IX1, IX2, with IX2 owning red; reading color=red asks only IX2; writing a new red car touches P3 (the doc partition) and IX2 (the term partition), asynchronously, with no cross-partition atomicity.
OperationLocal (document-partitioned)Global (term-partitioned)
Read color=redask all 6 partitions, merge — 6 requests, tail latency = slowest of 6ask only the partition owning term "red" (here IX2) — 1 request
Write a new red car1 partition (the doc's own, e.g. P3) — atomic with the write2+ partitions: P3 (the doc) and IX2 (the "red" term) — cross-partition, usually async

The asymmetry is the entire lesson: local pays the fan-out cost on every read of the secondary key, no matter how rare that query is. Global pays a smaller, bounded fan-out on every write that touches an indexed field, and in exchange gives every secondary-key read a single-partition, low-latency path. Neither scheme changes what the primary-key partitioning does — both still route by id for document storage; they differ only in where the color→doc mapping lives.

Pitfalls

Selection & trade-offs — how a senior engineer decides

Ask which side of the secondary-key traffic is heavier: writes on indexed fields, or reads by the secondary key? That single question almost always settles it, because the two schemes just move the fan-out cost from one side to the other — neither eliminates it.

SchemeChoose whenAvoid whenWrite costRead cost
Local (document-partitioned)write-heavy; secondary-key queries are rare, low-selectivity, or batch/analytical (search indexes, log stores)secondary-key point lookups are frequent and latency-sensitive1 partition, atomicN partitions, scatter-gather, tail-latency bound
Global (term-partitioned)read-heavy point lookups on the secondary key (e.g. "orders by customer_id" as a GSI)writes must be atomic/synchronous across the record and its indexes; index staleness is unacceptabledoc partition + every touched term partition, usually async1 (or few) partition(s)

There is a third, often-overlooked alternative worth naming: don't index at all — denormalize into a separate table/materialized view keyed directly by the value you query (a CQRS-style read model, e.g. a cars_by_color table populated by a stream). It buys the same single-partition read as a global index, without a shared "index" abstraction lagging behind — but you now own the duplication and the update pipeline yourself, and it has the same async-propagation hazard as a global index, just made explicit in your own code instead of hidden in the database. Crisp rule of thumb: write-heavy or rarely-queried secondary key → local (pay the scatter-gather only when it's actually invoked); read-heavy point lookups on a secondary key → global (pay a bounded write fan-out to make every such read cheap); if the caller needs read-your-own-write on that secondary key, treat the global index's lag as a hazard to design around — read from the primary path, poll, or accept the window — rather than assuming the database made it disappear.

Takeaways


Re-authored for this guide; both diagrams hand-authored as SVG. Source: Martin Kleppmann, Designing Data-Intensive Applications, ch. 6 (Partitioning and Secondary Indexes) — the document-partitioned vs term-partitioned distinction and the Elasticsearch/MongoDB/DynamoDB examples are drawn from that chapter. See also: Sharding Strategies (range vs hash vs directory vs geo).

🤖 Don't fully get this? Learn it with Claude

Stuck on Secondary Indexes in Partitioned Systems: local vs global? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Secondary Indexes in Partitioned Systems: local vs global** (System Design) and want to truly understand it. Explain Secondary Indexes in Partitioned Systems: local vs global from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Secondary Indexes in Partitioned Systems: local vs global** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Secondary Indexes in Partitioned Systems: local vs global** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Secondary Indexes in Partitioned Systems: local vs global** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes