DynamoDB Data Model Explained: Partition Keys, Sort Keys, GSIs, and LSIs

The DynamoDB data model is unlike anything in a relational database. Your partition key and sort key choices determine not just how data is stored, but which queries are even possible. Get them right and DynamoDB is blazing fast at any scale. Get them wrong and you are writing expensive table scans or fighting the database on every query.

What a DynamoDB data model is

In a relational database, you design a normalised schema and then write whatever queries you need. The query planner figures out how to retrieve data efficiently. DynamoDB works the opposite way.

DynamoDB has no query planner. The only efficient operations are:

  • Look up an item by its primary key (partition key, or partition key and sort key)
  • Query items with the same partition key, filtered or sorted by sort key
  • Query a secondary index (GSI or LSI) using its key

Everything else is a Scan, which reads the entire table. Scans are slow, expensive, and do not scale.

This means your data model must be shaped by your access patterns before you write a single line of code. The access patterns come first; the table structure follows.

If you are comparing DynamoDB to a relational option, the RDS vs DynamoDB comparison covers when each database is the right call.

Simple explanation: tables, items, and attributes

If DynamoDB is new to you, here is what each term means before the deeper material starts.

TermPlain EnglishRelational equivalent
TableThe top-level container for all your dataTable
ItemA single record, like one user, one order, or one eventRow
AttributeA field on an item. Only key attributes are required; all others are optionalColumn
Partition keyThe field DynamoDB hashes to decide where to store the item. Must be unique per item if there is no sort key.Primary key
Sort keyAn optional second key field. When present, items with the same partition key are grouped together and sorted by this value. Enables range queries.Composite key component

Concrete example. An orders table might look like this:

Table: Orders

Item 1:
  userId:    "user-abc"          ← partition key
  orderId:   "2025-03-01#ord-1"  ← sort key
  total:     49.99
  status:    "shipped"

Item 2:
  userId:    "user-abc"          ← same partition key
  orderId:   "2025-03-15#ord-2"  ← different sort key
  total:     120.00
  status:    "delivered"
  giftNote:  "Happy Birthday"    ← only this item has this attribute

Notice that giftNote only exists on Item 2. That is perfectly valid. Non-key attributes are entirely flexible — different items can have completely different shapes.

Note

This flexibility is one of DynamoDB’s biggest advantages over relational databases. You never need to run ALTER TABLE to add a new field. Just start writing items with the new attribute.

How DynamoDB data modeling works

The modeling process is more structured than in a relational database. The schema follows the queries, not the other way around. Work through these steps in order:

1. List every access pattern. Write down every way your application will read or write data. Be specific. “Get all orders for a user, sorted by date” is a valid access pattern. “Search orders by keyword” is not a DynamoDB-friendly access pattern.

2. Choose the partition key. The partition key must support your most common access pattern. It must have high cardinality (many distinct values) and distribute traffic evenly. A userId UUID is a good partition key. A status field with three values is not.

3. Decide whether you need a sort key. If any access pattern involves retrieving multiple items that share a parent (all orders for a user, all events for a device), add a sort key. The sort key determines the order of items within a partition and enables range queries.

4. Define the item shape. Plan what attributes each item will carry. Non-key attributes are flexible, but decide now which ones you will need for display, filtering, or GSI projection.

5. Add GSIs only for real query patterns. Each GSI must answer a specific access pattern that the main table key cannot. Do not add GSIs for hypothetical future queries. They add write cost on every item write.

6. Validate the design. Before creating the table, ask: Does any single partition key value concentrate too much traffic? Will any sort key range queries return an unmanageable number of items? Are you projecting only the attributes you need into each GSI?

Analogy

Designing a DynamoDB table is like designing a filing system before you move into a new office. If you know you will always look up files by client name, you set up drawers labelled by client. If you also need to find files by date, you add a date index on the side. But if you just dump everything into one pile and figure out the system later, you spend all your time searching. DynamoDB punishes “figure it out later” the same way.

Partition key

The partition key (also called the hash key) determines which physical partition stores an item. DynamoDB hashes the partition key value and routes the item to a partition based on that hash.

Rules:

  • Every item must have a partition key value
  • If there is no sort key, the partition key must be unique per item
  • The partition key value is immutable. To change it, you must delete and re-create the item.
  • Key attributes can only be String (S), Number (N), or Binary (B)

