Sparse indexes, overloading and sharding

Amazon DynamoDB · Advanced · 16 min

These patterns let a few indexes serve many access patterns and keep heavy traffic evenly spread.

A few patterns come up in nearly every serious DynamoDB design:

  • Sparse index: only items that have the index's key attribute are indexed. Set open_since only on open orders and a GSI on it lists exactly the open ones; remove the attribute when the order closes and it drops out. Small, cheap, and a natural work queue.
  • GSI overloading: give the index generic keys (GSI1PK, GSI1SK) and let each entity type put a different meaning in them: orders by STATUS#PENDING, users by EMAIL#.... One index then serves many patterns, which matters because each GSI adds write cost.
  • Inverted index: a GSI whose partition key is the table's sort key and vice versa, so many-to-many relationships can be read from both sides (students of a class, classes of a student).
  • Write sharding: if one key value gets too much traffic (a global leaderboard, today's date), append a random or calculated suffix DATE#2025-03-01#7 across N shards, and query all N shards in parallel when reading.

Also consider composite sort keys like COUNTRY#STATE#CITY so begins_with queries at any level work from one index.

Every pattern costs something: duplicated storage, extra writes, more complex reads. Add them for real access patterns only.

Example

import random
import boto3
from boto3.dynamodb.conditions import Key

table = boto3.resource("dynamodb").Table("App")   # GSI "GSI1" on (GSI1PK, GSI1SK)
SHARDS = 4

def record_vote(day, user):
    shard = random.randrange(SHARDS)               # spread one hot day over 4 keys
    table.put_item(Item={"PK": f"VOTE#{user}", "SK": day,
                         "GSI1PK": f"DAY#{day}#{shard}", "GSI1SK": user})

def votes_for(day):
    items = []
    for s in range(SHARDS):                        # read every shard
        items += table.query(IndexName="GSI1",
            KeyConditionExpression=Key("GSI1PK").eq(f"DAY#{day}#{s}"))["Items"]
    return items

for u in ["ann", "bob", "cy"]:
    record_vote("2025-03-01", u)
print(sorted(i["GSI1SK"] for i in votes_for("2025-03-01")))   # ['ann', 'bob', 'cy']

Remember

  • Sparse GSIs index only items that carry the key attribute
  • Overloaded generic GSI keys let one index serve many patterns
  • Inverted indexes read many-to-many relations from both sides
  • Write sharding spreads a hot key; reads fan out across shards

Watch out for

  • Sharding a key that is not actually hot, which only multiplies read requests.

Check yourself

  1. Why is a GSI on an attribute that only open orders have called a sparse index?

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/bp-indexes-general-sparse-indexes.html