Home Blog Best Practices Global Secondary Indexes (GSI) in DynamoDB: Complete Guide (2025)
Best Practices

Global Secondary Indexes (GSI) in DynamoDB: Complete Guide (2025)

March 10, 2025 By Orlando Adeyemi 11 min read read
Global Secondary Indexes (GSI) in DynamoDB: Complete Guide (2025)

Global Secondary Indexes (GSI) in DynamoDB: Complete Guide (2025)

Global Secondary Indexes (GSIs) are one of the most powerful features of Amazon DynamoDB, enabling flexible and efficient access patterns beyond what’s possible with your table’s primary key. Understanding how to use GSIs effectively is essential for building performant, cost-effective DynamoDB applications.

Table of Contents

What Are Global Secondary Indexes?

A Global Secondary Index (GSI) is an index with a partition key and an optional sort key that can be different from those on the base table. This allows you to query your data using attributes that aren’t part of your table’s primary key structure.

The “global” aspect means that the index spans all partitions in your table, allowing queries against the index to access any data in the base table, regardless of which partition contains it.

// Base table structure
{
  "UserId": "U123", // Partition key
  "OrderId": "O456", // Sort key
  "OrderDate": "2025-03-01",
  "Total": 129.99,
  "Status": "Shipped"
}

// GSI structure - query by Status and OrderDate
GSI1PK: "Status" (Partition key)
GSI1SK: "OrderDate" (Sort key)

With this GSI, you can efficiently perform queries like “find all shipped orders on a specific date” — a query that would require a full table scan without the index.

Key Benefits of GSIs

  1. Flexible Query Patterns: Query your data using attributes that aren’t part of your primary key
  2. Improved Performance: Eliminate inefficient table scans for alternate access patterns
  3. Data Insights: Enable quick analytics queries without impacting production traffic
  4. Reduced Storage: Avoid data duplication by accessing the same data through different keys
  5. Application Evolution: Add new access patterns as your application requirements change

Dynomate: Modern DynamoDB GUI Client

Built for real developer workflows with AWS profile integration, multi-session support, and team collaboration.

AWS SSO support & multi-region browsing
Script-like operations with data chaining
Git-friendly local storage for team sharing

GSI vs. LSI: Understanding the Differences

While both Global Secondary Indexes (GSIs) and Local Secondary Indexes (LSIs) enable additional query patterns, they have fundamental differences:

FeatureGlobal Secondary Index (GSI)Local Secondary Index (LSI)
Partition KeyCan differ from base tableMust be same as base table
Sort KeyOptional, can differ from base tableRequired, must differ from base table
Creation TimingCan be added or removed anytimeMust be created with the table
ConsistencyAlways eventually consistentCan be strongly consistent
QuotaUp to 20 per tableUp to 5 per table
ScopeSpans all partitionsRestricted to a partition
Separate ThroughputYes (provisioned mode)No (shares with base table)
Size LimitsNo partition size restrictions10GB partition size limit

LSIs might be preferable when you:

  • Need strong consistency for your queries
  • Already know all your access patterns during table creation
  • Have simple queries that always involve the same partition key

GSIs are typically the better choice when you:

  • Need flexibility to add new access patterns later
  • Want to query across multiple partitions
  • Need more than 5 additional indexes
  • Need to query on attributes completely different from your primary key

Creating and Managing GSIs

Creating a GSI

You can create a GSI when you first create your table or add it later. Here’s an example of creating a GSI on an existing table:

// Using AWS SDK for JavaScript v3
const params = {
  TableName: 'Orders',
  AttributeDefinitions: [
    { AttributeName: 'Status', AttributeType: 'S' },
    { AttributeName: 'OrderDate', AttributeType: 'S' }
  ],
  GlobalSecondaryIndexUpdates: [
    {
      Create: {
        IndexName: 'StatusDateIndex',
        KeySchema: [
          { AttributeName: 'Status', KeyType: 'HASH' }, // Partition key
          { AttributeName: 'OrderDate', KeyType: 'RANGE' } // Sort key
        ],
        Projection: {
          ProjectionType: 'INCLUDE',
          NonKeyAttributes: ['OrderId', 'UserId', 'Total']
        },
        ProvisionedThroughput: {
          ReadCapacityUnits: 5,
          WriteCapacityUnits: 5
        }
      }
    }
  ]
};

dynamodbClient.updateTable(params);

Querying a GSI

