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.