PrepZone Logo
PrepZone

Secondary Indexes at Scale

Global vs local indexes, write amplification, and query fan-out in distributed stores.

Why this matters

  • VaultCommerce's DynamoDB tables are keyed by user_id, but support agents search orders by tracking_number — that requires a global secondary index designed upfront.
  • Local indexes that fan out to every shard turn "simple" lookups into cluster-wide scatter-gather.
  • Interviews test GSI vs LSI trade-offs and why you cannot add arbitrary indexes after launch without cost.
Vertical
usersid, name, email
user_profilesbio, avatar
Horizontal
Shard Auser_id 0–999
Shard Buser_id 1000–1999
Vertical splits tables by column group. Horizontal splits rows across nodes by key.

Local vs global secondary indexes

Index types in distributed stores

  • Local secondary index (LSI) — alternate sort key within the same partition; no extra routing.
  • Global secondary index (GSI) — different partition key; spans the cluster like a shadow table.
  • Materialised view — precomputed query result maintained asynchronously (Cassandra, Postgres).
  • Inverted index — Elasticsearch's core structure for full-text lookup.
Java
Base table partition key: USER#42
  ├── ORDER#2026-001  (sort key)
  └── ORDER#2026-002

GSI partition key: TRACKING#1Z999
  └── points to ORDER#2026-001 on USER#42

VaultCommerce's tracking lookup hits the GSI partition TRACKING#1Z999 directly — O(1) routing. Without it, scanning all user partitions is impossible at scale.

Write amplification

Every GSI row duplicates write traffic. Updating an order status writes the base table and each GSI projection:

Java
order_update_cost:
  base_table: 1 WCU
  gsi_tracking: 1 WCU
  gsi_status_by_date: 1 WCU
  total: 3 WCU per status change

Design GSIs only for proven access patterns. VaultCommerce limits each table to three GSIs — each added only after query metrics justify the write tax.

Index typeRead costWrite costVaultCommerce example
LSISingle partitionIncluded in base writeOrders by date within user
GSISingle GSI partitionExtra write per indexLookup by tracking number
Scatter-gather localAll partitions queriedLowAnti-pattern — avoid
ElasticsearchInverted index lookupIndex on every doc changeProduct search
  • LSI

    Read costSingle partition
    Write costIncluded in base write
    VaultCommerce exampleOrders by date within user
  • GSI

    Read costSingle GSI partition
    Write costExtra write per index
    VaultCommerce exampleLookup by tracking number
  • Scatter-gather local

    Read costAll partitions queried
    Write costLow
    VaultCommerce exampleAnti-pattern — avoid
  • Elasticsearch

    Read costInverted index lookup
    Write costIndex on every doc change
    VaultCommerce exampleProduct search

Query fan-out and hot partitions

A poorly chosen GSI partition key concentrates traffic:

Java
GSI key: STATUS#SHIPPED  ← millions of orders share one partition
Result: hot partition, throttling, 503s during peak shipping

VaultCommerce shards high-cardinality GSI keys with a composite: STATUS#SHIPPED#2026-03-15 buckets by ship date — trading one query for many parallel lookups merged in application code.

Postgres secondary indexes at scale

Distributed concerns apply even on single-node Postgres when tables reach hundreds of millions of rows:

Java
-- Partial index: only active carts — smaller, faster
CREATE INDEX CONCURRENTLY idx_active_carts
  ON carts (user_id)
  WHERE status = 'active';

-- Covering index avoids heap lookups
CREATE INDEX idx_orders_user_covering
  ON orders (user_id, created_at DESC)
  INCLUDE (status, total);

CREATE INDEX CONCURRENTLY avoids blocking writes during index builds — mandatory for VaultCommerce's 400 GB orders table.

Index lifecycle discipline

Production index hygiene

  • Measure query frequency before creating GSIs or btree indexes.
  • Drop unused indexes — they slow every write silently.
  • Monitor index bloat and rebuild during low-traffic windows.
  • Prefer composite keys that match query sort order to avoid sort steps.

Quick recall

Everything you need if you only revisit this box.

  1. Secondary indexes enable alternate lookup paths at a write cost.
  2. Local indexes stay within a partition; global indexes route like separate tables.
  3. VaultCommerce uses GSIs for tracking-number lookup, LSIs for per-user date sorts.
  4. Every GSI duplicates writes — budget indexes against proven query volume.
  5. Avoid hot GSI partitions; bucket high-cardinality keys or redesign access patterns.

Test yourself

Answer these before moving on — recall is what makes it stick.