Querying a GSI is similar to querying the base table, but you specify the index name:

const params = {
  TableName: 'Orders',
  IndexName: 'StatusDateIndex',
  KeyConditionExpression: 'Status = :status AND OrderDate BETWEEN :start AND :end',
  ExpressionAttributeValues: {
    ':status': 'Shipped',
    ':start': '2025-03-01',
    ':end': '2025-03-10'
  }
};

const result = await dynamodbClient.query(params);

Modifying and Deleting GSIs

You can modify a GSI’s provisioned throughput or delete it entirely:

// Updating GSI throughput
const updateParams = {
  TableName: 'Orders',
  GlobalSecondaryIndexUpdates: [
    {
      Update: {
        IndexName: 'StatusDateIndex',
        ProvisionedThroughput: {
          ReadCapacityUnits: 10,
          WriteCapacityUnits: 10
        }
      }
    }
  ]
};

// Deleting a GSI
const deleteParams = {
  TableName: 'Orders',
  GlobalSecondaryIndexUpdates: [
    {
      Delete: {
        IndexName: 'StatusDateIndex'
      }
    }
  ]
};

GSI Projections: Balancing Performance and Cost

One of the most important decisions when creating a GSI is selecting what data to project (copy) into the index. DynamoDB offers three projection types:

1. KEYS_ONLY

Only the table and index keys are projected:

Projection: {
  ProjectionType: 'KEYS_ONLY'
}

Pros:

  • Minimizes storage costs
  • Smallest possible index size

Cons:

  • Requires a fetch from the base table if you need any non-key attributes

Best for:

  • Checking if items exist
  • When you only need key information
  • When the cost of fetching from the base table is acceptable

2. INCLUDE

Only specified attributes are projected:

Projection: {
  ProjectionType: 'INCLUDE',
  NonKeyAttributes: ['OrderDate', 'Total', 'Status']
}

Pros:

  • Balances storage costs and query efficiency
  • Returns commonly needed attributes without fetching from base table

Cons:

  • Requires careful planning of which attributes to include
  • May still require base table fetch for missing attributes

Best for:

  • When you know exactly which attributes your queries need
  • When you need to minimize stored data while avoiding base table fetches

3. ALL

All attributes from the base table are projected:

Projection: {
  ProjectionType: 'ALL'
}

Pros:

  • Maximum query efficiency
  • Never requires fetching from the base table
  • Automatically includes new attributes added to base table

Cons:

  • Highest storage costs
  • Duplicates all data from the base table

Best for:

  • Frequently queried indexes where performance is critical
  • When you need different attributes for different queries
  • When storage cost is less important than query performance

Cost-Benefit Analysis

When choosing a projection type, consider:

  1. Query Patterns: What attributes do your queries typically need?
  2. Read/Write Ratio: High-read applications may benefit more from ALL projections
  3. Storage Costs vs. Read Costs: Balance the cost of stored data vs. additional read operations
  4. Item Size: Large items with infrequently accessed attributes may benefit from selective INCLUDE projections

GSI Consistency and Eventual Consistency

Eventual Consistency Model

All GSIs in DynamoDB are eventually consistent, meaning there may be a slight delay between when you update an item in the base table and when that update appears in the GSI.

Time: T1 - Update item in base table
      T2 - (milliseconds to seconds later) Update appears in GSI

Most applications work fine with eventual consistency, but you should consider its implications:

Handling Consistency Gaps

  1. Application Design: Design your application to handle slightly stale data when querying GSIs

  2. Mitigation Strategies:

    • Include timestamps to identify potentially stale data
    • Implement retry logic for critical operations
    • Use versioning to detect conflicts
  3. When Strong Consistency Matters:

    • For operations requiring strong consistency, query the base table directly
    • Consider using LSIs which support strongly consistent reads (though with other limitations)

GSI Update Process

Understanding the update process helps anticipate consistency behavior:

  1. The base table is updated first
  2. Then each GSI is updated asynchronously
  3. GSI updates happen in parallel, not sequentially
  4. Time to consistency typically ranges from milliseconds to seconds
  5. Under heavy loads, consistency delays may increase

GSI Capacity Planning

Provisioned Capacity Mode

In provisioned capacity mode, each GSI has its own read/write capacity units:

ProvisionedThroughput: {
  ReadCapacityUnits: 5,
  WriteCapacityUnits: 5
}

