AWS Builder Center
Amazon DynamoDB Primer -- Event Layer Database

Amazon DynamoDB Primer -- Event Layer Database

Add Amazon DynamoDB to an architecture for a write fast now, read keys later pattern capable of writing at high-velocity

Series: AWS Databases (3 articles)

  1. 3
    Amazon DynamoDB Primer -- Event Layer Database This article
we used DynamoDB for all append-heavy workloads where access pattern priority was an ability to write fast where our User Interface / Experience depended on it. powering activity feeds, user action logs, location pins, audit trails
A SQL engine handling these writes concurrently with geospatial range queries would create lock contention and unpredictable latency on queries that might be returning results during an emergency. Separating event layers onto DynamoDB means our Aurora PostgreSQL instance used for geographic map data stays dedicated to geospatial math

Items, Keys, and Partitions

In DynamoDB, every piece of data is stored as an item. Every item is addressed by a primary key. Primary key determines placement inside the storage engine. DynamoDB organizes storage into partitions, each partition having its own throughput and storage segments. The way keys map to partitions is the foundation of DynamoDB's performance model
An item can be at most 400 KB. Using a local secondary index, the total collection of items sharing the same partition key cannot exceed 10 GB. Ðekawɔwɔ's events are 1-3 KB each. no local secondary indexes, so neither limit constrains the design. they define the ceiling of what a single partition key value can hold
Each partition supports up to 3,000 read capacity units per second and 1,000 write capacity units per second. A single write of an item up to 1 KB consumes 1 WCU. A single eventually consistent read of an item up to 4 KB consumes 0.5 RCU; a strongly consistent read consumes 1 RCU. Ðekawɔwɔ's 1-3 KB events cost exactly 1 WCU per write and 0.5 RCU per eventually consistent read -- smallest unit of work for both operations

Partition Key Design in DynamoDB

1 partition key used for project Ðekawɔwɔ is a composite of town identifier and a time bucket: town#ho#2026-07-02T14. This key spreads writes across partitions so no single partition absorbs a disproportionate share of traffic - if rains flood Ho and massive volumes of emergency reports come in at once, we still allow efficient queries for "what happened in Ho last hour." Within each partition, sort key is a timestamp plus event ID, giving natural chronological ordering for rendering our social-feed
Time-bucketed composite keys unlock a cost optimization. Query operations against composite key with a sort key condition (SK BETWEEN '2026-07-02T14:00:00' AND '2026-07-02T14:59:59') returns all matching items in a single paginated response, consuming read capacity proportional to data returned — not per item. For 50 items of 2 KB each (100 KB total), the Query consumes 12.5 RCU at eventually consistent pricing (100 KB / 4 KB per RCU × 0.5 for eventual consistency). The alternative costs 25 RCU, requires caller to already know all 50 keys. The cost gap exists because Query sums all item sizes first and rounds the total to 4 KB boundaries once, while BatchGetItem rounds each item individually to the next 4 KB boundary before summing — a rounding-order difference that compounds with item count. In practice, flat-key designs force a scan or a secondary index lookup first, pushing the effective cost and complexity 2–20x beyond what a well-designed composite key achievesi
4 DynamoDB tables in Ðekawɔwɔ exploit different partitioning strategies
TablePartition KeySort KeyWhy
activity_feedtown#\<id\>ts#\<iso\>Writes spread across towns; each town is its own partition
user_actionsuser#\<id\>ts#\<iso\>Each user is their own partition; scales linearly with user count
ephemeral_pinspin#\<id\>--UUID-based; maximally distributed, TTL-expiring
collected_datademo#\<id\>ts#\<iso\>Demo-isolated; never pollutes real traffic

Write-Ahead Log Broad Tree Data Structure

