Siddhant Deval
Siddhant Deval
backend25 min read

DynamoDB at Scale: Partition Internals, GSI Backpressure & ACID Transactions

DynamoDB's performance ceiling is determined at schema design time — a partition key with insufficient cardinality creates a hot partition that table-level throughput scaling cannot fix, because the limit is enforced per-partition at the storage node. This article covers partition key hashing, per-partition hard limits, hot partition detection and salt-key mitigation, GSI backpressure propagation, ACID transactions, and Global Tables conflict resolution.

DynamoDB at Scale: Partition Internals, GSI Backpressure & ACID Transactions

Every AWS primitive is a tradeoff surface, not a feature toggle. DynamoDB's "scale to any workload" promise has a hard asterisk: per-partition hard limits. A table provisioned at 10,000 WCU cannot write faster than 1,000 WCU to a single partition key, because that limit is enforced at the storage node — not at the table control plane. This is why provisioning more capacity does not fix hot-partition throttling. This article covers the physical storage model that produces this constraint, the GSI backpressure failure mode that causes base table write throttling from an under-provisioned index, and the ACID transaction semantics that enable financial-grade consistency in a non-relational system.

Prerequisite chain: Document & NoSQL Databases: MongoDB & DynamoDB → Part 7: Access-Pattern-First Modeling → this article.


1. Partition Internals — The Storage Model

DynamoDB stores data in partitions — independently managed storage and compute units. Understanding the partition model is the prerequisite for understanding every performance and cost constraint in DynamoDB.

1.1 Partition Key Hashing

DynamoDB uses an internal hashing algorithm to map partition key values to physical partitions. The hash distributes keys across partitions to balance load. The key insight: all items with the same partition key value land on the same partition, served by the same storage node.

Hash function: partition_key_value → partition_node

"USER#u001" → partition_node_A
"USER#u002" → partition_node_B
"USER#u003" → partition_node_C
"STATUS#PENDING" → partition_node_A  ← ALL pending orders hit the SAME node

If 100,000 orders all share STATUS#PENDING as their partition key, all 100,000 writes route to partition_node_A. Even if the table has 10,000 WCU provisioned, partition_node_A is hard-capped at 1,000 WCU/second.

1.2 Per-Partition Hard Limits

Limit Value Nature
Storage per partition 10 GB Hard limit — DynamoDB automatically splits at 10 GB
Read Capacity Units per partition 3,000 RCU/sec Per storage node — cannot be increased (~12 MB/s strong, ~24 MB/s eventual)
Write Capacity Units per partition 1,000 WCU/sec Per storage node — cannot be increased (~1 MB/s writes)
Maximum item size 400 KB Per item — binary blobs belong in S3
Crucial Requirement

The per-partition WCU and RCU limits are hard constraints enforced at the storage node level per second (1,000 WCU/sec and 3,000 RCU/sec). Increasing table-level provisioned capacity or switching to On-Demand mode does NOT increase these limits. Because capacity units scale with payload size, larger items directly reduce request throughput: 1,000 WCU/sec yields 1,000 writes/sec for 1 KB items, but only 500 writes/sec for 2 KB items, and just 2 writes/sec for 400 KB items. The only mitigation for a hot partition is to distribute writes across more partition key values.

1.3 Hot Partition Detection

Before mitigating a hot partition, you must isolate it. A hot partition exhibits a distinct signature: ConsumedWriteCapacityUnits or ConsumedReadCapacityUnits is well below the table-level provisioned or on-demand threshold, yet ThrottledRequests is non-zero and spikes continuously.

TYPESCRIPT
// CloudWatch Insights query: detect hot partition by divergence between consumed and provisioned
fields @timestamp, @message
| filter @message like "ProvisionedThroughputExceededException"
| stats count(*) as throttleCount by bin(5m)
| sort throttleCount desc

