Beginner8 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. Which one you need depends on which key you're changing.

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

A Global Secondary Index takes its own partition key and optional sort key, gets its own capacity, and can be added anytime, but serves eventually consistent reads only.

A Local Secondary Index keeps the table's 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), optionalAny scalar (S/N/B)
Key attributesUp to 4 partition-key + 4 sort-keyExactly one sort-key attr
When createdAnytimeTable-creation only
ConsistencyEventual onlyStrong available
CapacityIts ownShares the table's
Write propagationAsync (eventual)Synchronous
Max per table20 (default quota)5
10 GB item-collection capNoYes (per partition key)

The four-plus-four row is new: since November 2025 a GSI key can be built from several typed attributes instead of one concatenated string. An LSI's sort key is still exactly one attribute.

A rule of thumb

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

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.

GSIs are the usual default: they can be added later, scale on their own capacity, and have no 10 GB cap. AWS suggests a GSI whenever an item collection might grow past 10 GB. 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.

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 id: GetItem on the base table key.
  2. List open tickets for the team, highest priority first: needs a different sort order than ticket id.

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

ApproachKeysConsistency on listWhen it fits
GSI with GSI1PK = TEAM#7, GSI1SK = P#1#TICKET#8842, set only while openNew partition + sortEventualStatus changes often; add index after launch
LSI with same PK, LSI1SK = P#1#TICKET#8842, set only while openSame partition, new sortStrongIndex planned at table creation; status reads must be fresh
Filter on base Query, then sort by priority in your codeBase keys onlyStrongTiny partitions only: the filter bills the whole partition you read

Writing the index key only on open tickets makes both indexes sparse: closed tickets never enter them. On the GSI you could also make priority and ticketId two typed sort-key attributes instead of building the P#1#TICKET#8842 string.

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

An update costs an index write only when it touches what that index stores: one write if it changes a projected attribute, two if it changes an index key value (delete the old entry, put the new one), none if the index doesn't store it.

Say an update changes priority, which sits in both GSI1SK and LSI1SK, on a table that also has a GSI2 that doesn't store it. That is 2 + 2 + 0 = four index writes plus the base write.

An index entry over 1 KB costs more than one write unit per write; the indexes overview prices this per month.

LSI writes land on the same partition as the base item, because an LSI shares its partition key. Each LSI adds its write cost to that partition, so a busy partition key reaches the per-partition ceiling sooner.

A GSI moves its writes to its own partitions, but an under-sized or hot GSI still throttles base-table writes; see why a GSI throttles base-table writes.

The 10 GB LSI item-collection cap

On a table with an LSI, AWS caps each partition-key value at 10 GB, counting the base-table items and their LSI entries together. A table with no LSI has no such cap, and GSIs are exempt. A tenant with millions of tickets under one TEAM#7 can hit it.

At the cap, DynamoDB can reject writes that would grow the collection with ItemCollectionSizeLimitExceededException; writes that shrink it still succeed.

See it coming by passing ReturnItemCollectionMetrics: "SIZE" on PutItem, UpdateItem, DeleteItem and BatchWriteItem.

Alert when SizeEstimateRangeGB passes a threshold below 10 (AWS suggests 8 GB). To recover, delete or trim items in that collection, or move history to a new table or to S3. For collections that grow without bound, use a GSI from the start.

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
Item collection may grow past ~10 GB?Yes, no capRisky on LSI
Want isolated write scaling?YesNo, shares table WCU

Already have a live table and want an LSI? You can't add one. If eventual reads are acceptable, add a GSI with the table's partition key and the new sort key (UpdateTable with GlobalSecondaryIndexUpdates).

If you need strong reads, CreateTable a new table with the LSI, copy the items across (parallel Scan plus BatchWriteItem, dual-writing during the cutover), then switch readers. Import from S3 can't help: the table it creates takes GSIs but no LSIs.

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

Inspect indexes before you commit

In DynoTable, run a filter against the base table, then switch the filter builder's index picker to each GSI and run it again.

Attributes missing from the GSI result are not in its projection.

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.

Three guides that build on this choice

Try DynoTable to inspect each index's keys and projection on your own tables before you commit to a design.

By Dmitriy · Updated