DynamoDB
03 / 03

GSIs, Streams, DynamoDB Local & Interview Questions

DynamoDB: GSIs, Streams & Interview Questions

Global Secondary Indexes (GSI)

GSIs let you query on non-primary-key attributes. Each GSI has its own PK and optional SK, and maintains its own copy of the data — eventually consistent by default.

// Table: PK=USER#id, SK=ORDER#date
// Add GSI to query all orders by status (cross-user)
// GSI: PK=status, SK=createdAt

// Query the GSI
const { Items } = await db.send(new QueryCommand({
  TableName: TABLE,
  IndexName: 'GSI1',        // GSI name set during table creation
  KeyConditionExpression: 'GSI1PK = :status AND GSI1SK > :since',
  ExpressionAttributeValues: {
    ':status': 'ORDER_STATUS#pending',
    ':since': '2026-01-01',
  },
}))
  • Up to 20 GSIs per table (Local Secondary Indexes also available — same PK, different SK)

  • GSI consumes its own RCU/WCU — provision separately or use on-demand

  • Sparse indexes: only items with the GSI key attributes are indexed — useful for filtering

  • GSI overloading: reuse the same GSI for multiple entity types by putting entity-type prefixes in GSI keys

DynamoDB Streams

Streams capture a time-ordered sequence of item-level changes (INSERT, MODIFY, REMOVE). Use them to trigger Lambda functions, replicate data, or build event-driven systems.

// Enable on table: StreamSpecification = KEYS_ONLY | NEW_IMAGE | OLD_IMAGE | NEW_AND_OLD_IMAGES

// Lambda trigger automatically invoked with stream records
export const handler = async (event: DynamoDBStreamEvent) => {
  for (const record of event.Records) {
    if (record.eventName === 'INSERT') {
      const newItem = record.dynamodb?.NewImage
      // newItem attributes are in DynamoDB AttributeValue format
      // Use unmarshall from @aws-sdk/util-dynamodb to convert
    }
    if (record.eventName === 'MODIFY') {
      const old = record.dynamodb?.OldImage
      const updated = record.dynamodb?.NewImage
    }
    if (record.eventName === 'REMOVE') {
      const deleted = record.dynamodb?.OldImage
    }
  }
}

DynamoDB Local (Development)

# Run DynamoDB locally via Docker
docker run -p 8000:8000 amazon/dynamodb-local

# Point SDK to local instance
const client = new DynamoDBClient({
  region: 'local',
  endpoint: 'http://localhost:8000',
  credentials: { accessKeyId: 'fake', secretAccessKey: 'fake' }
})

# CLI with local
aws dynamodb list-tables --endpoint-url http://localhost:8000 --region us-east-1

Interview Questions

  • When to choose DynamoDB over PostgreSQL? Known access patterns, massive scale (millions of requests/sec), serverless/event-driven architecture. Avoid when: complex queries, ad-hoc reporting, many-to-many joins.

  • What is a hot partition and how do you avoid it? Single partition receiving too many requests. Fix: add random suffix to PK (write sharding), use higher-cardinality PKs, cache hot reads in ElastiCache.

  • Difference between Query and Scan? Query uses the index (efficient, O(results)); Scan reads the entire table (expensive, avoid). Always prefer Query with proper key design.

  • What is the maximum item size in DynamoDB? 400KB. For larger items, store in S3 and save the S3 key in DynamoDB.

  • How does single-table design differ from multi-table? Single-table stores all entities in one table with generic PK/SK — enables fetching related data in one Query. Multi-table is easier to reason about but requires multiple queries for related data.

  • How do you handle DynamoDB transactions? TransactWrite/TransactGet (up to 100 items, 4MB) — 2x cost. For high-volume: optimistic locking with ConditionExpression instead.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free