// Diagnostic metric signature:
// 1. SuccessfulRequestLatency P99 — spikes when partition nodes are stressed
// 2. ThrottledRequests — non-zero = throttling occurring
// 3. ConsumedWriteCapacityUnits vs ProvisionedWriteCapacityUnits divergence
//    → consumed << provisioned but still throttling = hot partition

Native Isolation: CloudWatch Contributor Insights

While table-level CloudWatch metrics confirm that throttling is occurring, they cannot identify which partition key is causing it. CloudWatch Contributor Insights for DynamoDB continuously tracks item-level telemetry and plots the exact partition keys responsible for throttled requests:

  • TopHotKeys: Isolates the exact partition key values (e.g., STATUS#PENDING or PRODUCT#flash-sale-sku) absorbing disproportionate write or read traffic.
  • TopThrottledKeys: Pinpoints which specific keys are exceeding the 1,000 WCU/sec or 3,000 RCU/sec storage node limits.

1.4 Hot Partition Mitigations

Depending on whether the hotspot stems from write concentration, un-sharded bulk ingestion, or high-concurrency read demand, different architectural patterns are required:

Pattern 1: Deterministic Suffix Sharding (High Writes with Direct Point Reads)

TYPESCRIPT
import { createHash } from 'crypto'

const SHARD_COUNT = 10 // Spreads writes across 10 distinct physical partition keys

// Deterministic hashing: same orderId always routes to the exact same shard
function getDeterministicShardedKey(orderId: string): string {
  const hash = parseInt(createHash('md5').update(orderId).digest('hex').slice(0, 8), 16)
  const shard = hash % SHARD_COUNT
  return `STATUS#PENDING#${shard}`
}

// Write: distributed evenly across STATUS#PENDING#0 through STATUS#PENDING#9
await docClient.send(new PutCommand({
  TableName: 'AppTable',
  Item: {
    PK: getDeterministicShardedKey(orderId),
    SK: `ORDER#${orderId}`,
    orderId,
    amount: 149.99,
  },
}))

// Direct Point Read: single round-trip without scatter-gather because orderId is known
async function getOrder(orderId: string) {
  return docClient.send(new GetCommand({
    TableName: 'AppTable',
    Key: {
      PK: getDeterministicShardedKey(orderId),
      SK: `ORDER#${orderId}`,
    },
  }))
}

Failure Modes & Limitations:

  • Input Skew Bottleneck: Deterministic sharding relies on high cardinality in the hash input (orderId). If you hash an attribute with skewed distribution (e.g., hashing tenantId where one enterprise customer generates 90% of traffic), writes will still concentrate on a single shard.
  • Scatter-Gather for Range Queries: If you need to list all pending orders, you must issue 10 parallel queries across all shards (Promise.all) and merge the results in memory, multiplying RCU consumption by the shard count.

Pattern 2: Random Suffix Sharding (Bulk Ingestion & Queue Draining)

TYPESCRIPT
// For workloads where writes never need to be looked up by a single key,
// but are processed in bulk (e.g., streaming audit logs or background worker queues)
function getRandomShardedKey(prefix: string, maxShards: number): string {
  const randomShard = Math.floor(Math.random() * maxShards)
  return `${prefix}#${randomShard}`
}

// Ingestion write: uniform random distribution across 20 shards
await docClient.send(new PutCommand({
  TableName: 'AppTable',
  Item: {
    PK: getRandomShardedKey('EVENT#AUDIT', 20),
    SK: `TS#${Date.now()}#${crypto.randomUUID()}`,
    payload: auditData,
  },
}))

// Bulk Read: scatter-gather across all 20 shards in parallel
async function drainAuditEvents(shardCount: number) {
  const queries = Array.from({ length: shardCount }, (_, i) =>
    docClient.send(new QueryCommand({
      TableName: 'AppTable',
      KeyConditionExpression: 'PK = :pk',
      ExpressionAttributeValues: { ':pk': `EVENT#AUDIT#${i}` },
      Limit: 100,
    }))
  )
  const pages = await Promise.all(queries)
  return pages.flatMap((page) => page.Items ?? [])
}

Failure Modes & Limitations:

  • Unindexed Point Lookups: Because items are assigned to random shards, single-item lookups by entity ID become impossible without querying every shard or maintaining a separate index.
  • Uneven Shard Draining: In scatter-gather pagination, different shards drain at different speeds; tracking pagination cursors (ExclusiveStartKey) across 20 asynchronous shard streams requires complex state coordination.

Pattern 3: Upstream Write Buffering (Smoothing Flash Surges Over 1,000 WCU/sec)

TYPESCRIPT
// When a single logical item (e.g., flash sale SKU or event ticketing inventory)
// experiences momentary write bursts exceeding 1,000 writes/sec,
// application-level sharding fails because all writes target the same physical counter.

// Architecture: Decouple synchronous client writes via SQS FIFO
// Client Request → API Gateway → SQS FIFO (MessageGroupId = skuId) → Lambda Processor → DynamoDB

export const handler = async (event: SQSEvent) => {
  // Lambda receives batches of up to 10 purchases for the same SKU
  // Consolidates 10 individual decrements into a single atomic mutation
  const totalQuantity = event.Records.reduce((sum, r) => sum + JSON.parse(r.body).quantity, 0)
  
  await docClient.send(new UpdateCommand({
    TableName: 'AppTable',
    Key: { PK: 'PRODUCT#taylor-swift-tix', SK: 'INVENTORY' },
    UpdateExpression: 'SET inventory = inventory - :total',
    ConditionExpression: 'inventory >= :total',
    ExpressionAttributeValues: { ':total': totalQuantity },
  }))
}

Failure Modes & Limitations:

  • Asynchronous User Experience: Clients cannot receive immediate synchronous confirmation of their purchase; the frontend must poll an order status endpoint, listen on a WebSocket, or await an asynchronous webhook.
  • Queue Head-of-Line Blocking: If a poisonous message or processing error causes batch execution to fail, SQS FIFO halts processing for that entire MessageGroupId until the message retries or reaches the DLQ.

Pattern 4: Read-Side Hotspot Shielding with DAX (Surpassing 3,000 RCU/sec)

TYPESCRIPT
// For read hotspots (the "Celebrity Profile" problem) where a single item
// is requested 50,000 times/sec, blowing past the 3,000 RCU/sec limit.
// Sharding a single profile item is impossible.

// Solution: Amazon DynamoDB Accelerator (DAX) in-memory cache layer
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
import AmazonDaxClient from 'amazon-dax-client'

// DAX client provides seamless API-compatible microsecond in-memory reads
const daxClient = new AmazonDaxClient({ endpoints: ['dax-cluster.xyz.dax-clusters.cache.amazonaws.com:8111'] })
const docClientWithDax = DynamoDBDocumentClient.from(daxClient)

// Point read: hits in-memory microsecond cache, bypassing DynamoDB storage nodes completely
const celebrityProfile = await docClientWithDax.send(new GetCommand({
  TableName: 'AppTable',
  Key: { PK: 'USER#celebrity', SK: 'PROFILE' },
}))

Failure Modes & Limitations:

  • Zero Write Protection: DAX is a write-through cache; writes still pass through to DynamoDB and are subject to the hard 1,000 WCU/sec partition limit. DAX solves read hotspots, never write hotspots.
  • Cache Eviction & Stampedes: During cluster failover or memory pressure evictions, sudden cold-cache misses trigger a read stampede that cascades directly to the underlying DynamoDB partition, causing immediate throttling.

2. GSI Backpressure — The Upstream Write Throttle

This is the most dangerous DynamoDB failure mode because it is invisible: a GSI under-provisioned relative to the base table write rate throttles base table writes, not GSI reads.

2.1 The Failure Chain

Scenario: AppTable writes 1,000 WPS. GSI1 provisioned at 10 WCU.

Every base table write that updates an item with GSI1 key attributes
  → triggers a GSI1 write
  → GSI1 is provisioned at 10 WCU → can only handle 10 writes/second
  → at 1,000 WPS, GSI1 is overwhelmed

Effect: DynamoDB throttles the BASE TABLE write
  → ProvisionedThroughputExceededException on PutItem/UpdateItem calls
  → Error surfaces in the application as a base table write failure
  → CloudWatch shows base table ThrottledWrites = high
  → Engineers increase BASE TABLE WCU → no improvement (GSI is the bottleneck)
  → Root cause: GSI1 capacity, not base table capacity

2.2 Monitoring and Mitigation

TYPESCRIPT
// Required CloudWatch alarms — one per GSI, not just per table
new Alarm(this, 'GSI1WriteThrottle', {
  metric: new Metric({
    namespace: 'AWS/DynamoDB',
    metricName: 'WriteThrottleEvents',
    dimensionsMap: {
      TableName: 'AppTable',
      GlobalSecondaryIndexName: 'GSI1', // ← Must specify the GSI name
    },
    statistic: 'Sum',
    period: Duration.minutes(1),
  }),
  threshold: 0,
  evaluationPeriods: 1,
  comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
  alarmDescription: 'GSI1 write throttling is causing base table write failures',
})

// GSI capacity must be provisioned at the same rate as the base table
// For On-Demand mode: GSI capacity scales with the table automatically
// For Provisioned mode: set GSI RCU/WCU to at least the expected write rate per item
Crucial Requirement

On-Demand mode eliminates GSI backpressure — DynamoDB scales GSI capacity automatically with table write throughput. For Provisioned mode, the GSI WCU must be sized to match the write rate of items that carry the GSI key. A GSI on a low-cardinality attribute (status field with 3 values) concentrates writes and is a hot-partition risk even in On-Demand mode.


3. ACID Transactions

DynamoDB supports ACID transactions via TransactWriteItems (up to 25 write operations) and TransactGetItems (up to 25 read operations) across any number of tables in the same account and region.

3.1 TransactWriteItems — Cross-Item Consistency

TYPESCRIPT
// Use case: order placement — must atomically:
// 1. Create the order item
// 2. Decrement product inventory
// 3. Create the ledger entry
// All three succeed, or none of them succeed

await docClient.send(new TransactWriteCommand({
  TransactItems: [
    {
      Put: {
        TableName: 'AppTable',
        Item: {
          PK: `ORDER#${orderId}`,
          SK: 'METADATA',
          status: 'PENDING',
          total: 149.99,
          userId,
        },
        ConditionExpression: 'attribute_not_exists(PK)', // Idempotency guard
      }
    },
    {
      Update: {
        TableName: 'AppTable',
        Key: { PK: `PRODUCT#${productId}`, SK: 'INVENTORY' },
        UpdateExpression: 'SET inventory = inventory - :qty',
        ConditionExpression: 'inventory >= :qty', // Prevent negative inventory
        ExpressionAttributeValues: { ':qty': quantity }
      }
    },
    {
      Put: {
        TableName: 'LedgerTable', // Cross-table transaction
        Item: {
          PK: `LEDGER#${orderId}`,
          SK: `DEBIT#${Date.now()}`,
          amount: -149.99,
          reason: 'order_payment',
        }
      }
    }
  ],
  // Idempotency token: safe to retry with same token — duplicate has no effect
  ClientRequestToken: idempotencyKey,
}))
Crucial Requirement

TransactWriteItems consumes 2× WCU per item — once for the write operation and once for the transactional overhead. A 3-item transaction costs 6 WCU minimum (2 WCU per 1 KB block per item). At high write rates, this doubles the effective write cost. Use transactions only when cross-item consistency is a genuine business requirement — not as a convenience wrapper for multi-item writes that do not require atomicity. For the complete capacity unit pricing model, 4 KB read aggregation rules, and On-Demand vs. Provisioned math, see Section 8: DynamoDB Cost Taxonomy.

3.2 Conditional Writes — Idempotency, OCC & Guard Patterns

Conditional writes turn single-item mutations into atomic predicate evaluations at the storage engine level. They enforce critical business invariants without paying the 2× WCU overhead of full transactions.

Pattern 1: Bounded Atomic Mutation (Safe Counter without Pre-Read)

TYPESCRIPT
// ❌ Naive atomic counter — NOT idempotent, unbounded
// On timeout retry, decrements TWICE. If inventory = 0, decrements to -1.
await docClient.send(new UpdateCommand({
  TableName: 'AppTable',
  Key: { PK: 'PRODUCT#p1', SK: 'INVENTORY' },
  UpdateExpression: 'ADD inventory :delta',
  ExpressionAttributeValues: { ':delta': -1 },
}))

// ✅ Bounded atomic decrement — atomic, no prior read required, stock never drops below zero
await docClient.send(new UpdateCommand({
  TableName: 'AppTable',
  Key: { PK: 'PRODUCT#p1', SK: 'INVENTORY' },
  UpdateExpression: 'SET inventory = inventory - :qty',
  ConditionExpression: 'inventory >= :qty',
  ExpressionAttributeValues: { ':qty': 1 },
}))
// If inventory < 1 → ConditionalCheckFailedException (zero state mutation, out of stock)

Failure Modes & Limitations:

  • Hot Partition Bottleneck: All concurrent purchases target the exact same partition key (PRODUCT#p1), hard-capped at 1,000 WCU/sec. During flash sales, high write volume causes ProvisionedThroughputExceededException regardless of total table capacity.
  • Zero Audit Lineage: Decrementing in-place doesn't record which order or customer consumed the inventory. Production systems must combine this with an order item creation in a transaction or write an audit log to maintain reconciliation records.

Pattern 2: Idempotent Insert (First-Write-Wins Guard)

TYPESCRIPT
// Prevents duplicate API requests or retried webhooks from overwriting existing data
await docClient.send(new PutCommand({
  TableName: 'AppTable',
  Item: {
    PK: `PAYMENT#${paymentIntentId}`,
    SK: 'METADATA',
    amount: 99.00,
    status: 'AUTHORIZED',
    createdAt: new Date().toISOString(),
  },
  ConditionExpression: 'attribute_not_exists(PK)', // First write wins; retries fail safely
}))

Failure Modes & Limitations:

  • Composite Key Scope: On tables with a composite primary key (PK + SK), attribute_not_exists(PK) checks the entire item identity (PK AND SK). It will not prevent inserting a duplicate paymentIntentId if the sort key differs.
  • Upstream Exception Handling: When a duplicate request arrives, DynamoDB throws ConditionalCheckFailedException. Upstream API handlers must explicitly catch this error and return HTTP 200 OK (idempotent success) or return the existing record instead of bubbling an unhandled 500 error to clients.

Pattern 3: Versioned Optimistic Locking (Preventing the ABA Problem)

TYPESCRIPT
// Value-matching OCC (e.g., status = :expected) is vulnerable to the ABA problem:
// Thread A reads "ACTIVE". Thread B sets "SUSPENDED". Thread C restores "ACTIVE".
// Thread A's update succeeds despite two intermediate mutations occurring!

// ✅ Monotonic version integer guarantees strict chronological isolation
await docClient.send(new UpdateCommand({
  TableName: 'AppTable',
  Key: { PK: 'ORDER#o99', SK: 'METADATA' },
  UpdateExpression: 'SET deliveryAddress = :addr, version = version + :inc',
  ConditionExpression: 'version = :currentVersion',
  ExpressionAttributeValues: {
    ':addr': '742 Evergreen Terrace',
    ':currentVersion': existingOrder.version,
    ':inc': 1,
  },
}))

Failure Modes & Limitations:

  • Retry Storms Under High Contention: Optimistic concurrency assumes collisions are rare. If dozens of workers concurrently attempt to update the same item (e.g., reserving seats on the same flight), all but one will fail the version check. If clients immediately retry, they trigger exponential retry storms and waste WCU on rejected writes. High-contention workflows should be serialized via SQS FIFO or write-sharded instead.

Pattern 4: State Machine Transition Guard

TYPESCRIPT
// Enforces valid lifecycle transitions directly in the storage engine
// An order can transition to CANCELLED only if currently in PENDING or REVIEW status
await docClient.send(new UpdateCommand({
  TableName: 'AppTable',
  Key: { PK: 'ORDER#o99', SK: 'METADATA' },
  UpdateExpression: 'SET #status = :newStatus, cancelledAt = :now',
  ConditionExpression: 'attribute_exists(PK) AND #status IN (:pending, :review)',
  ExpressionAttributeNames: {
    '#status': 'status', // Reserved keyword protection
  },
  ExpressionAttributeValues: {
    ':newStatus': 'CANCELLED',
    ':pending': 'PENDING',
    ':review': 'REVIEW',
    ':now': new Date().toISOString(),
  },
}))

Failure Modes & Limitations:

  • Sibling State Races: Transition guards prevent backward state jumps, but cannot arbitrate two competing, valid transitions from the same starting state (e.g., Worker 1 executes PENDING → PROCESSING while Worker 2 executes PENDING → CANCELLED). The first write wins; the loser receives ConditionalCheckFailedException and must execute explicit compensation logic (e.g., refunding payment if processing lost the race).
  • Reserved Keyword Syntax Traps: Attributes like status, date, order, and percent are DynamoDB reserved keywords. Forgetting ExpressionAttributeNames causes an immediate ValidationException.

Pattern 5: Distributed Mutex Lease Lock

TYPESCRIPT
// Acquire a distributed lock without Redis; auto-expires if worker crashes
const now = Date.now()
const leaseTtl = now + 30_000 // 30-second lease window

await docClient.send(new UpdateCommand({
  TableName: 'AppTable',
  Key: { PK: 'LOCK#reconciliation', SK: 'LOCK' },
  UpdateExpression: 'SET leaseOwner = :workerId, leaseExpiresAt = :expiresAt',
  ConditionExpression: 'attribute_not_exists(leaseOwner) OR leaseExpiresAt < :now',
  ExpressionAttributeValues: {
    ':workerId': 'worker-node-42',
    ':expiresAt': leaseTtl,
    ':now': now,
  },
}))

Failure Modes & Limitations:

  • Client Clock Skew (NTP Drift): DynamoDB does not evaluate server-side timestamps in ConditionExpression; :now is supplied by the client application. If a worker node suffers NTP clock drift and its system clock runs fast, it can prematurely steal the lease from an actively executing node.
  • GC Pauses and Fencing Violations: If worker A experiences a 35-second Garbage Collection or network pause, its 30-second lease expires and worker B acquires the lock. When worker A wakes up, it may write corrupt data assuming it still owns the lock unless guarded by a monotonic fencing token on every downstream mutation.
Pro Tip & Optimization

Conditional Write Cost Rule: When a ConditionExpression evaluates to false, DynamoDB throws ConditionalCheckFailedException and consumes the full write capacity (WCU) of the attempted write. While conditional writes avoid the 2× WCU multiplier of TransactWriteItems, high contention on a single key can consume significant WCU on rejected writes. For hot items, rate-limit upstream or use write sharding (Section 1.4).


4. DynamoDB Streams and Lambda Integration

TYPESCRIPT
// CDK: DynamoDB Streams with Lambda ESM and filter expressions
import { Table, StreamViewType } from 'aws-cdk-lib/aws-dynamodb'
import { DynamoEventSource, StartingPosition } from 'aws-cdk-lib/aws-lambda-event-sources'
import { FilterCriteria, FilterRule } from 'aws-cdk-lib/aws-lambda'

const table = new Table(this, 'AppTable', {
  stream: StreamViewType.NEW_AND_OLD_IMAGES, // Best for audit; most expensive (full payloads)
})

// ESM filter: only invoke Lambda for INSERT events on ORDER items
// Eliminates unnecessary Lambda invocations for other entity types
processorFn.addEventSource(new DynamoEventSource(table, {
  startingPosition: StartingPosition.TRIM_HORIZON,
  batchSize: 100,
  bisectBatchOnFunctionError: true,
  maxRecordAge: Duration.hours(1),
  retryAttempts: 3,
  onFailure: new SqsDestination(dlq),
  filters: [
    FilterCriteria.filter({
      eventName: FilterRule.isEqual('INSERT'),
      dynamodb: {
        NewImage: {
          entityType: { S: FilterRule.isEqual('ORDER') }
        }
      }
    })
  ]
}))
Stream view type Content Use case Cost
KEYS_ONLY PK + SK only Change detection Lowest
NEW_IMAGE New item state Event sourcing Medium
OLD_IMAGE Previous state Soft deletes, undo log Medium
NEW_AND_OLD_IMAGES Both states Full audit trail, diff detection Highest

5. On-Demand vs Provisioned — Cost Crossover

Attribute On-Demand Provisioned + Auto Scaling
Billing model Per request ($1.25/million WRU, $0.25/million RRU) Per hour ($0.00065/WCU-hr, $0.00013/RCU-hr)
Zero-traffic cost $0 Minimum provisioned hours × rate
Traffic spike handling Automatic Delayed by Auto Scaling (3–15 min lag)
Cost crossover Wins for spiky/unpredictable traffic Wins for steady, predictable traffic
Typical break-even ~200 WCU equivalent sustained ~200 WCU sustained 24/7
Partition mechanics: Table → hash ring → physical partitions (per-partition limits labeled) → hot partition in red distributing to green partitions via salt sharding
Partition mechanics: Table → hash ring → physical partitions (per-partition limits labeled) → hot partition in red distributing to green partitions via salt…
Comparison matrix: GSI vs LSI vs Sparse Index vs Table Scan across 7 criteria: consistency, creation constraints, cost, write amplification, cardinality requirements, backpressure risk, projection flexibility
Comparison matrix: GSI vs LSI vs Sparse Index vs Table Scan across 7 criteria: consistency, creation constraints, cost, write amplification, cardinality requ…

Summary

Concept Rule
Per-partition limits 1,000 WCU / 3,000 RCU per partition — table capacity cannot override these
Hot partition detection Consumed WCU << Provisioned WCU + ThrottledRequests > 0 = hot partition, not under-provisioning
Hot partition mitigation Sharding (deterministic/random), SQS FIFO write-buffering, and DAX read-shielding
GSI backpressure Under-provisioned GSI throttles base table writes — alarm on GSI metrics, not only table metrics
TransactWriteItems 2× WCU per item, 25 item max — use for genuine financial-grade consistency only
Conditional writes Enforces invariants (bounded decrements, versioned OCC, state transitions, mutex leases) at 1× WCU

What's Next

In Part 9: Messaging Foundations — SQS, SNS & Fan-Out Patterns, we shift from the data layer to the messaging layer: why SNS and SQS solve completely different problems, what happens when you use SNS as a work queue, and how the visibility timeout is the mechanism behind SQS's at-least-once delivery guarantee.

Research & Synthesis Note

This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.

#DynamoDB#Partition Internals#Hot Partition#GSI#ACID#Global Tables#AWS
Siddhant Deval

Written by Siddhant Deval

Senior Full-Stack Engineer building high-scale architectures, browser performance engineering systems, and SaaS platforms.