a time to come will be harsh -- hope is a man who has seen the myriad futures -- who can guide you through their treacherous eddies and byways -- guide you from this, mankind's birthplace -- to a GLORIOUS DESTINY in the Cloud - paraphrased Nathaniel Richards - Marvel Avengers Vol 3 53 - how I think of Paxos Leader & their quorum
DynamoDB partitions store data in a multi-target or Broad-tree (B-Tree) structure on modern Solid State Drive-backed storage. A replication group for a partition consists of storage replicas — each containing both a write-ahead log (WAL) and B-tree key-value data — coordinated via Multi-Paxos consensus.
Paxos is a consensus protocolsused to ensure a cluster of storage replication machines. Replication groups can include log replicas that store only recent Write Ahead Log entries without the B-tree, enabling fast durability recovery without the overhead of copying the full data structure
Write arrives at Paxos leader, which generates a WAL entry and sends to replication group. Once a quorum of replicas (typically two of three) persists WAL entry to their local logs, the write is acknowledged to the client. Each replica — including the leader — applies the mutation to its local B-tree after persisting the Write Ahead Log entry, an in-place update to the appropriate leaf node
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Paxos Replication — Write Quorum
Storage Replicas
┌─────────────────────┐
┌─────▶│ W (WAL) │ B (B-tree) │ Replica 1
│ └─────────────────────┘
│
┌─────┐ ┌───────┐ │ ┌─────────────────────┐
│ │ │ │──┼─────▶│ W (WAL) │ B (B-tree) │ Replica 2
│ CLI │─────▶│LEADER │ │ └─────────────────────┘
│ │ │ │──┤
└─────┘ └───────┘ │ ┌─────────────────────┐
▲ └─────▶│ W (WAL) ┊ B (B-tree) ┊ Replica 3
│ └─────────────┊ - - - - - -┊ (log only)
│ └╌╌╌╌╌╌╌╌╌╌╌┘
│
│ ┌──────────────────────────────────┐
│ │ Quorum: 2 of 3 replicas ACK │
└─────────┤ before write confirmed │
2/3 └──────────────────────────────────┘

Flow:
Client ──▶ Leader ──▶ fans out to 3 storage nodes
- Each has WAL (write-ahead log) + B-tree
- Replica 3: dashed B-tree = log replica only
- Write succeeds when 2/3 replicas acknowledge
- ACK returns to client via leader
This architecture delivers stable single-digit-millisecond latency. A Broad-tree mutation is a bounded, in-place operation — find leaf, update it, possibly rebalance — with no background compaction process competing for I/O bandwidth. Unlike LSM-tree engines where periodic compaction can cause unpredictable latency spikes,. Write path has no deferred reorganization step
When a storage replica fails, healing requires copying both B-tree and WAL from a healthy replica — a process that takes several minutes. Adding a log replica for immediate durability protection takes only seconds because it copies just recent Write Ahead Log entries. Fast log replica, slow storage replica — maintains durability guarantees during failure while minimizing the window of reduced redundancy

Reads — Consistency Model
DynamoDB's consistency model follows directly from its replication architecture. Only Paxos leader serves strongly consistent reads — by definition, Paxos leader has latest state because all writes flow through it. Eventually consistent reads can be served by any replica in the group, which may not yet have applied all Write Ahead Log entries from the leader
Staleness window for eventually consistent reads is time for a WAL entry to propagate from the leader to the replica you happened to read from — typically milliseconds under normal operation. If you read from a slightly stale replica, you simply get data that is milliseconds behind. Repeat the read a moment later, or issue a strongly consistent read to the leader, and you see the latest state
Strongly consistent reads cost 1 RCU per 4 KB. Eventually consistent reads cost 0.5 RCU per 4 KB — half price because DynamoDB can route them to any replica, distributing read load across group. For Ðekawɔwɔ's activity_feed, primary Region uses strongly consistent reads for the "your own feed" view (read-after-write guarantee), while the broader town feed uses eventually consistent reads where millisecond-level staleness is invisible to users

Writes — Conditional and Transactional

