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

  1. GSI vs LSI — repartitioned reads vs same-hash alternate sort; creation-time rules and the 10 GB LSI collection cap.
  2. Index projectionsALL, KEYS_ONLY, INCLUDE; storage and write size trade-offs.
  3. Sparse indexes — omit the index key on cold rows so only the hot subset is copied.
  4. Key overloading — pack multiple access patterns into one GSI with disciplined key shapes.
  5. GSI eventually consistent — read-your-writes failures and UI patterns that need base-table reads.
  6. GSI throttles base table — under-provisioned index capacity blocking parent writes.
0 of 8 readQuiz

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.

Table: orders — PK on customer, GSI "status-placed" on status
fieldvaluenote
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 (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 writes0 GSIs1 GSI2 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 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 , 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 patternReach forWhy
Query by an attribute that isn't the table keyGSIThe only index that repartitions; can be added to a live table anytime
Second sort order in the same partition, strong readsLSIShares table capacity and supports ConsistentRead; creation-time only, 10 GB collection cap
Query a small hot subset of a huge tableSparse GSIOnly items carrying the index key are copied, so the index stays tiny and cheap
Narrow a result set that is already one cheap QueryFilter expressionNo copy to maintain, but you still pay to read every discarded item
A one-off question you will never ask againNo indexA 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 INCLUDE attributes across all of a table's indexes; KEYS_ONLY and ALL projections don't count against it.
  • Query and Scan only: GetItem and BatchGetItem don'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, or Binary; 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 a Backfilling flag once the copy starts) and a Query against it is rejected until the status reaches ACTIVE.
  • 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. DescribeTable reports 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 Table settings dialog in DynoTable showing the orders table's key structure with its GSIs.
The Table settings dialog in DynoTable showing the orders table's key structure with its GSIs.

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 filter builder's index picker choosing the status-placed GSI, with the derived Query badge.
The filter builder's index picker choosing the status-placed GSI, with the derived Query badge.

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.