Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Amazon DynamoDB Data Modeling for Developers

By Kokil Thapa | Last reviewed: September 2026

Amazon DynamoDB data modeling for developers is not SQL with fewer joins. You design around how the application reads and writes data, then choose partition keys, sort keys, and indexes to match those paths. Teams coming from PostgreSQL for Laravel developers often hit throttling, hot partitions, or costly scans because they normalized first. This guide walks through the mental model, concrete item shapes, and the trade-offs you will face on real projects—including when DynamoDB is the wrong tool.

How is Amazon DynamoDB data modeling different from relational design?

In MySQL or PostgreSQL, you start with entities and foreign keys. DynamoDB flips that order. You start with questions like "fetch all orders for user X" or "list inventory by warehouse." Each question becomes an access pattern with a known key structure.

DynamoDB is a key-value store with optional sort keys inside each partition. There are no joins at query time. If two record types relate, you either duplicate data across items or run multiple queries and assemble results in application code.

That shift affects cost and latency directly. A well-modeled table serves reads in single-digit milliseconds at predictable RCU/WCU. A poorly modeled one triggers Scan operations that burn capacity and time out under load.

Two Design Starting PointsRelational (SQL)Entities → TablesFK joins at read timeNormalize to reduce dupesDynamoDB (NoSQL)Access patterns firstKeys + GSIs per queryDenormalize by designMindset shiftShared Step: Document Every Read/Write PathWho reads it? How often? Latency budget? Consistency needs?Map each path to Query, GetItem, or BatchGet
Amazon DynamoDB data modeling for developers begins with access patterns, not entity diagrams.

On a production Laravel application I maintained, the team kept PostgreSQL as the system of record. DynamoDB backed high-volume session tokens and webhook deduplication. That split worked because each store matched its access pattern. For greenfield API development, document those patterns before you pick the database.

The access-pattern worksheet

Before writing CloudFormation or CDK, list patterns in a table. Example for an eCommerce order service:

PatternOperationKey shapeFrequency
Get order by IDGetItemPK=ORDER#idHigh
List user ordersQueryPK=USER#id, SK begins ORDER#High
Orders by statusQuery on GSIGSI1PK=STATUS#pendingMedium
Inventory by SKUGetItemPK=SKU#idHigh

If a pattern cannot map to a key condition, redesign the item or add a GSI. Scans are the escape hatch—and they should stay rare.

What are partition keys and sort keys in DynamoDB?

The partition key (PK) determines which physical partition stores your item. DynamoDB hashes the PK value and routes the request. All items sharing one PK live in the same partition and sort by the sort key (SK).

Choose PK values that spread load evenly. A PK of STATUS#pending for every open order creates a hot partition. Prefer high-cardinality prefixes like USER#<uuid> or ORDER#<uuid>.

Sort keys enable range queries within a partition. SK=ORDER#2026-09-08#ord-991 lets you fetch one order or all orders for a user between two dates with BETWEEN.

Composite Key LayoutTable: CommerceStoreUser Order ItemPK: USER#u42SK: ORDER#ord99Inventory ItemPK: INV#SKU-001SK: WH#ktmSame partition = one Query callAll USER#u42 orders share PK, sorted by SK prefixHigh-cardinality PK avoids hot partitions
Partition and sort keys are the foundation of Amazon DynamoDB data modeling for developers.

Overloaded keys and entity type prefixes

Single-table design stores multiple entity types in one table. Prefix PK and SK values so items are distinguishable:

// Order header item
{
  "PK": "USER#7f3a",
  "SK": "ORDER#2026-09-08T14:30:00Z#ord-991",
  "entityType": "Order",
  "status": "pending",
  "totalNpr": 4500,
  "gsi1pk": "STATUS#pending",
  "gsi1sk": "2026-09-08T14:30:00Z"
}

// Line item nested in same partition
{
  "PK": "ORDER#ord-991",
  "SK": "LINE#1",
  "entityType": "LineItem",
  "sku": "SKU-001",
  "qty": 2
}

The entityType attribute helps unmarshaling code route items to the right struct. Use a JSON formatter during design reviews so the team agrees on shapes before deployment.

How do you model one-to-many relationships in DynamoDB?