Good partition keys have high cardinality (many distinct values) and even access distribution (reads and writes spread across many values, not concentrated on a few).

Good partition keysPoor partition keys
userId (UUID)status (few values, creates hot partitions)
orderId (unique per order)date (all today’s writes go to one partition)
deviceId (unique IoT device)country (US traffic dominates)
sessionId (UUID per session)boolean flag (only two possible partitions)

Sort key

Adding a sort key (range key) does two things:

  1. Multiple items can share the same partition key, as long as each has a unique sort key within that partition
  2. Range queries become possible: retrieve items where the sort key begins_with a prefix, falls between two values, or is less than or greater than a given value
Analogy

Think of the partition key as a filing cabinet drawer and the sort key as the label on each folder inside that drawer. You go directly to the right drawer (partition key), then quickly flip to the right folder or range of folders (sort key). Without a sort key, each drawer holds exactly one folder.

Sort key values are often designed to enable multiple query patterns. A timestamp-prefixed ID like 2025-03#ord-abc123 lets you:

  • Get all orders for a user: query by userId
  • Get all orders in March 2025: query begins_with("2025-03")
  • Get the 10 most recent orders: query by userId, sort key descending, limit 10

Choosing good keys

The partition key choice is the most consequential DynamoDB design decision. A poor choice cannot be fixed without recreating the table and migrating all data.

The hot partition problem. Each partition has a throughput ceiling. If your partition key has low cardinality or is monotonically increasing (like a timestamp), most traffic concentrates on a single partition. That partition throttles. Other partitions sit idle. You pay for capacity you cannot use.

Warning

Using a timestamp or auto-increment counter as a partition key is the single most common DynamoDB design mistake. All writes go to the newest partition. The partition throttles under load. Add a UUID, or at minimum a random shard prefix such as shard-4#2025-03-15T10:30:00, to spread writes across multiple partitions.

Fix hot partitions by:

  • Choosing UUIDs or high-cardinality identifiers as partition keys
  • Avoiding timestamps, counters, or status fields as partition keys
  • Adding a random suffix if you must use a sequential value: deviceId#0 through deviceId#9 spreads writes across 10 partitions

Sort key design patterns:

  • Use a timestamp prefix for time-range queries: 2025-03-15T10:30:00#eventId
  • Use a type prefix for heterogeneous items: ORDER#ord-123, RETURN#ret-456
  • Use a hierarchy prefix for parent-child nesting: COMMENT#comment-789

See the DynamoDB Queries page for how Query operations use these key patterns at runtime.

Worked example: modeling an orders table

Here is a realistic end-to-end example for an e-commerce orders table.

Access patterns:

  1. Get all orders for a user, sorted by date (most recent first)
  2. Get a specific order by orderId
  3. Get all orders for a product (to show “who bought this?”)
  4. Get all pending orders for admin review

Primary key design:

  • Partition key: userId (String) — supports patterns 1 and 2
  • Sort key: orderId (String, format: 2025-03-15#ord-abc123) — enables date-range queries

Example item:

{
  "userId":      "user-abc",
  "orderId":     "2025-03-15#ord-abc123",
  "productId":   "prod-789",
  "productName": "Wireless Headphones",
  "total":       79.99,
  "status":      "shipped",
  "createdAt":   "2025-03-15T14:22:00Z"
}

GSI for pattern 3: Query by productId.

  • GSI partition key: productId
  • GSI sort key: orderId
  • Projection: ALL

GSI for pattern 4: Query pending orders.

Pattern 4 (all pending orders) is a poor fit for DynamoDB because status has low cardinality. A better approach: use a sparse index. Add a pendingAt attribute only to pending orders. When an order ships, remove pendingAt. Create a GSI on pendingAt. The GSI only contains pending items, stays small, and avoids hot partitions.

Queries this design enables:

# Pattern 1 — all orders for a user, newest first
aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "userId = :uid" \
  --expression-attribute-values '{":uid": {"S": "user-abc"}}' \
  --scan-index-forward false

# Pattern 2 — specific order by partition + sort key
aws dynamodb get-item \
  --table-name Orders \
  --key '{"userId": {"S": "user-abc"}, "orderId": {"S": "2025-03-15#ord-abc123"}}'

# Pattern 3 — all orders for a product (via GSI)
aws dynamodb query \
  --table-name Orders \
  --index-name ProductOrderIndex \
  --key-condition-expression "productId = :pid" \
  --expression-attribute-values '{":pid": {"S": "prod-789"}}'
Tip

Notice that the sort key value (2025-03-15#ord-abc123) encodes the date as a prefix. This is intentional. DynamoDB sorts lexicographically, so date-prefixed sort keys let you use begins_with and between for time-range queries without scanning the entire partition.

Data types

DynamoDB supports these attribute types:

TypeSymbolDescription
StringSUnicode text; used for most identifiers and text values
NumberNArbitrary precision numbers; stored as strings internally
BinaryBBinary data (base64-encoded in JSON); max 400KB per item
BooleanBOOLtrue or false
NullNULLNull value; absent attributes are generally preferred over NULL
ListLOrdered list of values (like a JSON array); elements can be mixed types
MapMUnordered set of name-value pairs (like a JSON object)
String SetSSUnordered set of unique string values
Number SetNSUnordered set of unique number values
Binary SetBSUnordered set of unique binary values

Key constraint: Only key attributes (partition key and sort key) have enforced types, and they must be String, Number, or Binary. All non-key attributes are schema-flexible. Different items in the same table can carry completely different attributes with different types.

Global Secondary Indexes (GSIs)

A GSI is an alternate view of your table data with a different partition key and optional sort key. It lets you run efficient queries against attributes that are not part of the main table key.

When you write an item to the main table, DynamoDB automatically writes to all GSIs that include that item’s projected attributes.

Key facts about GSIs:

  • Up to 20 GSIs per table
  • Can be created or deleted at any time (unlike LSIs)
  • Are eventually consistent with the main table. There is a brief propagation delay.
  • Only store the attributes you project into them. You choose: keys only, specific attributes, or all.
  • Add write cost. Every item write updates every GSI that covers that item.
# Create a table with a GSI
aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=userId,AttributeType=S \
    AttributeName=orderId,AttributeType=S \
    AttributeName=productId,AttributeType=S \
  --key-schema \
    AttributeName=userId,KeyType=HASH \
    AttributeName=orderId,KeyType=RANGE \
  --global-secondary-indexes '[{
    "IndexName": "ProductOrderIndex",
    "KeySchema": [
      {"AttributeName": "productId", "KeyType": "HASH"},
      {"AttributeName": "orderId", "KeyType": "RANGE"}
    ],
    "Projection": {"ProjectionType": "ALL"}
  }]' \
  --billing-mode PAY_PER_REQUEST

For encryption and access control on DynamoDB tables and indexes, see DynamoDB Security.

Local Secondary Indexes (LSIs)

An LSI uses the same partition key as the main table but a different sort key. It gives you an alternative way to sort and range-query items within a partition without changing the partition key.

Key facts about LSIs:

  • Must be defined at table creation. Cannot be added or removed later.
  • Share the same partition key as the main table
  • Limited to 10GB of data per partition key value
  • Support strongly consistent reads (unlike GSIs which are eventually consistent)
  • Up to 5 LSIs per table
Note

LSIs are less commonly needed than GSIs. Their main advantage is strongly consistent reads on an indexed sort key. If you do not specifically need that, a GSI gives you more flexibility because it can be created or removed as access patterns change over time.

GSI vs LSI

FeatureGSILSI
Partition keyAny attributeSame as main table
Sort keyAny attribute (optional)Different from main table (required)
When createdAnytime, including after table creationAt table creation only. Cannot be added later.
Read consistencyEventually consistent onlyEventually or strongly consistent
Storage limitNo partition-level limit10GB per partition key value
Max per table205
Best forAlternate access patterns on any attributeAlternate sort orders when strongly consistent reads matter

Default to GSIs unless you specifically need strongly consistent reads on an indexed attribute and you know your partition sizes will stay under 10GB.

When to use DynamoDB for this kind of model

DynamoDB’s data model is the right fit when:

  • You know your access patterns upfront. DynamoDB rewards applications with stable, predictable query paths: user profile lookups, session storage, e-commerce order retrieval, IoT telemetry ingestion.
  • You need high-scale key-value or document workloads. Millions of reads and writes per second with single-digit millisecond latency at any scale.
  • Schema flexibility matters. Your items do not all have the same shape, and you do not want to run schema migrations.
  • You are building event-driven architectures. DynamoDB Streams can trigger Lambda functions on every item change, which is a natural fit for event-driven systems.
Poor fit

