Beginner7 min read

DynamoDB GSI vs LSI

Both a Global Secondary Index (GSI) and a Local Secondary Index (LSI) let you Query by an attribute that isn't your table's key. They are not interchangeable — the differences decide which one a pattern needs.

What is the difference between a GSI and an LSI in DynamoDB?

A Global Secondary Index can use any top-level scalar attribute (String, Number, or Binary) as its partition key, gets its own capacity, and can be added anytime — but only serves eventually consistent reads. A Local Secondary Index keeps the table's same partition key with a different sort key, supports strongly consistent reads and shares the table's capacity, but must be created with the table.

The differences that matter

GSILSI
Partition keyAny scalar (S/N/B)Same as the table
Sort keyAny scalar (S/N/B)Any scalar (S/N/B)
When createdAnytimeTable-creation only
ConsistencyEventual onlyStrong available
CapacityIts ownShares the table's
Write propagationAsync (eventual)Synchronous (atomic)
Max per table20 (default, raisable)5 (hard)
10 GB partition capNoYes (per PK)

A rule of thumb

  • Need a different (e.g. look up orders by status instead of customer)? You need a GSI — an LSI can't repartition.
  • Need a second sort order within the same partition — an LSI keeps the table's exact partition key and only swaps in a different sort key — decided up front, with reads? An LSI fits.

The choice collapses to one question — which key are you changing:

YesNo, same PKnew sort keyNeed to queryanother way?Differentpartition key?GSILSIOwn partitionsEventual readsOwn capacityAdd anytimeShared partitionStrong reads OKShared capacityTable-creation only

Different partition key forces a GSI; a different sort key on the same partition is the only case an LSI fits.

In practice most teams reach for GSIs almost exclusively: they're addable later, independently scaled, and not subject to the 10 GB per-partition limit. Overload a single GSI's keys to serve several patterns — see single-table design.

If you're adding a GSI to kill a Scan, remember it has its own read/write capacity. Size that extra cost with the pricing calculator, and try DynoTable to inspect an index's projected attributes before you commit to it.

Worked example: open tickets by priority

Say you run a support desk. Tickets live under PK = TEAM#7 with SK = TICKET#8842. Two access patterns compete:

  1. Fetch one ticket by idGetItem on the base table key.
  2. List open tickets for the team, highest priority first — needs a different sort order than creation time.

Pattern 2 cannot be a base-table Query on SK alone because the sort key is the ticket id, not status. Your options:

ApproachKeysConsistency on listWhen it fits
GSI with GSI1PK = TEAM#7, GSI1SK = STATUS#open#P#1#TICKET#8842New partition + sortEventualStatus changes often; add index after launch
LSI with same PK, LSI1SK = STATUS#open#P#1#...Same partition, new sortStrongIndex planned at table creation; status reads must be fresh
Filter on base QueryBase keys onlyStrongTiny partitions only — filters bill the whole partition

For a busy queue where agents refresh every few seconds, eventual consistency on a GSI is usually acceptable. For a financial ledger line that must reflect a status change before the API returns, serve the read from the base table or an LSI.

Write amplification in numbers

Every base-table write propagates to each secondary index that projects the changed attributes. A ticket update that touches status and priority on a table with two GSIs and one LSI can fan out to four index writes plus the base write.

On on-demand billing in us-east-1, a 1 KB item write costs one WCU per destination. Updating one attribute on that item across one GSI adds roughly one more WCU to the request total — the pricing calculator accepts item size and request rate if you want a monthly line item before you add indexes.

LSIs share the table's throughput pool. A hot LSI sort key on a overloaded partition can throttle base-table writes even when the base partition key spread looks healthy — GSIs isolate that risk at the cost of separate capacity.

The 10 GB LSI partition ceiling

An LSI lives inside the same physical partition as its base-table partition key. AWS documents a 10 GB per partition limit for item collections that include LSI data. A tenant with millions of tickets under one TEAM#7 partition can hit that ceiling while the GSI-free base table still looks fine on paper.

GSIs repartition on their own keys, so no single customer partition carries the entire LSI payload. That repartitioning is why most production schemas default to GSIs even when an LSI could technically serve the read.

Choosing at design time

QuestionLean GSILean LSI
Need a different partition key?YesNo — LSI cannot
Must reads be strongly consistent?No — GSI is eventualYes
Index added after table exists?YesNo — table creation only
Partition may grow past ~10 GB of related items?Yes — GSI spreadsRisky on LSI
Want isolated write scaling?YesNo — shares table WCU

When you overload several access patterns onto one GSI, sketch the key templates first in the single-table design tool — it surfaces which reads share an index and flags unsupported patterns before you pay for a backfill.

Inspect indexes before you commit

In DynoTable, open the table's index picker and run the same filter against the base table and each GSI side by side. You see projected attributes immediately — attributes missing from the GSI result are not in the projection — and you can confirm whether an eventual GSI read is fresh enough for your UI.

Build the KeyConditionExpression for a candidate GSI in the DynamoDB expression builder, then emit a full paginated program with the query builder to paste into a staging test.

Updated