DynamoDB Secondary Indexes: GSI vs LSI

Global vs Local Secondary Indexes in DynamoDB — when to use each, projection types, costs and limits — so you can query by non-key attributes.

5 min read· Updated Jun 15, 2026
On this page

A secondary index lets you query a table by attributes that aren’t part of the primary key. Without one, your only option for non-key lookups is a full table scan — slow and expensive. DynamoDB offers two flavors, and choosing the wrong one (or learning you can’t add an LSI later) is a common, painful mistake.

This guide explains how Global and Local Secondary Indexes differ, when to use each, and how projections affect cost.

Why you need indexes at all

DynamoDB’s base table only supports efficient queries on the primary key. If your Orders table is keyed on customerId + orderDate, you can list a customer’s orders instantly — but “find all orders with status SHIPPED” requires scanning every item. See query vs scan for why scans don’t scale.

A secondary index solves this by maintaining an alternate key schema over the same data, automatically kept in sync by DynamoDB. You query the index exactly like a table.

Global Secondary Index (GSI)

A GSI has its own partition key and optional sort key, completely independent of the base table’s keys. It’s the workhorse index.

Base table:  PK = customerId   SK = orderDate
GSI1:        PK = status       SK = orderDate

Now Query on GSI1 with status = "SHIPPED" returns shipped orders sorted by date, no scan required.

Key characteristics:

  • Independent keys. The GSI partition key can be any attribute (or composite of attributes you write).
  • Eventually consistent only. GSIs are updated asynchronously after the base write commits, so there’s a small replication lag. You cannot request ConsistentRead=true on a GSI.
  • Separate throughput. A GSI has its own read and write capacity. In on-demand mode it scales independently; in provisioned mode you allocate RCUs/WCUs per index.
  • Add or drop anytime. You can create a GSI on a live table and DynamoDB backfills it in the background.
  • Limit: 20 GSIs per table by default (raisable via support).

Local Secondary Index (LSI)

An LSI shares the same partition key as the base table but uses a different sort key. It gives you alternate ways to sort and range-query within a single partition.

Base table:  PK = customerId   SK = orderDate
LSI1:        PK = customerId   SK = orderTotal

This lets you query a single customer’s orders sorted by total instead of date.

Key characteristics:

  • Same partition key, different sort key. You’re always still querying within one partition key value.
  • Strongly consistent reads supported. Because an LSI lives on the same partition as the base item, it can be read with strong consistency.
  • Shares the base table’s throughput. No separate capacity to provision.
  • Must be created at table creation. You cannot add or remove an LSI later — this is the big gotcha.
  • Limit: 5 LSIs per table.
  • 10 GB item collection limit. Because the LSI and base items share a partition, the total size of all items under one partition key (base + all LSIs) cannot exceed 10 GB. GSIs have no such limit.

GSI vs LSI side by side

FeatureGSILSI
Partition keyAny attributeSame as base table
Sort keyAny attribute (optional)Different from base
Read consistencyEventual onlyEventual or strong
ThroughputSeparate from baseShared with base
Create after table exists?YesNo — creation time only
Max per table205
Item collection size limitNone10 GB per partition

The practical rule: reach for a GSI first. Use an LSI only when you specifically need strong consistency on an alternate sort key and you’re certain at design time that you’ll need it.

Projections: what data the index stores

An index is a copy of your data with a different key. You control how much data is copied via the ProjectionType:

  • KEYS_ONLY — only the index keys and the base table keys. Smallest and cheapest. Reads return just the keys; fetch the rest with GetItem (a second round trip).
  • INCLUDE — keys plus a chosen list of non-key attributes. A middle ground: project exactly the attributes your queries need.
  • ALL — every attribute of the item is copied. Most storage and write cost, but queries are fully self-contained.
GSI1:
  KeySchema:  PK = status, SK = orderDate
  Projection:
    ProjectionType: INCLUDE
    NonKeyAttributes: [orderTotal, shippingCarrier]

If a query requests an attribute that isn’t projected on a GSI, DynamoDB does not fetch it from the base table automatically — your query simply won’t return it. Project what you read.

Cost: indexes aren’t free

Every write to the base table that touches a projected attribute also costs write capacity on each affected index:

  • Insert an item → one write on the base table plus one write on each index whose key attributes are present.
  • Update a projected attribute → DynamoDB may perform a delete-and-insert on the index, costing extra WCUs.
  • A GSI also incurs its own storage cost for the projected copy.

Over-projecting (ALL everywhere) and over-indexing both inflate write costs silently. For the mechanics of how reads and writes are metered, see capacity and pricing.

Index overloading in single-table design

In single-table designs, indexes use generic names like GSI1PK and GSI1SK, and different entity types write different values into them. One GSI can then serve many access patterns. This key overloading is core to the pattern — see single-table design for worked examples.

USER  item:  GSI1PK = EMAIL#a@b.com   GSI1SK = USER#123
ORDER item:  GSI1PK = STATUS#SHIPPED  GSI1SK = 2026-06-16#A1

Choosing an index: a quick decision path

  1. Do you need to query by a non-key attribute? If no, you don’t need an index.
  2. Is the new query within a single partition key, just a different sort order, and does it need strong consistency? An LSI fits — but only if you can add it at table creation.
  3. Otherwise, use a GSI. It’s flexible, addable later, and covers the vast majority of cases.
  4. Project only the attributes your queries return. Start with KEYS_ONLY or INCLUDE; reach for ALL only when you genuinely read most of the item.

Seeing your indexes clearly

Indexes are easy to misconfigure: an under-projected GSI silently drops attributes, an LSI you forgot to add at creation can’t be retrofitted, and overloaded GSI keys are hard to reason about from raw JSON. Tablyne, a native DynamoDB GUI, lists every index with its key schema and projection, and lets you run queries against an index directly, so you can confirm an index actually serves the access pattern you built it for.

Get your indexes right and almost any access pattern becomes a single fast Query. The trick is deciding which type you need before you create the table — because for LSIs, there’s no second chance.

Frequently asked questions

Can I add a Local Secondary Index to an existing table?

No. LSIs must be created at table creation time and cannot be added or removed afterward. GSIs, by contrast, can be added or deleted on a live table at any time.

Are DynamoDB GSIs strongly consistent?

No. Global Secondary Indexes only support eventually consistent reads because they are maintained asynchronously. If you need strongly consistent reads on a non-key attribute, use a Local Secondary Index, which supports them.

Do secondary indexes cost extra?

Yes. GSIs have their own provisioned or on-demand throughput and storage, billed separately from the base table. Every write that affects a projected attribute also consumes write capacity on each relevant index.

Keep reading