DynamoDB is a poor fit when you need ad-hoc queries over arbitrary columns, your application requires complex multi-table joins, query patterns are unknown or change frequently, or you need full ACID transactions across many tables with complex SQL logic. In those cases, a relational database is the right tool.

In those cases, RDS vs DynamoDB walks through the decision in detail.

For a broader view of which AWS database or storage service to pick, see Choosing the Right AWS Storage Service.

DynamoDB vs RDS for data modeling

The question is not just “which database is better.” It is “which data model fits your application.”

FactorDynamoDBRDS (PostgreSQL / MySQL)
Schema designAccess patterns first; schema derived from queriesNormalised schema first; queries adapt to it
Query flexibilityKey-based only; scans are expensiveAny SQL query; optimizer handles it
JoinsNot supported; denormalise or use GSIsFull JOIN support
TransactionsACID across up to 100 itemsFull ACID across any number of rows
ScalingAutomatic, horizontal, to any sizeVertical scale or read replicas
Best forKnown access patterns, high throughput, flexible schemaAd-hoc queries, relational data, complex transactions

The DynamoDB Overview covers throughput modes, consistency options, and Global Tables. Useful context if you are evaluating DynamoDB as a whole, not just the data model.

Common mistakes

  1. Designing the schema before identifying access patterns. If you normalise data like a relational database and then try to query it, you will be forced into expensive Scans or many GSIs that only partially work.
  2. Choosing a low-cardinality partition key. A partition key with few distinct values (status, boolean, country) concentrates writes onto a small number of partitions. Those partitions throttle while others sit idle.
  3. Using a timestamp or auto-increment as a partition key. All current writes go to the newest partition. This is the most common cause of hot partitions. Use a UUID or add a shard prefix.
  4. Overusing GSIs. Every GSI adds write cost. Each item write updates every GSI that covers that item. Create GSIs for real, recurring queries, not for hypothetical future access patterns.
  5. Storing large blobs in DynamoDB items. Items are limited to 400KB including all attribute names and values. Store images, PDFs, or large JSON in Amazon S3 and keep the S3 object key in DynamoDB.
  6. Treating Query like SQL filtering. A DynamoDB Query retrieves items by primary key. FilterExpression only removes items after they are read; it does not reduce read cost. Use key design and GSIs to filter, not FilterExpressions on non-key attributes.
  7. Running Scans in production. Scans read every item in the table. On a large table this is slow, expensive, and can consume your entire provisioned throughput. Design your keys and indexes so that Scans are never needed for normal operations.

Frequently asked questions

What is the difference between a partition key and a sort key in DynamoDB?

The partition key determines which physical partition stores the item. DynamoDB hashes the value and routes the item to a partition based on that hash. The sort key orders items within that partition and enables range queries (begins_with, between, less than, greater than). The partition key is required; the sort key is optional. When you add a sort key, multiple items can share the same partition key as long as their sort key values differ.

When do I need a GSI in DynamoDB?

Add a GSI when you have a real, recurring query pattern that the table primary key cannot answer. For example, if your table has partition key userId but you also need to query by productId to find all users who bought a product, you need a GSI with productId as the partition key. Do not create GSIs speculatively. Every GSI adds write cost because DynamoDB writes to all GSIs on every item write.

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

A GSI (Global Secondary Index) can use any attributes as its partition key and sort key, can be added or deleted at any time, and is eventually consistent with the main table. An LSI (Local Secondary Index) must use the same partition key as the table but a different sort key, must be defined at table creation and cannot be added later, and supports strongly consistent reads. LSIs also have a 10GB limit per partition key value. Most teams default to GSIs unless they specifically need strongly consistent indexed reads.

How do I avoid hot partitions in DynamoDB?

Choose partition keys with high cardinality (many distinct values) and even access distribution across those values. Avoid timestamps, boolean flags, or low-cardinality status fields as partition keys because they concentrate writes. If you must use a near-sequential value, add a random suffix or shard prefix (for example, userId#0 through userId#9) to spread writes across multiple logical partitions.

Should I use DynamoDB or RDS for my data model?

Use DynamoDB when your access patterns are well-defined and key-based, you need high-scale throughput with single-digit millisecond latency, and your data fits a document or key-value model. Use RDS when you need ad-hoc SQL queries, complex joins, full ACID transactions across many tables, or a schema where query patterns are not known upfront. The key question is: do you know exactly how you will query your data? If yes, DynamoDB is a strong option. If no, start with RDS.

Last verified: 5 April 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.