Global Secondary Indexes
GSIs let you query by attributes other than the table's primary key. Define them as class attributes on your Model.
Define a GSI
from pydynox import Model, ModelConfig, GlobalSecondaryIndex
from pydynox.attributes import StringAttribute, NumberAttribute
class User(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(hash_key=True)
sk = StringAttribute(range_key=True)
email = StringAttribute()
status = StringAttribute()
age = NumberAttribute()
# GSI with hash key only
email_index = GlobalSecondaryIndex(
index_name="email-index",
hash_key="email",
)
# GSI with hash and range key
status_index = GlobalSecondaryIndex(
index_name="status-index",
hash_key="status",
range_key="pk",
)
Query a GSI
Use the index attribute to query:
# Query by email
users = User.email_index.query(email="john@example.com")
for user in users:
print(user.name)
# Query by status
active_users = User.status_index.query(status="active")
for user in active_users:
print(user.email)
Range key conditions
When your GSI has a range key, you can add conditions:
# Query active users with pk starting with "USER#"
users = User.status_index.query(
status="active",
range_key_condition=User.pk.begins_with("USER#"),
)
# Query with comparison
users = User.status_index.query(
status="active",
range_key_condition=User.pk >= "USER#100",
)
Filter conditions
Filter non-key attributes after the query:
# Query active users over 30
users = User.status_index.query(
status="active",
filter_condition=User.age >= 30,
)
Note: Filters run after the query. You still pay for RCU on filtered items.
Sort order
Control the sort order with scan_index_forward:
# Ascending (default)
users = User.status_index.query(status="active", scan_index_forward=True)
# Descending
users = User.status_index.query(status="active", scan_index_forward=False)
Pagination
Use limit to control page size:
# Get results in pages of 10
result = User.status_index.query(status="active", limit=10)
for user in result:
print(user.email)
# Check if there are more results
if result.last_evaluated_key:
print("More results available")
Metrics
Access query metrics after iteration:
result = User.email_index.query(email="john@example.com")
users = list(result)
print(f"Duration: {result.metrics.duration_ms}ms")
print(f"RCU consumed: {result.metrics.consumed_rcu}")
Create table with GSI
When creating tables programmatically, include GSI definitions:
client = DynamoDBClient()
client.create_table(
"users",
hash_key=("pk", "S"),
range_key=("sk", "S"),
global_secondary_indexes=[
{
"index_name": "email-index",
"hash_key": ("email", "S"),
"projection": "ALL",
},
{
"index_name": "status-index",
"hash_key": ("status", "S"),
"range_key": ("pk", "S"),
"projection": "ALL",
},
],
)
Projection types
Control which attributes are copied to the index:
"ALL"- All attributes (default)"KEYS_ONLY"- Only key attributes"INCLUDE"- Specific attributes (usenon_key_attributes)
# Keys only - smallest index, lowest cost
{
"index_name": "status-index",
"hash_key": ("status", "S"),
"projection": "KEYS_ONLY",
}
# Include specific attributes
{
"index_name": "email-index",
"hash_key": ("email", "S"),
"projection": "INCLUDE",
"non_key_attributes": ["name", "created_at"],
}
Limitations
- GSIs are read-only. To update data, update the main table.
- GSI queries are eventually consistent by default.
- Each table can have up to 20 GSIs.