PrepZone Logo
PrepZone

DynamoDB GSI and LSI

Global and local secondary indexes — alternate access paths without full table scans.

Read these first

Why this matters

  • VaultCommerce needs orders by user (primary key) and orders by status for ops dashboards (GSI) — without scanning the table nightly.
  • GSIs replicate data asynchronously — expect eventual consistency and plan for write amplification on hot items.
  • LSIs must be defined at create time and share the 10 GB per-partition limit with the base table — rarely added later.
Partition key: USER#42Routes to shard
Items in partition
ORDER#2026-001Sort key
ORDER#2026-002Sort key
PROFILESort key
Partition key routes to a shard. Sort key orders items within that partition.

Global Secondary Index

A GSI has its own partition and sort key drawn from table attributes:

Java
aws dynamodb update-table \
  --table-name VaultCommerceOrders \
  --attribute-definitions \
    AttributeName=GSI1PK,AttributeType=S \
    AttributeName=GSI1SK,AttributeType=S \
  --global-secondary-index-updates '[{
    "Create": {
      "IndexName": "GSI1-StatusDate",
      "KeySchema": [
        { "AttributeName": "GSI1PK", "KeyType": "HASH" },
        { "AttributeName": "GSI1SK", "KeyType": "RANGE" }
      ],
      "Projection": { "ProjectionType": "INCLUDE", "NonKeyAttributes": ["totalAmount","customerName"] },
      "ProvisionedThroughput": { "ReadCapacityUnits": 5, "WriteCapacityUnits": 5 }
    }
  }]'

VaultCommerce writes GSI1PK=STATUS#SHIPPED, GSI1SK=2026-04-01T10:00:00Z on each order update so ops queries pending shipments by status.

GSI vs LSI

  • GSI — new partition key; sparse indexes OK; add after creation; eventually consistent reads only.
  • LSI — same partition key, alternate sort key; must exist at table create; supports strong reads on same partition.
  • Projection — ALL, KEYS_ONLY, or INCLUDE specific attributes — trade storage vs query efficiency.

Querying a GSI

Java
from boto3.dynamodb.conditions import Key

table.query(
    IndexName="GSI1-StatusDate",
    KeyConditionExpression=Key("GSI1PK").eq("STATUS#SHIPPED")
    & Key("GSI1SK").between("2026-04-01", "2026-04-02"),
)

Each GSI consumes its own WCU on every base-table write that touches projected attributes — budget for dual writes during peak checkout.

Local Secondary Index example

For a table keyed PK=USER#id, SK=ORDER#date, an LSI LSI1-SK=AMOUNT#... lets you sort the same user's orders by amount without a GSI — but only within that user's partition.

Java
# Defined only at CreateTable time
"LocalSecondaryIndexes": [{
  "IndexName": "LSI1-Amount",
  "KeySchema": [
    { "AttributeName": "PK", "KeyType": "HASH" },
    { "AttributeName": "AmountSK", "KeyType": "RANGE" }
  ],
  "Projection": { "ProjectionType": "ALL" }
}]

VaultCommerce prefers GSIs for ops cross-partition queries; LSIs when multiple sort orders per user partition are known at design time.

Quick recall

Everything you need if you only revisit this box.

  • GSI: alternate partition+sort keys; add anytime; separate throughput; eventual reads.
  • LSI: same partition key, different sort key; create-time only; strong reads possible.
  • Project only attributes queries need — INCLUDE saves storage vs ALL.
  • GSI writes add WCU overhead — every base update may touch multiple indexes.
  • VaultCommerce GSI queries orders by shipment status for ops dashboards.
  • Sparse index keys keep rarely queried states out of the index.

Test yourself

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