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
| GSI | LSI | |
|---|---|---|
| Partition key | Any scalar (S/N/B) | Same as the table |
| Sort key | Any scalar (S/N/B) | Any scalar (S/N/B) |
| When created | Anytime | Table-creation only |
| Consistency | Eventual only | Strong available |
| Capacity | Its own | Shares the table's |
| Write propagation | Async (eventual) | Synchronous (atomic) |
| Max per table | 20 (default, raisable) | 5 (hard) |
| 10 GB partition cap | No | Yes (per PK) |
A rule of thumb
- Need a different partition key (e.g. look up orders by
statusinstead ofcustomer)? 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 strongly-consistent reads? An LSI fits.
The choice collapses to one question — which key are you changing:
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:
-
Fetch one ticket by id —
GetItemon the base table key. - 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:
| Approach | Keys | Consistency on list | When it fits |
|---|---|---|---|
GSI with GSI1PK = TEAM#7, GSI1SK = STATUS#open#P#1#TICKET#8842
|
New partition + sort | Eventual | Status changes often; add index after launch |
LSI with same PK, LSI1SK = STATUS#open#P#1#...
|
Same partition, new sort | Strong | Index planned at table creation; status reads must be fresh |
Filter on base Query
|
Base keys only | Strong | Tiny 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
| Question | Lean GSI | Lean LSI |
|---|---|---|
| Need a different partition key? | Yes | No — LSI cannot |
| Must reads be strongly consistent? | No — GSI is eventual | Yes |
| Index added after table exists? | Yes | No — table creation only |
| Partition may grow past ~10 GB of related items? | Yes — GSI spreads | Risky on LSI |
| Want isolated write scaling? | Yes | No — 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.
Related reading
- GSI eventually consistent reads — why a just-written item can be missing from a GSI query for a moment.
- Index projections — what each index physically stores.
- Sparse indexes — keep GSIs small by indexing only items that carry a marker attribute.

Top comments (0)