Conditional writes evaluate correctness rules inside partition. Evaluation is local because partitions contain all the information needed to determine whether the condition is true. Locality is why conditional writes cannot enforce uniqueness across partitions — but within a single partition key value, they provide idempotent write semantics with zero coordinator overhead. Cost follows normal item-size rules (1 WCU per KB, rounded up — 1 to 3 WCU for Ðekawɔwɔ's events), and a failed condition check still consumes that write capacity, so capacity planning should account for the expected rejection rate on high-contention keys
DynamoDB avoids cross-partition coordination unless a transaction explicitly requires it. Most workloads — including Ðekawɔwɔ's event layer, which is append-only and keyed by town plus timestamp — never invoke coordinator. Event layers use conditional writes (ConditionExpression on PutItem) for idempotency: attribute_not_exists(event_id) ensures each event is written exactly once. The geo-mirror Lambda uses the same pattern — if a Stream record is delivered twice due to a retry, the conditional write silently rejects the duplicate. Double-match prevention, which requires cross-item atomicity, lives in Aurora DSQL where optimistic concurrency control handles it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Geo-Mirror Pipeline — Async Stream Processing
╭─────────────╮ ≋≋≋≋≋≋≋≋≋≋≋≋≋ /\
│ Source │ ≋ DynamoDB ≋ / \ ╭──────────╮ ┌──────────┐
│ Table │────▶ ≋ Stream ≋──▶/ Fn \─────▶│ PostGIS │────▶│ DB │
│ (DynamoDB)│ ≋≋≋≋≋≋≋≋≋≋≋≋≋ / Lambda\ │ Transform│ │ .~~~~. │
╰─────────────╯ /________\ ╰──────────╯ │ ( ) │
│ │ `~~~~' │
│ └──────────┘
│
│ ~40% filtered
▼
╲ ╱
X ← events dropped
╱ ╲
When correctness requires multi-item atomicity, DynamoDB provides TransactWriteItems. AWS classifies DynamoDB transactions as pessimistic concurrency control: DynamoDB monitors all items in transaction and cancels the entire operation if any item is modified by a concurrent write, preventing conflicts rather than detecting them after the fact. If a conflict is detected, the transaction fails with TransactionCanceledException and the client retries. This is distinct from the optimistic locking pattern (version attribute + conditional writes) used elsewhere in the event layer, and distinct from Aurora DSQL's OCC model which detects conflicts at commit time rather than preventing them during execution. Transactions consume 2x the WCU of equivalent non-transactional writes because the operation requires both a prepare phase and a commit phase, each requiring durable storage
Decisions between conditional writes and TransactWriteItems reduces to a simple question: does correctness require atomicity across multiple items or multiple tables? If yes, pay the 2x cost and accept the latency. If no — and most event-ingestion workloads fall here — conditional writes give you single-item correctness at standard cost

Capacity Modes and Warm Throughput

DynamoDB offers two capacity modes. Provisioned mode lets you specify exact RCU and WCU allocations per table, with auto-scaling rules that adjust within configured bounds. On-demand mode instantly accommodates up to double a table's previous peak traffic, with no capacity planning required. Beyond that 2x ceiling, DynamoDB needs ramp time to allocate additional capacity — AWS's documented guidance uses a 30-minute figure specifically at 100,000+ reads/sec scale; the underlying 2x-instant/ramp-beyond-that mechanic applies at any scale, but the exact 30-minute number is not confirmed as a universal constant across all traffic levels. Traffic spikes exceeding double the previous peak within that ramp window may see throttling. In November 2024, AWS reduced on-demand pricing by 50 percent, making it cost-competitive with provisioned mode for workloads that spike unpredictably. Ðekawɔwɔ uses on-demand exclusively: emergency traffic is inherently spiky, and the halved price eliminates the only argument against it for this access pattern
You can switch from provisioned to on-demand mode up to four times in a 24-hour rolling window, and from on-demand to provisioned at any time with no limit. This matters for workloads with predictable batch windows — switch to provisioned for a known heavy import, then back to on-demand for normal operation. Ðekawɔwɔ does not need this today because on-demand handles both idle periods and emergency bursts, but options exist for future batch historical imports where provisioned reserved capacity would be cheaper
Warm throughput represent numbers of read and write operations a table can instantaneously support without ramping up. It is a table-level concept, not a per-partition-key one — DynamoDB automatically adjusts a table's warm throughput upward as overall usage increases, and you can also proactively increase it yourself. Once increased, warm throughput values cannot be decreased. For Ðekawɔwɔ, sustained high traffic to any single town's partitions during an emergency contributes to activity_feed's table-wide warm throughput floor, which benefits subsequent traffic patterns across the whole table rather than being scoped to that one town's key
Batch capacity scaling is a pattern where you temporarily increase provisioned capacity before a large import, run batch, then scale back down. Ðekawɔwɔ does not need this today since on-demand absorbs everything, but the pattern exists for a future scenario: importing years of historical emergency data from municipal records. In that case, switching to provisioned mode with a high WCU allocation for the import window, then switching back, would cost less than running the same volume through on-demand pricing

DynamoDB Streams Geo-Mirror Pipeline

DynamoDB Streams captures every write to a correlating event table as a time-ordered sequence of change records, retained for 24 hours. A Lambda function subscribes to the stream, filters for location-relevant events (new emergency pins, resource registrations, status changes), and writes the geospatial data to our Aurora PostGIS layer. DynamoDB Streams is the bridge — event capture happens at DynamoDB speed with no SQL overhead, then fans out asynchronously to the geospatial engine. The emergency search path never competes with the event ingestion path
Stream consumer configuration matters. Ðekawɔwɔ's geo-mirror Lambda uses FilterCriteria to process only events that contain geographic coordinates — roughly 60 percent of all activity_feed writes. Events without location data (status changes, text-only updates) pass through the stream but never invoke the Lambda, saving both compute cost and PostGIS write pressure. BisectBatchOnFunctionError splits a failed batch in half and retries each half independently, isolating a single poison record without blocking the entire shard. ParallelizationFactor is set to 2, allowing two concurrent Lambda invocations per shard during sustained bursts. ReportBatchItemFailures lets the function return partial success — if 24 of 25 records process correctly, only the failed record retries rather than the entire batch
DynamoDB recommends limiting single-Region, non-global tables to two concurrent Streams consumers per shard. For Global Tables specifically — which is what all four Ðekawɔwɔ tables are — AWS recommends limiting Streams to a single application consumer per shard, since internal Global Tables replication process already occupies part of that read capacity.
Kinesis Data Streams for DynamoDB removes per-shard consumer constraint: it supports up to 5 simultaneous consumers per shard, or up to 20 with enhanced fan-out, and records retain for up to 1 year instead of 24 hours. The trade-off is cost: Kinesis charges per shard-hour plus per-GB retrieved. Ðekawɔwɔ uses DynamoDB Streams because the architecture has exactly one application-facing consumer (geo-mirror), matching the recommended single-consumer limit for Global Tables. 24-hour retention is more than sufficient for replay scenarios, and the $0 base cost aligns with the scale-to-zero design philosophy. If a second consumer appeared — if we add an analytics pipeline — we'd use Kinesis Data Streams for the fan-out capability

Clock-Tick Expirations and Ephemeral Pins

We write a pin with a Time To Live attribute set to current time plus the desired lifespan, typically a few hours. DynamoDB scans for expired items in the background and deletes them automatically. Items are typically removed "within a few days" of their expiration timestamp, at no additional write cost and with no cron job required. The deletion itself flows through DynamoDB Streams as a REMOVE event, so our Lambda function can also clean up the corresponding PostGIS record when a pin expires
TTL expiry is asynchronous by design. TTL sweeper scans partitions for expired items, issues standard delete operations against the B-tree, and emits REMOVE events to Streams. TTL is intentionally decoupled from the write path to avoid interfering with ingestion — the sweeper never competes with your PutItem operations for throughput. Time To Live deletes do not consume write capacity units in the Region where the expiry occurs. For a Global Table, though, that deletion still has to propagate — the replicated TTL delete consumes a replicated write unit in each replica Region, so high TTL churn on ephemeral_pins is a real cost to factor into capacity planning for every Region the table replicates to, not just the source
Because deletion can lag by days, application filters expired items on read: any item where expires_at < now() is excluded from query results even if DynamoDB has not yet physically removed it. This is a standard pattern for TTL-based architectures — the delay is a service-level scheduling property, not a storage engine artifact
In Ðekawɔwɔ, ephemeral_pins have a default TTL of one hour. When sweeper deletes a batch of expired pins, the REMOVE events flow through DynamoDB Streams to geo-mirror Lambda, which issues DELETE statements against the PostGIS pins_geo table. The entire cleanup chain — TTL expiry, Stream event, Lambda invocation, PostGIS delete — runs without human intervention and without scheduled jobs. Only consented pins that users explicitly choose to persist get written to PostGIS via a direct API call rather than the stream

Global Tables — Multi-Region Replication

Global Tables replicate data across Regions using a dedicated internal replication mechanism built into the DynamoDB storage engine layer. This mechanism is distinct from customer-facing DynamoDB Streams API — you can disable Streams on a Global Table and cross-Region replication continues working normally. A write in one Region is captured by this internal replication log and applied to the corresponding item in every other replica table asynchronously. The separation matters: your application-facing consumers (Lambda triggers, Kinesis adapters) attach to the customer-facing Streams API, while the replication infrastructure operates on its own internal change propagation path. The two systems are architecturally independent even though both react to the same underlying writes
Two consistency modes exist: MREC (multi-Region eventual consistency) and MRSC (multi-Region strong consistency). MREC is default — writes replicate asynchronously, typically under a second, with last-writer-wins conflict resolution based on item-level timestamps. MRSC replicates synchronously before returning success to the client, giving strongly consistent reads from any Region but with higher write latency. Under Multi Region Strong Consistency, a write is acknowledged only after it has been synchronously replicated to at least one other Region — in a three-Region setup, that means write is durable in at least two Regions before the client receives success
Critical design constraint: you choose consistency mode at table creation time and cannot change it afterward. A single table cannot mix modes. If you need both patterns — eventual for high-throughput event ingestion and strong for critical state — those must be separate tables with separate Global Table configurations. This is a creation-time lock-in that cannot be reversed without recreating the table and migrating data
MRSC carries hard constraints that affect architectural decisions: it does not support TTL, does not support local secondary indexes, does not support DynamoDB transactions (TransactWriteItems/TransactGetItems), requires exactly three Regions (or two full replicas plus one witness Region — a witness participates in consensus but cannot serve reads/writes. MSRC incurs no storage or write cost), table must be empty at Global Table creation. Additionally, MRSC Regions must belong to same Region — US (N. Virginia, Ohio, Oregon), EU (Ireland, London, Paris, Frankfurt), or AP (Tokyo, Seoul, Osaka) — and cannot span sets. These constraints mean multi-Region eventual consistency is purpose-built for workloads that need zero RPO with simple write patterns — not for general-purpose tables with complex access patterns
Ðekawɔwɔ uses MREC for all 4 tables. activity_feed table requires TTL (for ephemeral event cleanup) and will eventually require transactions (for atomic multi-item status updates), which rules out multi-Region eventual consistency. ephemeral_pins table uses TTL as its primary lifecycle mechanism. Architectural trade-off accepts a non-zero RPO window (typically under one second of replication lag) in exchange for full DynamoDB feature set. For life-safety data, the application layer provides its own confirmation mechanism — an emergency request is not considered "received" until the client gets a 200 response from the API, which happens only after the local Region durably commits the write
Operational readiness for Global Tables extends beyond replication mode. Region must have sufficient on-demand or provisioned capacity to absorb full production traffic if the primary becomes unavailable. During a Regional disruption, the DynamoDB control plane may be unreachable — you cannot modify table settings, add indexes, or change capacity modes until the disruption resolves. This means all structural configuration must be correct before an incident, not during one. Synthetic canaries — lightweight Lambda functions that perform test writes and reads on a schedule — validate that the failover Region is genuinely ready to serve traffic. The ReplicationLatency metric in CloudWatch measures the delay between a write in the source Region and its appearance in the replica; an alarm at sustained latency above 3,000ms for 5 minutes gives early warning before users notice stale data
Time to Live deletes in Global Tables are replicated to all other replicas — deletion propagates across Regions as a replicated write. However, each Region's TTL sweeper runs independently and may discover and delete the same expired item at slightly different times. The net effect is convergent: the item disappears everywhere, but the timing of physical removal varies by Region

Global Secondary Indexes

Global secondary indexes maintain their own partitions, separate from base table. When an item is written to the base table, DynamoDB asynchronously writes the projected attributes to the GSI partition. Write amplification means every base-table write also produces a write to each GSI that projects the modified attribute — a single PutItem can become two or three physical writes depending on how many indexes exist. If a GSI doesn't have sufficient write capacity, the base table itself throttles — the write amplification cost is not deferrable
When a GSI is added after table creation, DynamoDB performs a backfill. Backfill scans the entire base table, extracts projected attributes, and writes them to GSI partitions. Backfill consumes throughput from table and can take hours or days for large tables. During backfill, every new base-table write also updates the GSI, further increasing write amplification. Defining GSIs at table creation time avoids backfill entirely — the indexes are populated as items arrive, with no retroactive scan required
DynamoDB's metadata service maintains routing information for each GSI independently from base table. Partition splits on the base table do not directly cause GSI partition splits — each index manages its own partition lifecycle based on its own traffic patterns
Ðekawɔwɔ's activity_feed table has no GSIs on streaming write path, avoiding write amplification entirely on the hot ingestion side. The two GSIs (by-author, by-status) exist on access patterns that are read-heavy and write-light — they were defined at table creation, which means no backfill was ever required. The by-author index (partition key: author_id, sort key: sk) powers the user profile page. The by-status index (partition key: town#<id>#open, sort key: sk) powers the "open asks in my town" feed. Both use ProjectionType.ALL, trading storage for read performance by avoiding fetches back to the base table

Partition Temperature — Hot and Cold

Because DynamoDB spreads data across physical partitions, each partition develops its own traffic profile. Idle partitions are cold — engine reduces their resource allocation because nothing pressures them. Partitions absorbing burst traffic are hot — the engine increases throughput allocation and may split them. Ðekawɔwɔ sees this whenever a single town experiences an emergency: hundreds of writes to town#ho in minutes push that partition toward its throughput ceiling
Temperature in DynamoDB is relationship between incoming request volume and the throughput allocated to the partition. Once that relationship tilts too far, the engine adjusts
Hot partitions traffic symptoms — throughput counter spikes, request throttling, elevated per-partition latency — all are visible in CloudWatch and Contributor Insights. Underneath those symptoms, DynamoDB engine is dealing unobservable replication lag on that partition's key range as followers catch up on WAL entries, added load on the Paxos leader, and more frequent B-tree page splits as the partition absorbs writes faster than usual. A town-wide emergency is the real-world cause — hundreds of writes to town#ho in minutes. Adaptive capacity responds by automatically and instantly increasing the throughput allocated to that partition, as long as the table's total provisioned or on-demand capacity isn't exceeded. It cannot exceed the hard per-partition ceiling of 3,000 RCU or 1,000 WCU per second, though — if a single partition hits that ceiling, it throttles regardless of how much unused capacity exists elsewhere in the table, and if the imbalance persists, the partition splits
Hot partitions almost always trace back to poor key distribution. Adaptive capacity can mitigate imbalance but cannot fix a schema that concentrates writes on a small set of keys. Ðekawɔwɔ's time-bucketed composite keys (town#ho#2026-07-02T14) are specifically designed to distribute writes — even during an emergency, hourly bucket means that sustained traffic rolls into new partitions every 60 minutes, preventing any single partition from accumulating indefinite pressure. This is exactly the failure mode the partition key was built to prevent
Contributor Insights surfaces which partition key values are consuming most throughput and throwing the most throttle events. During an emergency in Ho, Contributor Insights would show town#ho#2026-07-02T14 as the top contributor — confirming that the burst is expected behavior from a single-town event rather than a schema deficiency requiring redesign
Warm throughput ensures overall table doesn't need to ramp from zero when a new emergency hits — but it does not prevent per-partition throttling. AWS documentation is explicit: a table can have high warm throughput and still throttle on individual partitions that exceed 1,000 WCU or 3,000 RCU. Time-bucketed key design remains the actual protection against hot-partition risk

Latency Variance

Variance that does exist comes from distributed-system mechanics: partition splits (which require B-tree redistribution and metadata service updates), Paxos leader elections during replica failures, transaction coordination across partitions for TransactWriteItems, GSI write amplification fan-out, and replica healing (which takes several minutes when a storage replica must be rebuilt from scratch, copying the full B-tree + WAL). TTL sweeps also contribute — when the service processes a batch of expired items, it issues a burst of delete operations that consume internal resources on that partition
DynamoDB hides most of this behind stable single-digit-millisecond p50 and p99 because B-tree lookups have bounded depth (O(log n) with a fixed page size), WAL is append-only (no seek overhead for writes), and DynamoDB automatically splits a partition once it approaches its throughput or storage limits — keeping any single B-tree's depth from growing without bound. After a town emergency resolves and dozens of ephemeral pins expire, TTL sweeps generate a burst of delete operations on that partition — a brief elevation in internal activity that is typically invisible to the application because the deletes are processed as background operations that do not consume table throughput in the source Region

DynamoDB in Production Applications

These internal behaviors matter because Ðekawɔwɔ's workload stresses DynamoDB in predictable ways. Event layers produce small, frequent, keyed writes that align with every optimization the engine provides — and the failure modes map cleanly to operational signals we can monitor
Partition key design is foundational. Time-bucketed composite keys (town#ho#2026-07-02T14) distribute writes across partitions and avoid hot keys. Sort keys encode timestamp plus event ID, giving natural chronological ordering and bounded partition growth. The design ensures that even during a town-wide emergency, no single partition absorbs more than one hour of traffic before writes roll to a new key. Write sharding (town#ho#<shard_N> where N = hash of user_id mod 10) is available if monitoring shows a single town consistently approaching the 1,000 WCU per-partition ceiling, but current traffic projections indicate this is unnecessary until 100,000 simultaneous users concentrate in a single town
The Streams pipeline connects event layers and our Amazon Aurora PostgreSQL geospatial engine. Every write to activity_feed with geographic coordinates flows through DynamoDB Streams to the geo-mirror Lambda, which extracts latitude and longitude and inserts into PostGIS activity_geo. The insertion is idempotent (ON CONFLICT DO NOTHING on event_id), so retries from BisectBatchOnFunctionError or network hiccups never produce duplicate map pins. FilterCriteria ensures non-geographic events never invoke the Lambda at all
Time To Live chains handle cleanup without human intervention. Ephemeral pins write with a TTL attribute. The sweeper deletes them. REMOVE events flow through Streams. The geo-mirror Lambda catches those events and deletes the corresponding PostGIS records. The entire lifecycle — creation, display, expiration, cleanup — is event-driven and requires no scheduled jobs
On-demand capacity absorbs emergency bursts without pre-provisioning, up to double the table's previous peak instantly and beyond that with ramp time (AWS documents ~30 minutes at 100,000+ req/sec scale). Adaptive capacity handles sustained per-partition imbalance by increasing that partition's allocation, up to the hard 3,000 RCU / 1,000 WCU per-partition ceiling. Together they mean that Ðekawɔwɔ rarely throttles during the exact moments when users need the system most — during an emergency when write volume spikes — provided the time-bucketed key design keeps any single partition well under that ceiling
VPC Gateway Endpoints allow PostGIS Lambda functions to access DynamoDB without NAT Gateways. This is a cost decision as much as a networking one: NAT Gateways cost $0.045/hour (~$32.85/month) plus $0.045/GB processed, while VPC Gateway Endpoints for DynamoDB and S3 are free. The geo-mirror Lambda sits in a VPC (required for PostGIS access via private subnet) but reaches DynamoDB through the gateway endpoint at zero additional cost
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
QUERY: Sum sizes, round ONCE
┌──────────────────────────────────────────────────────────────────────┐
│ ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐ │
│ │▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓││▓▓│ │
│ └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘ │
│ ┌──┐┌──┐┌──┐ │
│ │▓▓││▓▓││▓▓│ ← 18 items in one partition read │
│ └──┘└──┘└──┘ │
└──────────────────────────────────────────────────────────────────────┘
│
─────────────┘
│
▼
┌───────────┐
│ 25 RCU │
└───────────┘
BATCHGETITEM: Round EACH item individually
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐
│ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │
└────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐
│ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ │ ▓▓ │ ← 15 individual
└────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ gets, each rounded
│ │ │ │ │ │ │ to 4 KB
└──────┴──────┴──┬───┴──────┴──────┴──────┘
│
▼
┌───────────┐
│ 50 RCU │ ← 2x the cost!
└───────────┘
Event layers remain independent from Aurora PostgreSQL and Aurora DSQL. DynamoDB does not know about PostGIS. PostGIS does not know about DSQL. The Streams pipeline is the only coupling, and it is asynchronous, unidirectional, and failure-isolated. If PostGIS goes down, DynamoDB continues accepting writes. If DynamoDB Streams falls behind, PostGIS continues serving reads from its existing data. The databases scale on different axes, fail independently, recovering without coordination. As solutions architects, our blast radius is fully minimized to the best the cloud has to offer

Operational Tooling and Ecosystem

Point-in-time recovery (PITR) continuously backs up table data with per-second granularity, with a configurable retention window between 1 and 35 days (35 is maximum, not a fixed value). Restores create a new table — not an in-place rollback. If a bad Lambda deployment corrupts data or an errant script deletes items it should not, you restore the table to the exact second before the incident. PITR is enabled on all four Ðekawɔwɔ tables as a non-negotiable baseline — the cost is minimal and the alternative is data loss
S3 Export lets you snapshot an entire table to S3 in DynamoDB JSON or Apache Ion format without consuming any table throughput. Zero-ETL integrations extend this further: DynamoDB can replicate data directly to Amazon OpenSearch Service, Amazon SageMaker Lakehouse, or Amazon Redshift without building a custom export pipeline. For Ðekawɔwɔ, future use cases include historical pattern analysis — "what types of emergencies are common in the rainy season?" — running analytical queries over months of event data without touching the operational table
Import from S3 lets you seed a new table from S3 data without consuming write capacity units — billing is per-GB of uncompressed data processed rather than per-item writes. This is significant: it works only for new tables. Cannot import into an existing table. For Ðekawɔwɔ, the initial seed data could have used S3 Import; the actual seeding used direct PutItem calls instead, but the path remains available for future scenarios like bootstrapping a new Region's tables from an S3 Export of the primary
ExtendDB is an open-source DynamoDB-compatible adapter implementing DynamoDB API with pluggable storage backends (PostgreSQL at launch), useful for local integration testing without provisioning actual tables
DAX (DynamoDB Accelerator) is an in-memory cache that reduces read latency from single-digit milliseconds to microseconds — relevant for read-heavy workloads with repeated access to the same keys (gaming leaderboards, session stores). It requires VPC placement with always-on nodes. Ðekawɔwɔ's write-heavy access pattern and sub-10ms Query latency mean DAX would add cost without meaningful user-facing improvement
DynamoDB supports two table classes: Standard and Standard-IA (Infrequent Access). Standard-IA reduces storage cost by 60 percent but increases read and write request costs by 25 percent — break-even favors Standard-IA when storage dominates and requests are rare. Ðekawɔwɔ's active tables use Standard. A future archive table for resolved emergency records would be a natural fit for Standard-IA
Resource-based policies allow cross-account table access without assuming roles. table-per-period pattern creates a new table per time window and drops entire tables when the period expires, avoiding the write capacity cost of individual deletes at scale. Both patterns exist for future growth scenarios — multi-account separation and monthly archival workflows respectively — but neither applies to the current single-account, TTL-based architecture
Three databases, three access patterns, three scaling axes. DynamoDB absorbs writes. Amazon Aurora PostgreSQL PostGIS answers spatial queries. DSQL enforces transactional integrity on the social graph. We did our best to follow AWS Well Architected pillars and so far our billing on our app running in production is next-to-zero for these databases as we're still testing
This separation pattern can be seen in Amazon's FinTech treasury platform - using Neptune for graph topology (payment routing paths between legal entities) alongside DynamoDB for key-value reference data (entity attributes, bank account details, foundational mappings looked up by key rather than traversed) — same principles of letting each engine do what it does best guided by access patterns
This content was created following entry to the H0: Hack the Zero Stack with Vercel v0 and AWS
Databases hackathon. "Front-end in minutes. back-end designed to scale." #H0Hackathon
thank you for reading - ^.^

Series: AWS Databases (3 articles)

  1. 3
    Amazon DynamoDB Primer -- Event Layer Database This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article