When planning GSI capacity:

  1. Write Capacity: Must be sufficient to handle base table writes that affect the GSI

  2. Read Capacity: Should match expected query volume on the GSI

  3. Throttling Implications:

    • If a GSI has insufficient write capacity, writes to the base table will be throttled
    • If a GSI has insufficient read capacity, only queries on that GSI are throttled

On-Demand Capacity Mode

On-demand capacity mode simplifies capacity management and is often recommended for GSIs:

BillingMode: 'PAY_PER_REQUEST'
// No ProvisionedThroughput parameter needed

Benefits for GSIs:

  1. Simplified Management: No need to predict capacity requirements
  2. Automatic Scaling: Handles spikes in traffic automatically
  3. Cost Optimization: Pay only for what you use
  4. Reduced Throttling: Lower risk of throttling due to incorrect capacity estimation

Common GSI Access Patterns

GSIs enable many access patterns that would otherwise be inefficient. Here are common usage patterns:

Familiar with these Dynamodb Challenges ?

  • Writing one‑off scripts for simple DynamoDB operations
  • Constantly switching between AWS profiles and regions
  • Sharing and managing database operations with your team

You should try Dynomate GUI Client for DynamoDB

  • Create collections of operations that work together like scripts
  • Seamless integration with AWS SSO and profile switching
  • Local‑first design with Git‑friendly sharing for team collaboration

1. Inverted Indexes

// Base table: User data with primary key UserId
{
  "UserId": "U123",
  "Email": "user@example.com",
  "Name": "Jane Smith"
}

// GSI: Look up user by email
GSI1PK: "Email" (Partition key)

This pattern lets you find users by email address with a simple key lookup.

2. Materialized Aggregates

// Base table: Order items
{
  "OrderId": "O456",
  "ProductId": "P789",
  "Quantity": 2,
  "Price": 29.99,
  "GSI1PK": "DAILY#2025-03-10", // For aggregation
  "GSI1SK": "PRODUCT#P789"      // For sorting/filtering
}

// GSI: Query daily product sales
GSI1PK: "DAILY#2025-03-10" (Partition key)
GSI1SK: "PRODUCT#P789" (Sort key)

This pattern enables efficient queries for aggregation and reporting.

3. Sparse Indexes

// Base table: User activity
{
  "UserId": "U123",
  "ActivityId": "A456",
  "Type": "Login",
  "Timestamp": "2025-03-10T12:30:45Z"
}

// Only premium users have this attribute
{
  "UserId": "U789",
  "ActivityId": "A101",
  "Type": "Purchase",
  "Timestamp": "2025-03-10T14:20:30Z",
  "PremiumAction": "Yes" // Only present for premium user actions
}

// GSI: Only includes items with PremiumAction attribute
GSI1PK: "PremiumAction" (Partition key)
GSI1SK: "Timestamp" (Sort key)

This pattern creates an index that only contains a subset of items, useful for filtering specific categories.

4. Multi-Entity Pattern

// Base table: Contains both users and orders with different prefixes
{
  "PK": "USER#U123",
  "SK": "METADATA",
  "Name": "John Doe",
  "Email": "john@example.com"
}

{
  "PK": "ORDER#O456",
  "SK": "METADATA",
  "CustomerName": "John Doe",
  "Total": 99.99,
  "Status": "Processing"
}

// GSI: Access items by type
GSI1PK: "ENTITY#USER" or "ENTITY#ORDER" (derived from PK)
GSI1SK: "PK" (the original PK value)

This pattern supports single-table design by allowing efficient queries across specific entity types.

Advanced GSI Strategies

1. Overloading Indexes

Use the same GSI for multiple access patterns by carefully designing your key structure:

// Base item: User
{
  "PK": "USER#123",
  "SK": "METADATA",
  "GSI1PK": "EMAIL#user@example.com",
  "GSI1SK": "USER#123",
  "Name": "John Doe"
}

// Base item: Order
{
  "PK": "ORDER#456",
  "SK": "METADATA",
  "GSI1PK": "USER#123", // User who placed the order
  "GSI1SK": "ORDER#456",
  "Total": 129.99
}

// Base item: Product
{
  "PK": "PRODUCT#789",
  "SK": "METADATA",
  "GSI1PK": "CATEGORY#Electronics",
  "GSI1SK": "PRODUCT#789",
  "Name": "Wireless Headphones"
}

