Global and local secondary indexes

Amazon DynamoDB · Practical · 14 min

Most real tables need more than one access pattern, and indexes are how DynamoDB serves them.

A secondary index is an alternate key over the same items, maintained automatically, that you Query (or Scan) with IndexName=....

A global secondary index (GSI) has its own partition key and optional sort key, any attributes you like. It is effectively a separate table that DynamoDB keeps in sync asynchronously:

  • reads are eventually consistent only
  • it has its own capacity (on provisioned tables, under-provisioning a GSI throttles writes to the base table)
  • you can add or delete it at any time; default limit 20 per table
  • keys do not have to be unique in an index

A local secondary index (LSI) keeps the table's partition key but uses a different sort key. It must be created with the table, supports strongly consistent reads, and imposes a 10 GB limit per item collection. Up to 5 per table. Prefer GSIs unless you need strong consistency on the alternate sort order.

Projections decide which attributes are copied into the index: KEYS_ONLY, INCLUDE (a list) or ALL. Querying an index only returns projected attributes; wider projections cost more storage and write capacity.

Only items that have the index key attributes appear in the index. That is a feature: it enables sparse indexes, covered later.

Example

import boto3
from boto3.dynamodb.conditions import Key

client = boto3.client("dynamodb")
client.update_table(                      # add a GSI to an existing table
    TableName="Orders",
    AttributeDefinitions=[{"AttributeName": "status", "AttributeType": "S"},
                          {"AttributeName": "order_id", "AttributeType": "S"}],
    GlobalSecondaryIndexUpdates=[{"Create": {
        "IndexName": "status-index",
        "KeySchema": [{"AttributeName": "status", "KeyType": "HASH"},
                      {"AttributeName": "order_id", "KeyType": "RANGE"}],
        "Projection": {"ProjectionType": "INCLUDE", "NonKeyAttributes": ["total"]},
    }}],
)
# ...once the index is ACTIVE:
table = boto3.resource("dynamodb").Table("Orders")
pending = table.query(IndexName="status-index",
                      KeyConditionExpression=Key("status").eq("PENDING"))
print([i["order_id"] for i in pending["Items"]])

Remember

  • GSIs: any key, added anytime, eventually consistent, own capacity
  • LSIs: same partition key, other sort key, created with the table, 10 GB per collection
  • Projections control which attributes an index returns
  • Items without the index key are simply absent from the index

Watch out for

  • Reading from a GSI immediately after a write and expecting to see the new item.

Check yourself

  1. Which statement about global secondary indexes is true?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html