Global Secondary Indexes in DynamoDB (GSI)
You add a GSI for every access pattern that lacks a key, wake up to tripled write cost, and still read stale rows from the index because you assumed strong consistency. A global secondary index is a second table AWS maintains for you — same billing rules, eventual reads only, write amplification on every base-table put.
At 100 writes per second of 1 KB items, on-demand in us-east-1, writes cost
$164.25 a month with no indexes, $328.50 with one GSI, and $492.75 with two.
Each ALL-projection GSI you add re-buys your write throughput.
What you can do after
- Price GSI write amplification before you commit to a second or third index.
- Choose GSI vs LSI vs filter expression for a specific access pattern.
- Read from a GSI knowing results are eventually consistent at half the RCU rate.
- Design sparse and overloaded keys without breaching projection and quota limits.
Reading order
- GSI vs LSI — repartitioned reads vs same-hash alternate sort; creation-time rules and the 10 GB LSI collection cap.
- Index projections —
ALL,KEYS_ONLY,INCLUDE; storage and write size trade-offs. - Sparse indexes — omit the index key on cold rows so only the hot subset is copied.
- Key overloading — pack multiple access patterns into one GSI with disciplined key shapes.
- GSI eventually consistent — read-your-writes failures and UI patterns that need base-table reads.
- GSI throttles base table — under-provisioned index capacity blocking parent writes.
What is a global secondary index?
A GSI is an index with its own partition key and optional sort key, chosen from
any top-level String, Number, or Binary attribute. The index key does not
need to include the table's key.
DynamoDB maintains the copy for you. Put, update, or delete an item and every
affected GSI is updated asynchronously; you never write to an index directly.
You read it with Query or Scan against the index name.
| field | value | note |
|---|---|---|
| PK | "CUST#9313" | table partition key |
| SK | "ORD#2026-08-01#K4" | table sort key |
| status | "SHIPPED" | GSI partition key |
| placed | "2026-08-01" | GSI sort key |
Query the status-placed GSI for status = "PENDING" and you get every
pending order across all customers, sorted by date. The base table cannot
answer that without a Scan.
An item that lacks the index key attributes is simply absent from the index. That gap is what sparse indexes exploit to turn a small hot subset of a huge table into its own pre-filtered collection.
What does a GSI cost?
The write side dominates. A 1 KB item costs one write
capacity unit (writes round up per 1 KB), and with an ALL
projection every base-table write repeats into every GSI. Measured at three
sustained write rates:
| Sustained writes | 0 GSIs | 1 GSI | 2 GSIs |
|---|---|---|---|
| 10 writes/s | $16.43/mo | $32.85/mo | $49.28/mo |
| 100 writes/s | $164.25/mo | $328.50/mo | $492.75/mo |
| 1,000 writes/s | $1,642.50/mo | $3,285.00/mo | $4,927.50/mo |
Items up to 1 KB, on-demand, us-east-1, at AWS's published $0.625 per million write request units (our pricing sync of 2026-07-29). 10 writes/s is 26.28 million WRUs over a 730-hour billing month.
Read the GSI columns as a floor, not an exact multiple. An index item carries the base table's keys, the index's own keys and about 100 bytes of overhead, so a base item close to the 1 KB line writes 2 WRUs into each index rather than 1 — the ×2 and ×3 grow from there.
An update that changes a GSI key value bills two index writes — one to delete the old entry, one to put the new one — so a volatile index key costs more than a steady one.
Storage repeats too. An ALL projection re-stores the table at $0.25/GB-month
in us-east-1 (same sync), plus 100 bytes of overhead per index item; 10 GB of
table is $2.50/month extra per GSI.
Shrinking the projection to KEYS_ONLY or INCLUDE shrinks
both bills; see index projections.
In provisioned mode the same amplification shows up as capacity, and an under-provisioned GSI throttles your base table's writes. Why a GSI throttles base-table writes walks through that failure.
To price your own workload, the pricing calculator runs on the same price table as the numbers above.
How a GSI bills reads
GSI reads are always eventually consistent, and eventual reads bill at half rate: 0.5 RCU per 4 KB.
A Query returning twenty 1 KB items sums to 20 KB, rounds to five 4 KB
units, and bills 2.5 RCUs. The same twenty items read strongly consistent from
the base table bill 5 RCUs.
The half price buys a lag. Changes propagate to a GSI "within a fraction of a
second, under normal conditions" (AWS's wording), and there is no
ConsistentRead option to wait it out.
If your code reads its own writes back through a GSI, read why a GSI is eventually consistent before that bites in production.
GSI vs LSI vs filter expression
| Your access pattern | Reach for | Why |
|---|---|---|
| Query by an attribute that isn't the table key | GSI | The only index that repartitions; can be added to a live table anytime |
| Second sort order in the same partition, strong reads | LSI | Shares table capacity and supports ConsistentRead; creation-time only, 10 GB collection cap |
| Query a small hot subset of a huge table | Sparse GSI | Only items carrying the index key are copied, so the index stays tiny and cheap |
Narrow a result set that is already one cheap Query | Filter expression | No copy to maintain, but you still pay to read every discarded item |
| A one-off question you will never ask again | No index | A Scan or an export costs once; an index costs every month |
The LSI cap is stricter than it sounds: 10 GB per partition-key value counts the base-table items plus every LSI entry with that key, and DynamoDB rejects writes once a collection hits it.
That, plus the creation-time-only rule, is why the honest default is a GSI. GSI vs LSI makes the full case.
Limits that shape index design
- 20 GSIs per table: the default quota, adjustable through Service Quotas.
- 5 LSIs per table: fixed, and only at
CreateTable; you can't add one later. - 100 projected attributes: the combined
INCLUDEattributes across all of a table's indexes;KEYS_ONLYandALLprojections don't count against it. QueryandScanonly:GetItemandBatchGetItemdon't work on any secondary index.- No base-table fetch from a GSI: a GSI query returns projected attributes only. An LSI query can fetch unprojected attributes, at extra read cost.
- Index keys are top-level scalars:
String,Number, orBinary; no sets, lists, maps, or nested attributes.
Adding a GSI to a live table
Unlike an LSI, a GSI can be added to a table that is already serving traffic:
UpdateTable accepts one index creation or deletion per call, and the table
stays ACTIVE throughout. DynamoDB builds the new index in two phases — the
index resource is created, then every existing item that carries the index
key attributes is backfilled into it.
Three things about the backfill catch people out:
- You can't query the index until it finishes. The index reports
CREATING(with aBackfillingflag once the copy starts) and aQueryagainst it is rejected until the status reachesACTIVE. - Backfill reads are free, but the index writes are real. DynamoDB reads the existing items with internal capacity, so the scan doesn't bill against the table — but every item lands in the index through the index's own write capacity. An under-provisioned GSI turns a backfill into a crawl; on-demand tables size it for you.
- Duration scales with table size. Minutes for small tables, hours once
the table holds hundreds of gigabytes.
DescribeTablereports the phase, not a percentage.
FAQ
Can you add a GSI to an existing DynamoDB table?
Yes. A GSI can be added to a live table at any time with UpdateTable, one
index per call, and the table keeps serving reads and writes while DynamoDB
backfills the index. Only LSIs are locked to table creation.
How long does a GSI take to backfill?
It scales with table size and the index's write capacity — minutes for small
tables, hours for large ones. The index reports CREATING until the backfill
finishes, and queries against it are rejected until the status is ACTIVE.
Can you change a GSI after it is created? No. DynamoDB has no way to change an existing GSI's keys or projection — you create a replacement index with the shape you want, move readers over, and delete the old one. Provisioned throughput is the exception: it can be updated in place.
Can you add an LSI to an existing table?
No. LSIs exist only if declared at CreateTable, and no tool can add one
later. If you need a second sort order on an existing table, the answer is a
GSI with the same partition key.
Open DynoTable
The Table settings dialog in DynoTable lists the table's key structure and every GSI and LSI
with its keys, so you can see which access patterns a table already serves
without running DescribeTable yourself.

The visual filter builder has an index picker and derives Query vs Scan
for you as you add conditions, so an expensive fallback to a full Scan is
visible before you run anything.

The same dialog also creates and deletes GSIs: name the key schema, pick an
ALL / KEYS_ONLY / INCLUDE projection, set per-index throughput on
provisioned tables, and watch the live Creating/Backfilling status while
DynamoDB builds the index. LSIs are listed read-only — that is DynamoDB's
rule, not the tool's — and an existing GSI can be replaced but not edited.
Table management is part of the paid plan, trial included.
Download DynoTable to inspect and query your indexes directly, and try the single-table design planner to turn a list of access patterns into a PK/SK/GSI layout before you commit to one.