This single GSI enables:

  • Finding users by email: Query GSI1 where GSI1PK = "EMAIL#user@example.com"
  • Finding all orders for a user: Query GSI1 where GSI1PK = "USER#123" AND begins_with(GSI1SK, "ORDER#")
  • Finding all products in a category: Query GSI1 where GSI1PK = "CATEGORY#Electronics"

2. Write Sharding for Hot Keys

Distribute writes across multiple partition key values for high-volume data:

// Instead of a single hot key like "STATUS#processing"
// Create sharded keys
"STATUS#processing#1"
"STATUS#processing#2"
"STATUS#processing#3"
...
"STATUS#processing#10"

To query:

// Perform multiple queries in parallel or sequence
const promises = [];
for (let i = 1; i <= 10; i++) {
  promises.push(
    dynamodbClient.query({
      TableName: 'Orders',
      IndexName: 'StatusIndex',
      KeyConditionExpression: 'GSI1PK = :status',
      ExpressionAttributeValues: {
        ':status': `STATUS#processing#${i}`
      }
    })
  );
}

// Combine results
const results = await Promise.all(promises);
const combinedItems = results.flatMap(result => result.Items);

3. Composite Sort Keys

Create sort keys that combine multiple attributes for flexible filtering:

// Base item
{
  "PK": "USER#123",
  "SK": "ORDER#2025-03-10#PREMIUM#456",
  "Total": 129.99
}

// This allows queries like:
// - All orders: begins_with(SK, "ORDER#")
// - Orders on a specific date: begins_with(SK, "ORDER#2025-03-10")
// - Premium orders on a specific date: begins_with(SK, "ORDER#2025-03-10#PREMIUM")
// - Specific order: SK = "ORDER#2025-03-10#PREMIUM#456"

GSI Limitations and Workarounds

1. Size Limits

Limitation: Maximum item size in a GSI is 10GB (same as base table)

Workaround:

  • Split large items into smaller items
  • Use sparse indexes by conditionally including attributes

2. Maximum Number of GSIs

Limitation: Maximum 20 GSIs per table

Workaround:

  • Overload indexes for multiple access patterns
  • Design a single-table model with careful key design
  • For extreme cases, consider multiple tables with duplicate data

3. Attribute Projection Limits

Limitation: Maximum 100 attributes in INCLUDE projection

Workaround:

  • Use composite attributes to combine related data
  • Switch to ALL projection if you need more attributes
  • Create multiple GSIs with different INCLUDE attributes

4. No Transactions on GSIs

Limitation: Cannot query GSIs in transactions

Workaround:

  • Design keys to enable transactions on the base table
  • Implement application-level verification logic

5. GSI Backfilling During Creation

Limitation: Adding a GSI to a large table can take time

Workaround:

  • Create GSIs during low-traffic periods
  • Monitor backfill progress with CloudWatch
  • Create GSIs earlier in your application lifecycle

Real-World GSI Examples

E-commerce Platform

// Base table items
{
  "PK": "CUSTOMER#C123",
  "SK": "PROFILE",
  "Email": "customer@example.com",
  "Name": "John Smith",
  "RegisteredDate": "2025-01-15"
}

{
  "PK": "CUSTOMER#C123",
  "SK": "ORDER#O456",
  "OrderDate": "2025-03-10",
  "Status": "Shipped",
  "Total": 129.99,
  "GSI1PK": "STATUS#Shipped",
  "GSI1SK": "2025-03-10"
}

{
  "PK": "PRODUCT#P789",
  "SK": "METADATA",
  "Name": "Wireless Headphones",
  "Category": "Electronics",
  "Price": 99.99,
  "GSI1PK": "CATEGORY#Electronics",
  "GSI1SK": "PRICE#099.99"
}

GSI Access Patterns:

  • Find all shipped orders on a specific date: Query GSI1 with GSI1PK = "STATUS#Shipped" AND begins_with(GSI1SK, "2025-03-10")
  • Find all electronics products, sorted by price: Query GSI1 with GSI1PK = "CATEGORY#Electronics"

Content Management System

// Base table items
{
  "PK": "AUTHOR#A123",
  "SK": "PROFILE",
  "Name": "Jane Author",
  "Email": "jane@example.com"
}

{
  "PK": "AUTHOR#A123",
  "SK": "ARTICLE#ART456",
  "Title": "Understanding DynamoDB",
  "PublishDate": "2025-03-10",
  "Status": "Published",
  "Category": "Database",
  "GSI1PK": "STATUS#Published",
  "GSI1SK": "2025-03-10",
  "GSI2PK": "CATEGORY#Database",
  "GSI2SK": "2025-03-10"
}