Relational databases use join tables. DynamoDB offers three practical patterns.

  1. Adjacency list: Parent and children share a PK; children differ by SK. One Query returns the full tree.
  2. Materialized aggregation: Store child collections as a list or map inside the parent item—fine for small, bounded sets.
  3. Duplicate items (index overloading): Write the same logical record twice with different PK/SK pairs so two access patterns each get a direct query.

For a booking platform like those in my Adventure Third Pole Trek portfolio work, "list bookings by customer" and "list bookings by date" both need fast paths. Duplicate items with GSI keys cost extra writes but eliminate scan-and-filter logic.

Write path with duplicate items

import { DynamoDBClient, TransactWriteItemsCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({ region: "ap-south-1" });

await client.send(new TransactWriteItemsCommand({
  TransactItems: [
    {
      Put: {
        TableName: "Bookings",
        Item: {
          PK: { S: "CUSTOMER#cust-42" },
          SK: { S: "BOOKING#2026-10-01#bk-77" },
          gsi1pk: { S: "DATE#2026-10-01" },
          gsi1sk: { S: "BOOKING#bk-77" },
          status: { S: "confirmed" }
        }
      }
    },
    {
      Put: {
        TableName: "Bookings",
        Item: {
          PK: { S: "BOOKING#bk-77" },
          SK: { S: "METADATA" },
          customerId: { S: "cust-42" },
          tripDate: { S: "2026-10-01" }
        }
      }
    }
  ]
}));

TransactWriteItems keeps both copies consistent. If one write fails, neither lands—a pattern worth copying from relational transaction thinking.

Adjacency List Query FlowApp RequestGet order + linesQueryPK = ORDER#ord-991Single RTTHeader + linesItems Returned in One PartitionSK: METADATASK: LINE#1SK: LINE#2SK: LINE#3No JOIN — assemble in application codePaginate with LastEvaluatedKey if result set is large
One-to-many relationships in DynamoDB use shared partition keys and hierarchical sort keys.

When should you use GSIs versus LSIs in DynamoDB?

Global Secondary Indexes (GSIs) have their own partition key and optional sort key. They can differ entirely from the base table. Local Secondary Indexes (LSIs) share the base table PK but use an alternate SK—only creatable at table creation time.

In practice, GSIs carry most alternate-access-pattern load. You pay per-GSI write amplification: each base-table write may trigger a GSI write. Budget for that in WCU planning.

FeatureGSILSI
Key schemaIndependent PK + optional SKSame PK, alternate SK
Create timingAnytime (async backfill)Only at table creation
CapacitySeparate RCU/WCU or on-demandShares table throughput
ConsistencyEventually consistent reads onlyStrongly consistent option
Best forAlternate lookup dimensionsAlternate sort on same entity group

Index overloading puts multiple entity types on one GSI by varying PK/SK prefixes. A GSI named GSI1 might serve "orders by status," "users by email," and "products by category"—each pattern uses distinct prefix values in gsi1pk and gsi1sk.

GSI design checklist

  • Project only attributes the query needs—smaller items mean lower RCU.
  • Avoid low-cardinality GSI partition keys (same hot-partition risk as the base table).
  • Limit GSI count; each adds write cost and operational surface.
  • Test with production-scale cardinality before launch.

For teams building on AWS alongside ECS on Fargate or a data lake on S3, DynamoDB often sits in the hot path while S3 holds analytics archives. Keep OLTP patterns in DynamoDB; export to S3 via DynamoDB Streams and AWS Glue for reporting.

What are common DynamoDB data modeling mistakes?

These show up repeatedly when developers treat DynamoDB like a document store with flexible ad-hoc queries.

Scan-driven features. Admin search across arbitrary fields without a supporting index will not scale. Add OpenSearch or design a dedicated GSI per filter dimension.

Unbounded item growth. Appending to a list attribute on every event creates items that exceed the 400 KB limit. Use a write sharding pattern: SK=EVENT#<shard#>#<timestamp> across fixed shard counts.

Monotonic PKs. Time-based PKs like DATE#2026-09-08 concentrate writes on one partition per day. Add a random suffix: DATE#2026-09-08#<uuid-mod-10>, then query all shards in parallel.

Ignoring TTL. Session data, idempotency keys, and cache rows should carry a ttl attribute. DynamoDB deletes expired items at no direct charge—cheaper than manual cleanup jobs.

Hot Partition FixAnti-PatternPK = STATUS#pendingAll writes hit one partitionThrottling under loadFix: Write ShardingPK = STATUS#pending#shard-NN parallel partitionsScatter writes, gather readsCapacity Planning ReminderOn-demand suits spiky traffic; provisioned saves cost at steady loadUse CloudWatch ConsumedWriteCapacityUnits to tune shard count
Hot partitions are a frequent failure mode in Amazon DynamoDB data modeling for developers.

MongoDB fits schema-flexible workloads where relational modeling feels wrong—see vector databases for PHP developers for another non-relational angle. DynamoDB wins when you need predictable latency, AWS-native integration, and serverless scaling without cluster ops.

How do you implement DynamoDB data modeling in a real project?

Walk through a concrete workflow. Assume a multi-vendor marketplace similar in shape to directory platforms I have shipped.

Step 1: Capture access patterns

Workshop with product and backend leads. Export the list to your ticket system. Every story that implies "search everything" gets flagged for index design or a search engine.

Step 2: Draft the single-table layout

/*
 * MarketplaceTable — access patterns:
 * 1. Get vendor profile by ID
 * 2. List products by vendor
 * 3. List products by category (browse page)
 * 4. Get product detail by slug
 */

// Vendor profile
{ PK: "VENDOR#v-12", SK: "PROFILE", name: "...", rating: 4.7 }

// Product under vendor partition
{ PK: "VENDOR#v-12", SK: "PRODUCT#p-88", title: "...", priceNpr: 1200,
  gsi1pk: "CATEGORY#electronics", gsi1sk: "PRODUCT#p-88",
  gsi2pk: "SLUG#wireless-earbuds", gsi2sk: "PRODUCT#p-88" }

Step 3: Define infrastructure as code

Resources:
  MarketplaceTable:
    Type: AWS::DynamoDB::Table
    Properties:
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: PK
          AttributeType: S
        - AttributeName: SK
          AttributeType: S
        - AttributeName: gsi1pk
          AttributeType: S
        - AttributeName: gsi1sk
          AttributeType: S
        - AttributeName: gsi2pk
          AttributeType: S
        - AttributeName: gsi2sk
          AttributeType: S
      KeySchema:
        - AttributeName: PK
          KeyType: HASH
        - AttributeName: SK
          KeyType: RANGE
      GlobalSecondaryIndexes:
        - IndexName: GSI1
          KeySchema:
            - AttributeName: gsi1pk
              KeyType: HASH
            - AttributeName: gsi1sk
              KeyType: RANGE
          Projection:
            ProjectionType: INCLUDE
            NonKeyAttributes: [title, priceNpr, vendorId]
        - IndexName: GSI2
          KeySchema:
            - AttributeName: gsi2pk
              KeyType: HASH
            - AttributeName: gsi2sk
              KeyType: RANGE
          Projection:
            ProjectionType: KEYS_ONLY

Step 4: Load-test before launch

Use AWS SDK retry logic with exponential backoff. Run awslabs/dynamodb-benchmark or Artillery scripts against staging. Watch ThrottledRequests and p99 latency. Fix key design before go-live—not after a Dashain traffic spike.

For enterprise application development, pair DynamoDB with proper testing and optimization. Seed realistic NPR-priced catalog data and validate pagination behavior under 10k-item partitions.

Official references worth bookmarking: the DynamoDB best practices guide and the core components documentation from AWS. For single-table theory, the Amazon builders library articles on adjacency lists remain the clearest explanation of why one table beats many.

Key Takeaways

  • List every access pattern before choosing PK, SK, and GSI shapes—scans are a design failure, not a workaround.
  • Use high-cardinality partition keys; shard writes when natural keys cluster on one value.
  • Denormalize and duplicate items when two query paths both need single-digit millisecond latency.
  • Prefer GSIs for alternate lookups; reserve LSIs for rare cases needing strong consistency on an alternate sort.
  • Set TTL on ephemeral rows and use TransactWriteItems when duplicate items must stay in sync.
  • Load-test partition distribution early; hot partitions cause throttling that no amount of on-demand billing fully hides.

People Also Ask

Is single-table design required for DynamoDB?

No. Single-table design reduces table sprawl and fits many microservice boundaries, but multi-table setups are valid when access patterns are isolated. Start single-table when patterns share infrastructure; split only when lifecycle, backup, or IAM boundaries demand it.

How is DynamoDB pricing affected by data modeling?

Every GSI doubles write cost for indexed attributes. Item size drives RCU per read. Efficient keys and sparse projections directly lower monthly bills—often more than switching between on-demand and provisioned capacity.

Can DynamoDB replace PostgreSQL or MySQL?

Not for general-purpose OLTP with ad-hoc reporting. DynamoDB excels at known, high-volume access paths. Keep relational databases for complex transactions, flexible reporting, and workloads that need multi-row ACID across unrelated entities.

What tools help design DynamoDB schemas?

NoSQL Workbench from AWS visualizes single-table layouts and generates sample data. DynamoDB Local runs offline for integration tests. Pair them with IaC templates and contract tests that assert each access pattern hits Query or GetItem.

Build the Right Data Layer for Your Next Project

Amazon DynamoDB data modeling for developers rewards upfront discipline. Map access patterns, design keys that spread load, and treat indexes as first-class cost centers—not afterthoughts. Whether you are adding a high-throughput cache beside Laravel, building a marketplace API, or modernizing webhook processing, the same rules apply: query-driven design beats entity-driven hope.

Need help choosing between DynamoDB, PostgreSQL, and Redis for your stack—or implementing the access patterns correctly? Review our marketplace directory portfolio work and custom software development services, then contact us to talk through your data layer before the first table goes live.

Frequently Asked Questions

Listing every access pattern first, then designing composite keys and GSIs so each query uses GetItem or Query—never a table scan. Denormalize aggressively; one item can hold an order header and its line items.

In MySQL or PostgreSQL you start with entities and foreign keys. DynamoDB flips that order: you start with questions like fetch all orders for user X. Each question becomes an access pattern with a known key structure. There are no joins at query time—related data is duplicated across items or assembled in application code after multiple queries. A well-modeled table serves reads in single-digit milliseconds at predictable RCU/WCU. A poorly modeled one triggers Scan operations that burn capacity and time out under load. On a production Laravel application I maintained, PostgreSQL stayed the system of record while DynamoDB backed high-volume session tokens and webhook deduplication because each store matched its access pattern.

The partition key determines which physical partition stores your item—DynamoDB hashes the PK and routes the request. All items sharing one PK live in the same partition and sort by the sort key. Choose PK values that spread load evenly; a PK of STATUS#pending for every open order creates a hot partition. Prefer high-cardinality prefixes like USER#uuid or ORDER#uuid. Sort keys enable range queries within a partition—SK=ORDER#2026-09-08#ord-991 lets you fetch one order or all orders for a user between two dates with BETWEEN. Partition and sort keys are the foundation of every DynamoDB schema.

Before writing CloudFormation or CDK, list every read and write path in a table with columns for pattern, operation, key shape, and frequency. Example: Get order by ID uses GetItem with PK=ORDER#id at high frequency; list user orders uses Query with PK=USER#id and SK begins ORDER# at high frequency. If a pattern cannot map to a key condition, redesign the item or add a GSI. Scans are the escape hatch and should stay rare. Workshop with product and backend leads, export the list to your ticket system, and flag any story that implies search everything for index design or a dedicated search engine.

Relational databases use join tables; DynamoDB offers three practical patterns. Adjacency list: parent and children share a PK but differ by SK—one Query returns the full tree. Materialized aggregation: store child collections as a list or map inside the parent item, fine for small bounded sets. Duplicate items with index overloading: write the same logical record twice with different PK/SK pairs so two access patterns each get a direct query. For a booking platform, list bookings by customer and list bookings by date both need fast paths. Duplicate items with GSI keys cost extra writes but eliminate scan-and-filter logic. Use TransactWriteItems to keep both copies consistent.

Global Secondary Indexes have their own partition key and optional sort key—they can differ entirely from the base table and can be created anytime with async backfill. Local Secondary Indexes share the base table PK but use an alternate SK—they are only creatable at table creation time and share table throughput. In practice GSIs carry most alternate-access-pattern load. You pay per-GSI write amplification: each base-table write may trigger a GSI write, so budget for that in WCU planning. GSIs offer eventually consistent reads only; LSIs allow a strongly consistent option. Prefer GSIs for alternate lookups; reserve LSIs for rare cases needing strong consistency on an alternate sort within the same entity group.

Index overloading puts multiple entity types on one GSI by varying PK and SK prefixes. A GSI named GSI1 might serve orders by status, users by email, and products by category—each pattern uses distinct prefix values in gsi1pk and gsi1sk. In a marketplace layout, a product item might set gsi1pk to CATEGORY#electronics for browse pages and gsi2pk to SLUG#wireless-earbuds for detail lookups. Project only attributes the query needs—smaller items mean lower RCU. Avoid low-cardinality GSI partition keys because they carry the same hot-partition risk as the base table. Limit GSI count since each adds write cost and operational surface.

No. Single-table design reduces table sprawl and fits many microservice boundaries, but multi-table setups are valid when access patterns are isolated. Start single-table when patterns share infrastructure; split only when lifecycle, backup, or IAM boundaries demand it.

Scan-driven features are the biggest failure mode—admin search across arbitrary fields without a supporting index will not scale; add OpenSearch or design a dedicated GSI per filter dimension. Unbounded item growth from appending to a list attribute on every event creates items that exceed the 400 KB limit—use write sharding with SK=EVENT#shard#timestamp across fixed shard counts. Monotonic PKs like DATE#2026-09-08 concentrate writes on one partition per day—add a random suffix and query all shards in parallel. Ignoring TTL on session data, idempotency keys, and cache rows wastes money on manual cleanup when DynamoDB deletes expired items at no direct charge.

Every GSI doubles write cost for indexed attributes. Item size drives RCU per read. Efficient keys and sparse projections directly lower monthly bills—often more than switching between on-demand and provisioned capacity.

Not for general-purpose OLTP with ad-hoc reporting. DynamoDB excels at known, high-volume access paths with predictable latency and AWS-native integration. Keep relational databases for complex transactions, flexible reporting, and workloads that need multi-row ACID across unrelated entities. On real client projects I have kept PostgreSQL as the system of record while DynamoDB handled high-throughput paths like session tokens and webhook deduplication. MongoDB fits schema-flexible workloads where relational modeling feels wrong. DynamoDB wins when you need serverless scaling without cluster ops and every query path is defined upfront.

NoSQL Workbench from AWS visualizes single-table layouts and generates sample data. DynamoDB Local runs offline for integration tests. Pair them with IaC templates—CloudFormation or CDK—and contract tests that assert each access pattern hits Query or GetItem, never Scan. Use a JSON formatter during design reviews so the team agrees on item shapes before deployment. For load testing before launch, run awslabs/dynamodb-benchmark or Artillery scripts against staging and watch ThrottledRequests and p99 latency. Official references worth bookmarking include the DynamoDB best practices guide and Amazon builders library articles on adjacency lists.

Hot partitions occur when too many reads or writes target one partition key value, causing throttling that on-demand billing cannot fully hide. Avoid low-cardinality PKs like STATUS#pending shared by every open order. Prefer high-cardinality prefixes such as USER#uuid. When natural keys cluster—time-based PKs like DATE#2026-09-08 concentrate writes on one partition per day—add a random suffix like DATE#2026-09-08#uuid-mod-10, then query all shards in parallel. Load-test partition distribution early with realistic catalog data and validate pagination behavior under 10k-item partitions. Fix key design before go-live, not after a traffic spike.

Step one: capture access patterns in a workshop and flag any open-ended search requirements. Step two: draft a single-table layout with prefixed PK and SK values and entityType attributes for unmarshaling—vendor profiles under VENDOR#id, products nested under vendor partitions with GSI keys for category browse and slug lookup. Step three: define infrastructure as code with PAY_PER_REQUEST billing, attribute definitions for PK, SK, and GSI keys, and INCLUDE or KEYS_ONLY projections sized to each query. Step four: load-test before launch using AWS SDK retry logic with exponential backoff, seed realistic NPR-priced catalog data, and fix throttling before production traffic arrives.

DynamoDB is the wrong choice when your team needs ad-hoc queries, flexible reporting, or admin search across arbitrary fields without pre-designed indexes—those patterns belong in PostgreSQL or OpenSearch. Workloads requiring complex multi-row ACID transactions across unrelated entities should stay relational. Treating DynamoDB like a document store with flexible ad-hoc queries leads to scan-driven features that burn RCU and time out under load. DynamoDB fits when access patterns are known upfront, volume is high, latency must stay in single-digit milliseconds, and AWS-native integration with services like DynamoDB Streams exporting to S3 via Glue matters for your architecture.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: