Based on the DEA-C01 exam guideReport an error
Amazon DynamoDB is a fully managed, serverless NoSQL key-value and document database. It is fundamentally built for OLTP, but data engineers need to know it for two reasons: how to pull data out as a source of operational data, and how to design it as a state store and lookup store for pipelines.
Data is organized as table → item (row) → attribute (column). An item is identified by its primary key.
PK = USER#1001 SK = ORDER#2026-06-26#A amount=120
PK = USER#1001 SK = ORDER#2026-06-26#B amount=80
PK = USER#1001 SK = PROFILE name="Kim"USER#1001 form one collection, fetched at once with a single PK.| Operation | Behavior | Cost |
|---|---|---|
GetItem | One item, specifying the full primary key | The size of that item |
Query | Specify the PK and look inside one collection with an SK condition (range or prefix) | The items read that match the condition |
Scan | The whole table (or index) from beginning to end | The size of the entire table |
FilterExpression of a Scan is a filter that discards after reading. Even if the result is 10 items, the read capacity for the entire table is consumed. This is the answer to "why is it expensive even though I added a filter?"Query and Scan return at most 1 MB at a time, and beyond that you continue by fetching pages.💡 Related Theory: DynamoDB designs its keys after deciding the access patterns first (access-pattern-first). A relational database normalizes the schema and writes queries later, but in DynamoDB the list of "what will be retrieved by which key" is the schema. Questions that were not on the list (arbitrary column aggregation, joins) can only be answered with a Scan, and that is why analytics is sent outside.
| Aspect | Provisioned | On-demand |
|---|---|---|
| Billing | Hourly charge for the RCUs and WCUs you set (whether used or not) | Read and write request units actually requested |
| Capacity management | Set it yourself or use Auto Scaling | None |
| Suitable traffic | Steady and predictable | Highly variable and unpredictable, new workloads |
| Handling spikes | Only as much as provisioned | Instantly accommodates up to 2x the previous peak |
A single partition of a table is designed to deliver up to 3,000 read units and 1,000 write units per second. No matter how large the total table capacity is, traffic piled onto one partition key runs into this limit.
-- Anti-pattern: using only the date as the PK for a time series concentrates that day's traffic on one partition
PK = 2026-06-26
-- Write sharding: spread across N partitions with a suffix (when reading, query all of 0 to N-1)
PK = 2026-06-26#03
-- A more common solution: device ID as the PK, timestamp as the SK
PK = DEVICE#8821 SK = 2026-06-26T10:15:00Z| Key choice | Distribution | Verdict |
|---|---|---|
user_id, device_id, order_id | Many values, even | Good |
date, status, country | Few or skewed values | Hot partition |
tenant_id (one large tenant accounts for most) | Skewed to one value | Sharding needed |
| Date + shard suffix | Artificially even | Possible, but reading becomes complex |
To query by an attribute that is not the primary key, you need a secondary index.
| Aspect | GSI (Global Secondary Index) | LSI (Local Secondary Index) |
|---|---|---|
| Key | A different PK (and SK) from the table | The same PK as the table, a different SK |
| Scope | The whole table | Within one partition key |
| When it can be created | Added or removed at any time | Only when the table is created |
| Consistency | Eventually consistent only | Strongly consistent can be chosen |
| Capacity | Separate capacity from the table | Shares the table's capacity |
| Count | 20 per table by default (quota) | 5 per table |
| Key uniqueness | Index keys may be duplicated | - |
-- GSI example: look up orders by status
GSI: PK = status (SHIPPED/PENDING), SK = order_date
→ Supports a new access pattern such as "PENDING orders in date order"KEYS_ONLY (keys only), INCLUDE (adds the attributes you specify), ALL (everything). A GSI query cannot fetch attributes that are not projected, and ALL increases storage and write cost.status as the PK of a GSI as in the example above, the GSI itself becomes a hot partition. An index follows the same distribution rules as the table.💡 Related Theory: A GSI is a tool for "creating one more lookup axis that you know in advance," not an analytics tool. If you add a GSI every time an aggregation axis is added, each write incurs a cost per index, and you still cannot do SUM or GROUP BY. Questions whose lookup axes keep changing are the job of a columnar analytics store.
DynamoDB emits item-level changes (INSERT, MODIFY, REMOVE) as a stream. There are two options, and you can turn on both for one table.
| Property | DynamoDB Streams | Kinesis Data Streams for DynamoDB |
|---|---|---|
| Retention | 24 hours | Up to 1 year |
| Number of consumers | Up to 2 concurrent per shard | 5 per shard, 20 with enhanced fan-out |
| Ordering | Changes to the same item arrive in actual order | You judge the order yourself from the record timestamps |
| Duplicates | None (each record exactly once) | Occasional duplicates possible |
| Processing options | Lambda, Kinesis adapter (KCL) | Lambda, Managed Service for Apache Flink, Data Firehose, Glue streaming ETL |
| Clue for choosing | "React with Lambda on every change," simple | "Long-term retention, reprocessing," "multiple apps consume," "load into S3 through Firehose" |
What the stream holds is decided by the view type.
StreamViewType | What is included | Use |
|---|---|---|
KEYS_ONLY | Only the keys of the changed item | Announce only that a change happened and re-query the body |
NEW_IMAGE | The whole item after the change | Replicate the latest state to another store |
OLD_IMAGE | The whole item before the change | Keep deleted values, auditing |
NEW_AND_OLD_IMAGES | Both before and after the change | Compute differences, reproduce CDC completely |
DynamoDB table
├─ DynamoDB Streams ──► Lambda ──► transform ──► S3 / OpenSearch / another table
└─ Kinesis Data Streams ──► Data Firehose ──► S3 (Parquet) ──► Athena / Redshift Spectrum
└─► Managed Service for Apache Flink (real-time aggregation)| Method | Behavior | Consumes table capacity | Freshness | When it fits |
|---|---|---|---|---|
| S3 export | Exports the table at a point in time from the PITR backup to S3 | No | The time of export | Regular snapshot analysis, large volumes |
| Incremental S3 export | Only what changed during a specified period | No | In units of 15-minute to 24-hour windows | Catching up after a full export |
| Zero-ETL → Redshift | Automatic replication using a full export followed by incremental exports | No | 15 to 30 minute intervals | Analyzing in the warehouse with no operational burden |
| Athena federation (DynamoDB connector) | Athena reads the table directly | Yes | Real time | Occasional exploratory queries |
| Reading directly with Glue ETL or Redshift COPY | Reads by Scan | Yes | At the time of the run | Small tables, one-off |
# Export the table at a point in time from the PITR backup to S3
aws dynamodb export-table-to-point-in-time \
--table-arn arn:aws:dynamodb:ap-northeast-2:111122223333:table/Orders \
--s3-bucket acme-datalake-raw-prod \
--s3-prefix dynamodb/orders/ \
--export-format DYNAMODB_JSON| Workload | Verdict | Use instead |
|---|---|---|
| Single-digit millisecond lookups by key (sessions, shopping carts, device state) | Fits | - |
| Unpredictable large-scale concurrent requests, serverless backends | Fits (on-demand) | - |
| Storing a pipeline's checkpoints, metadata, and deduplication keys | Fits | - |
| Arbitrary column aggregation, GROUP BY reports | Does not fit | Export or stream → S3 + Athena, Redshift |
| Joining multiple tables | Does not fit | RDS/Aurora, Redshift |
| Full-text search | Does not fit | Stream or zero-ETL → OpenSearch |
| Large objects (documents of several MB, images) | Does not fit (item size limit) | Store in S3 and keep only the key in DynamoDB |
Operational (OLTP): DynamoDB → change capture (Streams/Kinesis) or snapshot (Export) → analytics (OLAP): S3 + Athena / RedshiftThis is the data engineering version of CQRS (Command Query Responsibility Segregation). Writes and key lookups go to DynamoDB, and analytics and aggregation go to a columnar store, so each does what it is good at. For the link between the two, use a stream if it must be real time, an export if it is periodic, and zero-ETL if you want to hand it over to a managed service.
| Symptom | Cause | Action |
|---|---|---|
| Table capacity is left over but only certain requests are throttled | Hot partition (skewed to one key) | Redesign the key, write sharding |
| Writes are throttled while the table WCU has room | Insufficient GSI write capacity | Set GSI capacity at or above the table's |
| A filtered query makes read capacity spike | Scan + FilterExpression | Design keys or a GSI so that it becomes a Query |
| The service lags while an analytics job runs | The analytics scans the table directly | Switch to S3 export |
| A value just written does not show up in an index query | Eventual consistency of the GSI | Read the table with strong consistency, or use an LSI |
| Adding a third stream consumer causes throttling | Streams limit of 2 concurrent readers per shard | Switch to Kinesis Data Streams |
| A consumer that was down for more than a day lost data | Exceeded the 24-hour retention of Streams | Kinesis Data Streams (long retention), rebuild from an export |
| The export fails | PITR not enabled | Enable PITR |
| The same change was applied twice downstream | Duplicate records on the Kinesis path, Lambda retries | Idempotent processing (upsert by key) |
| Clue in the question | Answer |
|---|---|
| "Used date and status as the partition key and got throttled" | Hot partition → even key, write sharding |
| "Query the whole table by an attribute that is not the primary key" | GSI |
| "A different sort order within the same partition key, strong consistency needed" | LSI (defined when the table is created) |
| "React to item changes in real time" | DynamoDB Streams + Lambda |
| "Keep change history for more than a few days, consumed by several applications" | Kinesis Data Streams for DynamoDB |
| "Continuously load the changes into S3" | Kinesis Data Streams → Data Firehose → S3 |
| "Export the whole table to S3 without affecting the production table" | S3 export (PITR required) |
| "Analyze DynamoDB data in Redshift without managing a pipeline" | Zero-ETL integration |
| "Occasionally join S3 data and DynamoDB with SQL" | Athena federated query |
| "Traffic is unpredictable and the team does not want to manage capacity" | On-demand mode |
| "Automatically delete old items and keep the deleted ones" | TTL + Streams + Lambda → S3 |
Tomorrow we cover RDS/Aurora, relational OLTP stores, along with zero-ETL and the criteria for choosing a store by looking at the workload.
Click a choice to reveal the answer and explanation.
Question 1
What problem occurs in DynamoDB when the PK of time-series data is specified only as the date (for example, 2026-06-26)?
Question 2
What do you use to add a new access pattern that queries the entire table by an attribute other than the primary key (for example, status)?
Question 3
Which feature captures item changes in a DynamoDB table in time order and flows them into an analytics pipeline?
Question 4
What is the most appropriate way to export data from a production DynamoDB table as of a specific point in time to S3 and analyze it with Athena without putting load on the table?
Question 5
Which workload is the most appropriate example of one for which using DynamoDB is unsuitable from an analytics perspective?