{
  "PK": "ARTICLE#ART456",
  "SK": "COMMENT#C789",
  "Author": "Reader123",
  "Content": "Great article!",
  "CommentDate": "2025-03-11",
  "GSI1PK": "ARTICLE#ART456",
  "GSI1SK": "COMMENT#2025-03-11"
}

GSI Access Patterns:

  • Find all published articles by date: Query GSI1 with GSI1PK = "STATUS#Published"
  • Find all database articles by date: Query GSI2 with GSI2PK = "CATEGORY#Database"
  • Find all comments for an article by date: Query GSI1 with GSI1PK = "ARTICLE#ART456" AND begins_with(GSI1SK, "COMMENT#")

Best Practices for GSI Design

1. Plan for Query Patterns First

Always design your indexes based on specific query patterns:

  • List all the ways your application needs to access data
  • Identify which attributes will be used in condition expressions
  • Determine which sort orders your application needs
  • Consider future access patterns as your application evolves

2. Use Composite Keys Strategically

Composite keys combine multiple attributes for flexible querying:

// Instead of:
GSI1PK: "ORDER"
GSI1SK: "2025-03-10"

// Use:
GSI1PK: "ORDER#ACTIVE"
GSI1SK: "CUSTOMER#C123#2025-03-10"

This allows for queries like:

  • All active orders: GSI1PK = "ORDER#ACTIVE"
  • All active orders for a customer: GSI1PK = "ORDER#ACTIVE" AND begins_with(GSI1SK, "CUSTOMER#C123")
  • All active orders for a customer on a specific date: GSI1PK = "ORDER#ACTIVE" AND begins_with(GSI1SK, "CUSTOMER#C123#2025-03-10")

3. Minimize Index Count Through Overloading

Use the same index for multiple access patterns when possible:

// User by email
GSI1PK: "EMAIL#user@example.com"
GSI1SK: "USER"

// Order by date
GSI1PK: "ORDER#2025-03-10"
GSI1SK: "STATUS#Shipped"

// Product by category
GSI1PK: "CATEGORY#Electronics"
GSI1SK: "PRODUCT#Headphones"

This single GSI serves multiple query needs without creating separate indexes.

4. Choose Projections Wisely

  • KEYS_ONLY: Use when you only need to check existence or when retrieving the full item from the base table is acceptable
  • INCLUDE: Use when you know exactly which attributes your queries need
  • ALL: Use for frequently accessed indexes where performance is critical

5. Monitor and Optimize GSI Performance

  • Use CloudWatch metrics to monitor GSI performance
  • Look for:
    • Throttled requests (ProvisionedThroughputExceeded)
    • High consumed capacity
    • Scan operations (which should be minimized)
  • Review GSI strategies regularly as your application evolves

6. Use Sparse Indexes When Appropriate

Create indexes that only contain a subset of your data:

// Only items with the "PremiumTier" attribute will appear in this GSI
GSI1PK: "PremiumTier"
GSI1SK: "UserRegion"

This makes queries for specific subsets of data more efficient.

Switching from Dynobase? Try Dynomate

Developers are switching to Dynomate for these key advantages:

Better Multi-Profile Support

  • Native AWS SSO integration
  • Seamless profile switching
  • Multiple accounts in a single view

Developer-Focused Workflow

  • Script-like operation collections
  • Chain data between operations
  • Full AWS API logging for debugging

Team Collaboration

  • Git-friendly collection sharing
  • No account required for installation
  • Local-first data storage for privacy

Privacy & Security

  • No account creation required
  • 100% local data storage
  • No telemetry or usage tracking

Conclusion

Global Secondary Indexes are a powerful feature that enables flexible and efficient access patterns in DynamoDB. By understanding how to properly design, create, and use GSIs, you can build applications that are both performant and cost-effective.

Key takeaways:

  • Design your GSIs based on specific query patterns
  • Use composite and overloaded keys to maximize flexibility
  • Choose appropriate projections to balance performance and cost
  • Monitor and optimize your GSIs as your application evolves
  • Consider GSI limitations and implement appropriate workarounds

For more information on optimizing your DynamoDB implementation, check out our related articles:

Ready to simplify your DynamoDB development experience? Try Dynomate - our powerful GUI client that makes working with DynamoDB indexes easier than ever.

Share this article:

Related Articles