CloudBrum
CloudBrum · Cloud decision-support platform
AWS Databases

Amazon DynamoDBLast reviewedReading time~12 minLevelBeginner to advanced

Amazon Web Services · Databases · Serverless key-value and document database

DynamoDB is the AWS database you pick when lookups by key must stay fast however big the table gets. You decide up front which questions the application will ask, choose a partition key to match, and AWS spreads the data over as many servers as it takes. There is no server to size or patch, and also no join and no ad hoc query, so the design work moves to the start of the project. When a DynamoDB bill surprises someone, the cause is usually an index nobody needed, a scan inside a loop, or one hot key.

On this page (17 sections)

    Mind map: Amazon DynamoDB at a glance

    Amazon DynamoDB mind map Amazon DynamoDB at a glance: Data model: Tables and items, Partition and sort key, Attributes, no schema; Indexes: Global secondary, Local secondary, Projections; Capacity: On-demand, Provisioned, auto scaling, Table classes; Reads and writes: Eventual or strong reads, Conditional writes, Transactions; Resilience: Point-in-time recovery, On-demand backups, Global tables; Change and cache: Streams, Time to live, DAX; Security: IAM and conditions, KMS encryption, VPC endpoints; Pricing: Read and write units, Storage and indexes, Backups, replication. Amazon DynamoDB at a glance A serverless NoSQL database on AWS: the main building blocks and decisions CloudBrum Cloud decision-support platform CloudBrum Tables and items Partition and sort key Attributes, no schema Data model Global secondary Local secondary Projections Indexes On-demand Provisioned, auto scaling Table classes Capacity Eventual or strong reads Conditional writes Transactions Reads and writes Point-in-time recovery On-demand backups Global tables Resilience Streams Time to live DAX Change and cache IAM and conditions KMS encryption VPC endpoints Security Read and write units Storage and indexes Backups, replication Pricing DynamoDB17-point analysis CloudBrum· 17-point cloud service analysis cloudbrum.com
    CloudBrum DynamoDB at a glance · 17-point analysis Data model Tables and items Partition and sort key Attributes, no schema Indexes Global secondary Local secondary Projections Capacity On-demand Provisioned, auto scaling Table classes Reads and writes Eventual or strong reads Conditional writes Transactions Resilience Point-in-time recovery On-demand backups Global tables Change and cache Streams Time to live DAX Security IAM and conditions KMS encryption VPC endpoints Pricing Read and write units Storage and indexes Backups, replication CloudBrum · cloudbrum.com

    Free to share. The downloaded image includes the CloudBrum link.

    Key features (summary)

    • Fast at any sizeA read or write by key returns in single-digit milliseconds whether the table holds a thousand items or billions. DynamoDB splits data by partition key and adds partitions as the table grows, so you never resize anything.
    • Keys decide everythingEach item has a partition key and, optionally, a sort key. A query names one partition key value and can narrow by sort key, which is why the keys come from your access patterns and not from your entities.
    • Secondary indexesA global secondary index (GSI) gives the same data another key, so a second question such as "orders by status" is also a cheap query. Every index is a copy that you pay to write and to store.
    • Two ways to pay for capacityOn-demand charges per request and needs no planning. Provisioned lets you set read and write capacity, with auto scaling to follow the load, and costs less when traffic is steady and predictable.
    • Conditions and transactionsA conditional write succeeds only if the item is in the state you expect. TransactWriteItems and TransactGetItems group up to 100 actions, across tables, all or nothing, at double the capacity cost.
    • Streams and time to liveA stream records item changes for 24 hours for Lambda or EventBridge Pipes to read. Time to live (TTL) removes items after a timestamp that you store, in the background and without a write charge.
    • Backups and recoveryPoint-in-time recovery restores a table to any second in the last 1 to 35 days, into a new table. On-demand backups and AWS Backup keep copies for as long as you need them.
    • Global tablesReplicate a table to other Regions and write in any of them. The default is eventual consistency between Regions; multi-Region strong consistency, generally available since June 2025, gives a recovery point of zero in return for slower cross-Region writes.

    Basic concepts

    Item
    One record, a set of attributes. An item can be up to 400 KB, and two items in a table don’t have to have the same attributes.
    Partition key
    The attribute DynamoDB hashes to decide where an item lives. It is the one value every Query must name, so it should have many different values spread evenly across your traffic.
    Sort key
    An optional second part of the primary key. Items with the same partition key are stored in sort key order, which is how a query can ask for a range, such as the last ten orders.
    Query and Scan
    A Query reads one partition key value, and is fast and cheap. A Scan reads the whole table or index, and costs the full size of what it touches even if a filter drops most of it.
    Global secondary index (GSI)
    An index with its own partition and sort key. You can add it at any time, it has its own capacity, and reads from it are eventually consistent.
    Local secondary index (LSI)
    An index that shares the table’s partition key but has another sort key. It must be created together with the table, and it limits each partition key value to 10 GB.
    Read and write unit
    The measure of capacity. One write unit writes up to 1 KB, and one read unit reads up to 4 KB with strong consistency. An eventually consistent read costs half, and a transactional read or write costs double.
    On-demand and provisioned
    The two capacity modes. On-demand bills each request. Provisioned bills the capacity you set per hour, used or not.
    Conditional write
    A write that carries a condition, such as attribute_not_exists(orderId). When the condition is false the write is rejected with ConditionalCheckFailedException, which is how you build idempotent writes.
    Stream
    An ordered log of item changes that keeps records for 24 hours. A consumer such as Lambda reads it to react to changes or to copy data elsewhere.
    Time to live (TTL)
    A number attribute that holds an expiry time in epoch seconds. DynamoDB deletes expired items in the background, usually within a few days of the timestamp.
    DAX
    DynamoDB Accelerator, a cache cluster that sits in front of a table and serves repeated reads in microseconds.
    Global table
    A table that DynamoDB replicates across Regions. Every replica accepts writes, and you choose between eventual consistency and multi-Region strong consistency.

    How to create & configure it

    Create a table, an index and safe defaults with the AWS CLI

    1. List the questions firstWrite down every way the application reads data: orders of one customer, open orders by status, one order by ID. Each question becomes a key or an index. Do this before you touch the console.
    2. Choose a partition key that spreadsPick an attribute with many values and even traffic, such as a customer or device ID. A key like status or a date sends most requests to a handful of partitions.
    3. Create the table on-demandStart with on-demand so there is no capacity to guess. Switch to provisioned later, once the traffic pattern is clear and steady.
    4. Add only the indexes you needEach index doubles as a second table to write and store. Project just the attributes the query returns, not all of them.
    5. Write with conditionsUse attribute_not_exists for inserts and a status check for updates. A retried request then can’t overwrite newer data or create a duplicate.
    6. Query by key, never scanUse Query with a key condition. Reserved words such as status and name need a placeholder in ExpressionAttributeNames.
    7. Turn on recovery and delete protectionEnable point-in-time recovery and deletion protection before the first real data arrives. Both are cheap, and you can’t add the recovery history afterwards.
    8. Set time to live for temporary dataStore an expiry timestamp in a number attribute for sessions, carts and tokens, and let DynamoDB do the cleanup.
    9. Watch for throttlingThrottledRequests and ConsumedReadCapacityUnits in CloudWatch tell you about hot keys and capacity limits before a customer does.

    Tip: a local secondary index can only be created with the table, and a table’s keys can’t be changed later. If you got the keys wrong, the fix is a new table and a copy of the data. See the code references for the same setup as Terraform.

    Common issues after configuration

    SymptomLikely causeWhat to check / fix
    Requests are throttledOne partition key value gets far more traffic than the rest (a hot key), a provisioned table is set too low, or on-demand traffic jumped well above the table’s recent peakLook at ThrottledRequests by table and index. Spread the load with a better key or a suffix, raise provisioned capacity, or let the table warm up before a launch.
    The provided key element does not match the schemaA key attribute is missing, has the wrong name or the wrong type (a number sent as a string)Compare the request with describe-table. The keys must match name and type exactly.
    ValidationException: item size exceeds 400 KBA single item grew past the limit, often through a list that keeps growingSplit the data into several items with a sort key, or keep the large part in S3 and store the object key.
    A query returns nothing for an item that existsThe key is case-sensitive, the query hit the wrong index, or the read was eventually consistent and ran straight after the writeCheck the exact key value, name the index, and use a strongly consistent read on the table when you need your own write back. Indexes are always eventually consistent.
    A scan is slow and expensiveThere is no key condition, so every item is read before the filter drops most of themAdd an index for the question you are asking, and use Query. Keep Scan for exports and one-off checks.
    ConditionalCheckFailedExceptionThe condition was false: the item exists, or its status isn’t what the update expectedThis is the guard working. Handle it as a normal result, and read the item again if you need to decide what to do next.
    TransactionCanceledExceptionOne action’s condition failed, an item was in another transaction, or capacity ran outRead CancellationReasons, which lists a reason per action in order, and retry only when the reason is a conflict or throttling.
    An index shows old dataGlobal secondary indexes are updated after the table write, usually within a fraction of a second but not at the same momentRead from the table when freshness matters, or design the flow so a short delay doesn’t matter.
    Expired items still appearTTL deletion is a background job, and the attribute must be a number in epoch secondsFilter on the expiry attribute in your queries, and check that the value is seconds, not milliseconds.
    The bill is higher than expectedIndexes that project every attribute, repeated scans, large items, strongly consistent reads and transactions, or replicated writes in global tablesBreak the bill down by table and index, project fewer attributes, replace scans with queries, and compare on-demand with provisioned for the steady tables.

    Use cases: when and where to use it

    Good fit

    • Applications that read and write by key at high volume, such as carts, sessions, profiles and device state
    • Workloads with traffic that swings from almost nothing to very large, where on-demand capacity fits
    • Serverless back ends built with Lambda and API Gateway, since there are no connections to manage
    • Gaming, ad tech and IoT data that needs single-digit-millisecond reads
    • Systems that need a change stream to drive other work, or to replicate to analytics
    • Multi-Region applications that need writes in more than one Region

    Consider something else

    • Joins, ad hoc SQL and reporting → Amazon RDS or Amazon Aurora
    • Analytics over lots of data → export to S3 and query it with Amazon Athena
    • Free-text search and aggregations → Amazon OpenSearch Service
    • A cache with eviction rules → Amazon ElastiCache (or DAX in front of a DynamoDB table)
    • Cassandra-compatible wide-column workloads → Amazon Keyspaces
    • The same job on other clouds → compare Azure Cosmos DB and Firestore

    Pros: why consider it?

    Why teams choose it

    • Nothing to patch, size or fail over: AWS runs the servers
    • Steady, low latency for key-based reads and writes at any table size
    • On-demand mode removes capacity planning
    • Backups, point-in-time recovery, streams and global tables are built in
    • Works naturally with Lambda, with no connection pools
    • Fine-grained IAM control down to the item and attribute

    Trade-offs to weigh

    • Queries must follow the keys and indexes you designed, so new questions can mean new indexes or a new table
    • No joins, no ad hoc queries and limited aggregation
    • Changing the keys of an existing table means a migration
    • A hot key can throttle one partition while the table as a whole looks quiet
    • Costs follow requests, so scans, indexes and transactions add up
    • Item size is capped at 400 KB, and the data model is unlike SQL, which takes time to learn

    Common threats, vulnerabilities & risks

    RiskSeverityWhy it mattersMitigation
    No point-in-time recoveryHighA bad deployment or a deleted attribute can’t be undone, and an on-demand backup only holds the moment it was taken.Enable point-in-time recovery on every table that holds real data, and test a restore once a quarter.
    Tables open to the whole accountHighA broad dynamodb:* permission lets one compromised role read or delete every table.Give each workload a role with the actions and the table ARNs it needs, and use condition keys such as dynamodb:LeadingKeys for per-user data.
    Deletion without protectionHighA delete-table from a script or a wrong console click removes the data and its history.Turn on deletion protection, and restrict dynamodb:DeleteTable to a small group.
    A hot partition keyMediumMost traffic lands on a few key values, so those partitions throttle while others sit idle.Pick keys with high cardinality, add a suffix or shard number for very busy keys, and use Contributor Insights to find the busy ones.
    Scans in production pathsMediumA scan inside a request handler reads the whole table and slows down and costs more as the data grows.Replace it with a query on a key or an index, and block scans through IAM for the application role.
    Unbounded item growthMediumA list attribute that keeps growing hits the 400 KB limit, and each update is billed for the size of the whole item.Store each entry as its own item under the same partition key, and cap or archive old ones.
    Indexes that project everythingMediumEvery attribute copied to an index is paid for on each write and again in storage.Project only the keys and the attributes the query returns, and delete indexes nobody reads.
    Sensitive data without a key policyLow–MedThe default encryption uses an AWS owned key, which can’t show who used it.Use a customer managed KMS key for regulated data, and watch its use in CloudTrail.
    Streams without a failure planLow–MedA consumer that keeps failing can fall behind, and records are gone after 24 hours.Alarm on iterator age, add a failure destination, and keep consumers idempotent.

    Performance optimisation

    • Choose a partition key with many values and an even spread of traffic, so no partition becomes the bottleneck.
    • Model for your queries: put the items you read together under one partition key, and read them with a single Query.
    • Use BatchGetItem and batch writes for many items, and retry the unprocessed ones with exponential backoff and jitter.
    • Keep items small. Large items cost more units per request, so store big blobs in S3 and keep the key.
    • Use eventually consistent reads unless the code truly needs its own write back, since they use half the capacity.
    • Add DAX for read-heavy, repeated lookups that need microsecond answers, and measure the hit rate before you pay for it.
    • Use projection expressions to return only the attributes you need, which cuts the data sent, though not the capacity used.
    • Add a random suffix to the keys of a very hot item, and read all the suffixes to combine the result.

    Reduce monthly cost

    FinOps is the habit of managing cloud spend together with engineering. These are the FinOps checks that matter most for Amazon DynamoDB.

    • Compare on-demand and provisioned for each table. Steady, predictable tables are usually cheaper provisioned with auto scaling.
    • Keep infrequently read tables in Standard-IA, which has lower storage and higher request prices.
    • Project fewer attributes into indexes, and delete indexes that no query uses.
    • Replace scans with queries, since a scan pays for every item it touches, not only the ones it returns.
    • Use eventually consistent reads where the code allows, at half the cost of strong ones.
    • Set TTL on sessions, logs and temporary data, because deleting expired items through TTL has no write charge.
    • Compress or trim large attributes, and move large payloads to S3.
    • Limit global table replicas to the Regions you need, since each replica pays for replicated writes.
    • Review backup and PITR storage and delete on-demand backups you no longer need.
    • Tag tables by team and put a budget alert on DynamoDB spend, so a runaway job shows up the same day.

    Figures here come from AWS's pricing page and published guides, read in October 2026. Prices differ by Region and sources disagree on some rates, so confirm on the live page (see Pricing).

    Security optimisation

    • Give each workload an IAM role with only the table and index ARNs and actions it needs, not dynamodb: on .
    • Use condition keys such as dynamodb:LeadingKeys and dynamodb:Attributes to limit a user to their own items.
    • Use a customer managed KMS key for sensitive tables, and review who may use it. Encryption at rest is always on.
    • Reach DynamoDB through a gateway VPC endpoint, with an endpoint policy that names the tables, so traffic stays off the internet.
    • Add a resource-based policy to block access from outside your organisation or network.
    • Turn on deletion protection and point-in-time recovery for production tables.
    • Record API calls with CloudTrail, including data events for tables that need an audit of reads and writes.
    • Keep secrets and card numbers out of items. Store a token or a reference, and keep the secret in Secrets Manager.
    • Alert on DeleteTable, UpdateContinuousBackups and policy changes.
    • Review IAM Access Analyzer findings for tables shared with other accounts.

    Governance & compliance

    Guardrails

    • A shared Terraform module for tables that always sets point-in-time recovery, deletion protection, a KMS key and tags
    • A service control policy that blocks dynamodb:DeleteTable and dynamodb:DeleteBackup outside a break-glass role
    • Naming rules for tables and key attributes, with the access patterns documented next to the table definition
    • A review step for new global secondary indexes, with the query that needs each one

    Continuous checks (AWS Config, Security Hub and your own scripts)

    • A scheduled script that lists tables without point-in-time recovery, deletion protection or time to live where it is expected (the C# tab does this)
    • AWS Config rules and Security Hub controls for DynamoDB backups and encryption, where your standard includes them
    • Alarms on ThrottledRequests, SystemErrors and stream iterator age
    • A CloudTrail alert for table deletion and policy changes
    • A monthly report of indexes with no reads

    AWS CloudTrail records who created, changed or deleted tables, AWS Config keeps the configuration history, AWS Backup can hold copies under a policy, and AWS Artifact holds compliance reports. AWS runs the service, replicates the data across Availability Zones and encrypts it at rest. The key design, who may read which items, how long backups last and what is stored in the first place are yours.

    Reduce carbon footprint

    • Use on-demand or well-tuned auto scaling, so you don’t hold capacity that nobody uses.
    • Replace scans with queries, which read far less data for the same answer.
    • Delete unused indexes, old backups and expired data with TTL.
    • Limit global table replicas to the Regions your users need.
    • If data rules and latency allow, keep the table in a Region whose power grid is cleaner.
    • The AWS carbon tool reports per account and does not list DynamoDB on its own, so quote no DynamoDB figure.

    Pricing

    Purchase options

    On-demand Per read and write request

    About $0.625 per million write units and $0.125 per million read units in US East (N. Virginia), Standard class. A write unit covers 1 KB and a strongly consistent read unit 4 KB. Eventually consistent reads cost half, and transactions cost double.

    Provisioned Per capacity unit per hour

    You pay for the read and write capacity you set, used or not, with auto scaling to follow the load and reserved capacity for steady tables.

    Storage About $0.25 per GB-month

    The first 25 GB are free. Standard table class. Standard-IA lists lower storage (about $0.10 per GB-month) and higher request prices, for tables that are rarely read.

    Extras Backups, streams, global tables, DAX

    Point-in-time recovery and on-demand backups by GB, stream reads above the free allowance, replicated writes in every global table Region, and DAX nodes by the hour.

    What you actually pay for

    • Read and write request units, or provisioned capacity units per hour
    • Stored data, including indexes
    • Point-in-time recovery and on-demand backup storage
    • Replicated writes for each global table replica
    • DAX nodes, stream reads above the free allowance, and data transfer between Regions

    AWS lists an always-free allowance of 25 GB of storage and 25 provisioned read and write capacity units, and rates differ by Region. See the official Amazon DynamoDB pricing page and the AWS Pricing Calculator.

    Important code references

    An orders application: an on-demand Orders table keyed by customer and order, an index for open orders by status, a Stock table, time to live, point-in-time recovery, deletion protection and a stream. The CLI tab builds it, writes with a condition, queries the table and the index, and places an order and reduces the stock in one transaction. Terraform describes the same table; the Python tab shows an insert that is safe to retry, a paged query and the transaction; and the C# tab audits every table in the Region for missing recovery, protection and wasteful indexes. Replace the Region and names with your own.

    # Placeholders: replace the Region with your own. The table and attribute names are examples.
    REGION=ap-south-1
    TABLE=Orders
    
    # 1. The orders table. Reading "all orders of one customer" is one Query on customerId, newest or oldest first on orderId.
    #    On-demand billing has no capacity to plan, and deletion protection blocks an accidental delete-table.
    #    The index answers a second question (open orders by status) and copies only the total, not the whole item.
    aws dynamodb create-table --region $REGION --table-name $TABLE \
      --attribute-definitions AttributeName=customerId,AttributeType=S AttributeName=orderId,AttributeType=S AttributeName=status,AttributeType=S AttributeName=createdAt,AttributeType=S \
      --key-schema AttributeName=customerId,KeyType=HASH AttributeName=orderId,KeyType=RANGE \
      --billing-mode PAY_PER_REQUEST --deletion-protection-enabled \
      --global-secondary-indexes 'IndexName=by-status,KeySchema=[{AttributeName=status,KeyType=HASH},{AttributeName=createdAt,KeyType=RANGE}],Projection={ProjectionType=INCLUDE,NonKeyAttributes=[total]}' \
      --tags Key=team,Value=orders
    aws dynamodb wait table-exists --region $REGION --table-name $TABLE
    
    # 2. A second small table for stock levels, used in the transaction below.
    aws dynamodb create-table --region $REGION --table-name Stock \
      --attribute-definitions AttributeName=sku,AttributeType=S --key-schema AttributeName=sku,KeyType=HASH --billing-mode PAY_PER_REQUEST
    aws dynamodb wait table-exists --region $REGION --table-name Stock
    aws dynamodb put-item --region $REGION --table-name Stock --item '{"sku": {"S": "sku-9"}, "onHand": {"N": "5"}}'
    
    # 3. Let DynamoDB delete expired items itself, and keep 35 days of recovery history.
    aws dynamodb update-time-to-live --region $REGION --table-name $TABLE --time-to-live-specification Enabled=true,AttributeName=expiresAt
    aws dynamodb update-continuous-backups --region $REGION --table-name $TABLE \
      --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true,RecoveryPeriodInDays=35
    
    # 4. Write an order only if it does not exist yet. Run it twice: the second call is refused, which is what makes a retry safe.
    cat > order.json <<JSON
    {"customerId": {"S": "c-100"}, "orderId": {"S": "o-0001"}, "status": {"S": "PLACED"}, "createdAt": {"S": "2026-11-02T09:30:00Z"},
     "total": {"N": "2499"}, "expiresAt": {"N": "1796000000"}}
    JSON
    aws dynamodb put-item --region $REGION --table-name $TABLE --item file://order.json --condition-expression "attribute_not_exists(orderId)"
    aws dynamodb put-item --region $REGION --table-name $TABLE --item file://order.json --condition-expression "attribute_not_exists(orderId)" \
      || echo "Refused, as expected: the order already exists."
    
    # 5. Query by key. This reads one customer's orders only, and --return-consumed-capacity shows what it cost.
    aws dynamodb query --region $REGION --table-name $TABLE --key-condition-expression "customerId = :c" \
      --expression-attribute-values '{":c": {"S": "c-100"}}' --return-consumed-capacity TOTAL
    
    # 6. Query the index. "status" is a reserved word in DynamoDB, so it goes through a placeholder name.
    aws dynamodb query --region $REGION --table-name $TABLE --index-name by-status --key-condition-expression "#s = :s" \
      --expression-attribute-names '{"#s": "status"}' --expression-attribute-values '{":s": {"S": "PLACED"}}'
    
    # 7. Move the order to SHIPPED only while it is still PLACED, so two workers can't both ship it.
    aws dynamodb update-item --region $REGION --table-name $TABLE --key '{"customerId": {"S": "c-100"}, "orderId": {"S": "o-0001"}}' \
      --update-expression "SET #s = :shipped" --condition-expression "#s = :placed" \
      --expression-attribute-names '{"#s": "status"}' --expression-attribute-values '{":shipped": {"S": "SHIPPED"}, ":placed": {"S": "PLACED"}}'
    
    # 8. One transaction: create the order and take one unit out of stock, or do neither.
    cat > tx.json <<JSON
    [
     {"Put": {"TableName": "$TABLE", "Item": {"customerId": {"S": "c-200"}, "orderId": {"S": "o-0002"}, "status": {"S": "PLACED"}, "createdAt": {"S": "2026-11-02T09:45:00Z"}, "total": {"N": "999"}},
              "ConditionExpression": "attribute_not_exists(orderId)"}},
     {"Update": {"TableName": "Stock", "Key": {"sku": {"S": "sku-9"}}, "UpdateExpression": "SET onHand = onHand - :one",
                 "ConditionExpression": "onHand >= :one", "ExpressionAttributeValues": {":one": {"N": "1"}}}}
    ]
    JSON
    aws dynamodb transact-write-items --region $REGION --transact-items file://tx.json
    
    # 9. A change stream for Lambda or EventBridge Pipes, and a named backup before a risky release.
    aws dynamodb update-table --region $REGION --table-name $TABLE --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES
    aws dynamodb create-backup --region $REGION --table-name $TABLE --backup-name $TABLE-before-release
    
    # 10. Has anything been throttled in the last hour?
    aws cloudwatch get-metric-statistics --region $REGION --namespace AWS/DynamoDB --metric-name ThrottledRequests \
      --dimensions Name=TableName,Value=$TABLE Name=Operation,Value=PutItem --statistics Sum --period 300 \
      --start-time $(date -u -d '-1 hour' +%FT%TZ) --end-time $(date -u +%FT%TZ)
    # Placeholders: pass in your own names. The values below are examples.
    variable "region" {
      type    = string
      default = "ap-south-1"
    }
    
    variable "table_name" {
      type    = string
      default = "Orders"
    }
    
    provider "aws" {
      region = var.region
    }
    
    resource "aws_dynamodb_table" "orders" {
      name         = var.table_name
      billing_mode = "PAY_PER_REQUEST"
      hash_key     = "customerId"
      range_key    = "orderId"
    
      attribute {
        name = "customerId"
        type = "S"
      }
    
      attribute {
        name = "orderId"
        type = "S"
      }
    
      attribute {
        name = "status"
        type = "S"
      }
    
      attribute {
        name = "createdAt"
        type = "S"
      }
    
      # Open orders by status. Only the keys and the total are copied into the index.
      global_secondary_index {
        name               = "by-status"
        hash_key           = "status"
        range_key          = "createdAt"
        projection_type    = "INCLUDE"
        non_key_attributes = ["total"]
      }
    
      ttl {
        attribute_name = "expiresAt"
        enabled        = true
      }
    
      point_in_time_recovery {
        enabled                 = true
        recovery_period_in_days = 35
      }
    
      server_side_encryption {
        enabled = true # uses the AWS managed key; add kms_key_arn for a customer managed key
      }
    
      deletion_protection_enabled = true
      stream_enabled              = true
      stream_view_type            = "NEW_AND_OLD_IMAGES"
    
      tags = {
        team = "orders"
      }
    }
    
    resource "aws_dynamodb_table" "stock" {
      name         = "Stock"
      billing_mode = "PAY_PER_REQUEST"
      hash_key     = "sku"
    
      attribute {
        name = "sku"
        type = "S"
      }
    }
    
    # Tell somebody when requests start to be throttled.
    resource "aws_cloudwatch_metric_alarm" "throttled" {
      alarm_name          = "${var.table_name}-throttled"
      namespace           = "AWS/DynamoDB"
      metric_name         = "ThrottledRequests"
      dimensions          = { TableName = aws_dynamodb_table.orders.name }
      statistic           = "Sum"
      period              = 300
      evaluation_periods  = 1
      threshold           = 1
      comparison_operator = "GreaterThanOrEqualToThreshold"
      treat_missing_data  = "notBreaching"
    }
    
    output "table_arn" {
      value = aws_dynamodb_table.orders.arn
    }
    # pip install boto3
    # An insert that is safe to retry, a paged query by customer, and an order plus a stock reduction in one transaction.
    import boto3
    from boto3.dynamodb.conditions import Key
    from botocore.exceptions import ClientError
    
    dynamodb = boto3.resource("dynamodb", region_name="ap-south-1")
    orders = dynamodb.Table("Orders")
    
    
    def place_order(customer_id, order_id, total):
        """Returns True when the order was created and False when it already existed, so a retried request is harmless."""
        try:
            orders.put_item(
                Item={"customerId": customer_id, "orderId": order_id, "status": "PLACED", "total": total},
                ConditionExpression="attribute_not_exists(orderId)",
            )
            return True
        except ClientError as err:
            if err.response["Error"]["Code"] == "ConditionalCheckFailedException":
                return False
            raise
    
    
    def orders_of(customer_id, page_size=25):
        """Yields every order of one customer. A Query returns at most 1 MB at a time, so follow LastEvaluatedKey."""
        kwargs = {"KeyConditionExpression": Key("customerId").eq(customer_id), "Limit": page_size}
        while True:
            page = orders.query(**kwargs)
            yield from page["Items"]
            if "LastEvaluatedKey" not in page:
                return
            kwargs["ExclusiveStartKey"] = page["LastEvaluatedKey"]
    
    
    def order_and_reserve(customer_id, order_id, sku, total):
        """Creates the order and takes one unit from stock in a single transaction. Returns False when the item is out of stock.
        The resource's client converts plain Python values to DynamoDB types for you, so nothing is serialised by hand here."""
        try:
            dynamodb.meta.client.transact_write_items(TransactItems=[
                {"Put": {"TableName": "Orders", "Item": {"customerId": customer_id, "orderId": order_id, "status": "PLACED", "total": total},
                         "ConditionExpression": "attribute_not_exists(orderId)"}},
                {"Update": {"TableName": "Stock", "Key": {"sku": sku}, "UpdateExpression": "SET onHand = onHand - :one",
                            "ConditionExpression": "onHand >= :one", "ExpressionAttributeValues": {":one": 1}}},
            ])
            return True
        except ClientError as err:
            if err.response["Error"]["Code"] == "TransactionCanceledException":
                print("cancelled:", [reason.get("Code") for reason in err.response.get("CancellationReasons", [])])
                return False
            raise
    
    
    if __name__ == "__main__":
        print("created:", place_order("c-300", "o-1001", 1200))
        print("created again:", place_order("c-300", "o-1001", 1200))
        for n in range(2, 6):
            place_order("c-300", "o-100%d" % n, 100 * n)
        print("orders of c-300:", [order["orderId"] for order in orders_of("c-300", page_size=2)])
        print("reserved:", order_and_reserve("c-300", "o-2001", "sku-9", 999))
    // dotnet add package AWSSDK.DynamoDBv2
    // Audits every table in the Region: point-in-time recovery off, deletion protection off, indexes that copy every attribute,
    // and tables with a time to live attribute missing. Edit the rules to match your own standard.
    using Amazon.DynamoDBv2;
    using Amazon.DynamoDBv2.Model;
    
    using var db = new AmazonDynamoDBClient();
    int problems = 0;
    string? start = null;
    
    do
    {
        var list = await db.ListTablesAsync(new ListTablesRequest { ExclusiveStartTableName = start });
        foreach (string name in list.TableNames)
        {
            TableDescription table = (await db.DescribeTableAsync(name)).Table;
    
            var backups = (await db.DescribeContinuousBackupsAsync(new DescribeContinuousBackupsRequest { TableName = name })).ContinuousBackupsDescription;
            if (backups.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus != PointInTimeRecoveryStatus.ENABLED)
            {
                Console.WriteLine($"{name}: point-in-time recovery is off");
                problems++;
            }
    
            if (table.DeletionProtectionEnabled != true)
            {
                Console.WriteLine($"{name}: deletion protection is off");
                problems++;
            }
    
            foreach (GlobalSecondaryIndexDescription index in table.GlobalSecondaryIndexes ?? new List<GlobalSecondaryIndexDescription>())
            {
                if (index.Projection.ProjectionType == ProjectionType.ALL)
                {
                    Console.WriteLine($"{name}/{index.IndexName}: the index copies every attribute, which you pay for on each write");
                    problems++;
                }
            }
    
            var ttl = (await db.DescribeTimeToLiveAsync(name)).TimeToLiveDescription;
            if (ttl.TimeToLiveStatus != TimeToLiveStatus.ENABLED)
                Console.WriteLine($"{name}: no time to live attribute (fine if nothing in this table expires)");
        }
        start = list.LastEvaluatedTableName;
    } while (start != null);
    
    Console.WriteLine(problems == 0 ? "No problems found." : $"{problems} problem(s) found.");
    return problems == 0 ? 0 : 1;

    Best videos to explore

    Videos picked for this service. Each one is labelled with its source, opens on YouTube if you prefer, and loads only when you press play.

    AWS re:Invent 2019: Data modeling with Amazon DynamoDB (CMY304)Official · AWS · Intermediate · Watch on YouTube
    AWS re:Invent 2024 - Dive deep into Amazon DynamoDB (DAT406)Official · AWS · Advanced · Watch on YouTube
    AWS re:Invent 2020: Amazon DynamoDB advanced design patternsOfficial · AWS · Advanced · Watch on YouTube
    AWS DynamoDB Full course For BeginnersCommunity video · Beginner · Watch on YouTube

    Interview questions

    BasicWhat is Amazon DynamoDB?

    It’s AWS’s fully managed NoSQL database. You create tables of items, find them by a primary key, and DynamoDB handles servers, replication across Availability Zones and scaling. It’s built for fast key-based reads and writes at any size, not for joins or ad hoc SQL.

    BasicWhat are a partition key and a sort key?

    Together they form the primary key. The partition key is hashed to decide where an item is stored, and a query must name one value of it. The optional sort key orders the items that share a partition key, so you can ask for a range, such as the latest ten orders of a customer.

    BasicWhat is the difference between on-demand and provisioned capacity?

    On-demand bills each read and write request, so there is nothing to plan and it suits new or spiky workloads. Provisioned bills the read and write capacity you set per hour, with optional auto scaling, and usually costs less when traffic is steady and well understood. You can switch a table between modes.

    IntermediateWhat is the difference between Query and Scan, and why does it matter?

    A Query reads the items of one partition key value, using the keys of a table or index, so its cost depends on what it returns. A Scan reads every item and then filters, so you pay for the whole table however few items match. Filters don’t reduce the capacity a scan uses, so a scan on a growing table gets slower and dearer over time.

    IntermediateHow do global and local secondary indexes differ?

    A global secondary index has its own partition and sort key, can be added at any time, has its own capacity and is eventually consistent. A local secondary index keeps the table’s partition key with a different sort key, must be created with the table, can be read with strong consistency and limits each partition key value to 10 GB. Most designs need global indexes only.

    IntermediateWhen would you use a strongly consistent read?

    When the code needs its own write back at once, for example reading a balance right after updating it. A strongly consistent read returns the latest committed data from the table and costs twice the capacity of an eventually consistent one. Indexes and global table replicas in other Regions can’t serve strongly consistent reads, except for multi-Region strong consistency tables.

    AdvancedHow do you design a table for several access patterns without a hot partition?

    List the queries first, then pick a partition key with many values and even traffic and a sort key that supports the range queries. Put items that are read together under one key, and use a global secondary index for the other patterns. For a key that is inherently hot, split it with a suffix and read all the suffixes in parallel. Check the result with Contributor Insights and the throttling metrics under realistic load.

    AdvancedHow do conditional writes and transactions help you build reliable workflows?

    A conditional write turns an insert into an idempotent one and stops two workers updating the same state, because only one satisfies the condition. A transaction groups up to 100 actions across tables so they all succeed or none do, with a client token for safe retries. They cost double the capacity and are subject to conflicts, so keep them for places where partial success would corrupt data.

    AdvancedHow would you choose between multi-Region eventual consistency and multi-Region strong consistency in global tables?

    The default global table accepts writes in every Region and reconciles them afterwards with last-writer-wins, which gives low write latency and a small chance of losing a concurrent update. Multi-Region strong consistency makes reads consistent across Regions and gives a recovery point of zero, at the price of higher write latency and some limits, for example on transactions. Choose it for data like balances or inventory where a lost write is unacceptable, and the default for profiles and carts.

    AdvancedRequests are throttled although consumed capacity is far below the limit. What is happening and how do you fix it?

    Capacity is enforced per partition, so one hot key value can throttle while the table total looks low. Find the key with Contributor Insights, then spread it with a better key or a suffix, cache hot reads with DAX, and for provisioned tables check that auto scaling responds fast enough. A new on-demand table, or a sudden jump well above its earlier peak, can also throttle briefly while it adapts.

    Similar or alternative services on other clouds

    CapabilityAWSAzureGoogle Cloud
    ServiceAmazon DynamoDBAzure Cosmos DB (API for NoSQL)Firestore
    Data modelTables of items, partition key and sort keyContainers of JSON items, partition keyCollections of documents, with subcollections
    CapacityOn-demand, or provisioned with auto scalingRequest units: provisioned, autoscale or serverlessNo capacity to set; billed per operation
    Secondary indexesGlobal and local indexes that you designEvery property indexed by default, with a policy to tuneSingle-field indexes automatic, composite ones declared
    ConsistencyEventual by default, strong per readFive levels, session by defaultStrongly consistent reads and queries
    Multi-RegionGlobal tablesMulti-region replication and writesMulti-region locations
    Change feedDynamoDB Streams, 24 hoursChange feedRealtime listeners and event triggers
    RecoveryPoint-in-time recovery, 1 to 35 daysContinuous backup, 7 or 30 daysPoint-in-time recovery, 7 days, and scheduled backups
    How you are billedRequest units or capacity units, plus storageRequest units or throughput per hour, plus storageReads, writes and deletes, plus storage

    Typical partners are AWS Lambda and Amazon API Gateway for a serverless back end, and Amazon EventBridge Pipes for reading a stream. Customer managed keys come from AWS KMS, and table metrics and alarms live in Amazon CloudWatch.

    Did you find this article helpful?

    Your answer helps us improve this page for the next reader.

    Sources and further reading

    CloudBrum wrote this page in its own words, using the provider’s public documentation listed below as the reference. Features, limits, names and prices change, so confirm anything that matters on the provider’s own pages before you decide.