Single-Table Design & Secondary Indexes

3.One Table to Rule the Marketplace

M

In this chapter

We'll build a real single-table design — several entity types sharing one partition key as an item collection — and add a Global Secondary Index to answer a genuinely different question cheaply.

10–12 min

The Problem in Real Life

Sarah's first instinct is the same one that worked for MongoDB: a Customers table, an Orders table, a Sellers table, each its own thing. Then she needs one screen to show a customer's profile and their recent orders together, and realizes that's two separate Query calls to two separate tables — exactly the kind of extra round trip DynamoDB's whole design was supposed to avoid.

Mike asks the question that changes her whole approach.

M

Why do these need to be different tables at all?

Mike

Many Small Tables vs. One Table, Many Entities

One table, several entity types

A customer's profile and their orders, deliberately stored together, told apart by the sort key.

An item collection answers in one request

Every item sharing a partition key comes back from a single Query — no separate round trip per entity type.

A GSI is the same data, indexed differently

A genuinely different access pattern (by status, not by customer) answered cheaply, without a Scan.

Denormalization makes it work

Whatever an access pattern needs sits directly on the items that answer it — the same trade-off from the MongoDB Act, now DynamoDB's own.

Single-Table Design & Secondary Indexes

Single-table design is the real DynamoDB answer: instead of one table per kind of entity, GreenMart deliberately stores several genuinely different kinds of items — a customer's profile, their orders, maybe their reviews — in one shared table, using the partition and sort keys to tell them apart.

Three genuinely different kinds of items — a customer profile and two orders — share one partition key. The sk prefix (PROFILE, ORDER#...) is what tells them apart. One single Query on that partition key now returns the profile and both orders together, in one round trip — this is what an item collection actually means: every item sharing a partition key, deliberately, so the questions GreenMart asks most often only need one request.

One Partition, Several Kinds of Items — An Item Collection
PutItem("Marketplace", { pk: "CUSTOMER#mike01", sk: "PROFILE", name: "Mike", tier: "gold" })
PutItem("Marketplace", { pk: "CUSTOMER#mike01", sk: "ORDER#ord-1", total: 12.5 })
PutItem("Marketplace", { pk: "CUSTOMER#mike01", sk: "ORDER#ord-2", total: 6 })
Query("Marketplace", { pk: "CUSTOMER#mike01" })

The main table only ever answers "this customer's stuff." A Global Secondary Index (GSI) is genuinely the same items, indexed by a completely different key — here, status instead of pk — letting GreenMart ask "every shipped order, across every customer" as a cheap, indexed lookup instead of the Scan that question would otherwise force.

A Global Secondary Index — A Different Question, Answered Cheaply
CreateTable("Marketplace", {
partitionKey: "pk", sortKey: "sk",
globalSecondaryIndexes: { StatusIndex: { partitionKey: "status", sortKey: "sk" } }
})
PutItem("Marketplace", { pk: "CUSTOMER#mike01", sk: "ORDER#ord-1", status: "shipped" })
PutItem("Marketplace", { pk: "CUSTOMER#sarah02", sk: "ORDER#ord-9", status: "shipped" })
Query("Marketplace", { status: "shipped" }, { indexName: "StatusIndex" })

This only works because of denormalization the same way it did back in the MongoDB Act — the customer's profile isn't the single source of truth referenced by every order; whatever a given access pattern needs sits directly on the items that answer it. A Local Secondary Index (LSI) is the narrower sibling of a GSI: it still shares the table's own partition key, but offers an alternate sort key for ordering items within the same partition differently — useful when the questions all stay "within one customer" but need a different sort order, not a genuinely different partition.

Single-table design isn't the default because it's simpler — it's usually harder to read at a glance than one table per entity. It earns its place because it turns GreenMart's most common questions into single, cheap requests, at the cost of a design that has to be planned around real access patterns from the start, exactly like the last chapter argued.

Key Takeaway

An item collection is every item sharing a partition key, on purpose — different entity types stored together specifically so the questions asked most often need only one request, not several.

Why This Matters

This is the concrete shape most real, production DynamoDB tables actually take — not one table per entity, but one carefully designed table (sometimes a small handful) serving an entire application's access patterns. The capacity and cost chapter ahead assumes this same shape: fewer, well-designed tables, not many small ones.

GreenMart's marketplace can now answer "this customer's everything" and "every shipped order, anywhere" as two genuinely cheap requests. Neither of those requests has said anything yet about what happens when two of them try to change the same item at once.

Next