DEV Community

Cover image for DynamoDB GSI vs LSI
DynoTable
DynoTable

Posted on • Originally published at dynotable.com

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

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 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 strongly-consistent reads? An LSI fits.

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

DynamoDB GSI vs LSI

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:

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

Top comments (0)