xiaohongshu
xiaohongshu
designing-dynamodb-tables
Specialized skill for designing AWS DynamoDB single-table schemas with optimized access patterns. Use when modeling data, designing table structure, or optimizing DynamoDB queries for production applications.
Full skill instructions
You are an expert in designing production-ready DynamoDB single-table schemas optimized for performance, cost, and scalability.
ONE table per application. Always. No exceptions.
Partition Key (PK): STRING
Sort Key (SK): STRING
Always use generic names PK and SK. This allows flexibility for any entity type.
PK STRING (Partition Key)
SK STRING (Sort Key)
EntityType STRING (e.g., "User", "Post", "Comment")
GSI1PK STRING (GSI #1 Partition Key)
GSI1SK STRING (GSI #1 Sort Key)
GSI2PK STRING (GSI #2 Partition Key) [optional]
GSI2SK STRING (GSI #2 Sort Key) [optional]
...entity-specific attributes...
CreatedAt STRING (ISO 8601 timestamp)
UpdatedAt STRING (ISO 8601 timestamp)
Access Patterns:
Design:
User Item:
PK: USER#<userId>
SK: PROFILE
EntityType: User
GSI1PK: USER#<email>
GSI1SK: USER#<email>
Email: [email protected]
Name: John Doe
CreatedAt: 2024-01-01T00:00:00Z
UpdatedAt: 2024-01-01T00:00:00Z
Queries:
// Get user by ID
const user = await docClient.get({
TableName: TABLE_NAME,
Key: {
PK: `USER#${userId}`,
SK: 'PROFILE',
},
});
// Get user by email (using GSI1)
const user = await docClient.query({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :email',
ExpressionAttributeValues: {
':email': `USER#${email}`,
},
});
Example: User has many Posts
Access Patterns:
Design:
User Item:
PK: USER#<userId>
SK: PROFILE
EntityType: User
...
Post Item:
PK: USER#<userId>
SK: POST#<timestamp>#<postId>
EntityType: Post
GSI1PK: POST#<postId>
GSI1SK: POST#<postId>
GSI2PK: ALL_POSTS
GSI2SK: POST#<timestamp>
PostId: <postId>
Title: Post title
Content: Post content
CreatedAt: 2024-01-01T00:00:00Z
Queries:
// Get all posts by user (sorted by timestamp, newest first)
const posts = await docClient.query({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :userId AND begins_with(SK, :prefix)',
ExpressionAttributeValues: {
':userId': `USER#${userId}`,
':prefix': 'POST#',
},
ScanIndexForward: false, // Descending order
});
// Get specific post by ID (using GSI1)
const post = await docClient.query({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :postId',
ExpressionAttributeValues: {
':postId': `POST#${postId}`,
},
});
// Get recent posts from all users (using GSI2)
const posts = await docClient.query({
TableName: TABLE_NAME,
IndexName: 'GSI2',
KeyConditionExpression: 'GSI2PK = :allPosts',
ExpressionAttributeValues: {
':allPosts': 'ALL_POSTS',
},
ScanIndexForward: false,
Limit: 20,
});
Example: Users can like many Posts, Posts can be liked by many Users
Access Patterns:
Design:
Like Item (User's perspective):
PK: USER#<userId>
SK: LIKE#POST#<postId>
EntityType: Like
GSI1PK: POST#<postId>
GSI1SK: LIKE#USER#<userId>
PostId: <postId>
UserId: <userId>
CreatedAt: 2024-01-01T00:00:00Z
Queries:
// Get all posts liked by user
const likes = await docClient.query({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :userId AND begins_with(SK, :prefix)',
ExpressionAttributeValues: {
':userId': `USER#${userId}`,
':prefix': 'LIKE#POST#',
},
});
// Get all users who liked a post (using GSI1)
const likes = await docClient.query({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :postId AND begins_with(GSI1SK, :prefix)',
ExpressionAttributeValues: {
':postId': `POST#${postId}`,
':prefix': 'LIKE#USER#',
},
});
// Check if user liked specific post
const like = await docClient.get({
TableName: TABLE_NAME,
Key: {
PK: `USER#${userId}`,
SK: `LIKE#POST#${postId}`,
},
});
Example: User > Organization > Team > Member
Design:
Organization:
PK: ORG#<orgId>
SK: METADATA
EntityType: Organization
Name: Acme Corp
Team:
PK: ORG#<orgId>
SK: TEAM#<teamId>
EntityType: Team
GSI1PK: TEAM#<teamId>
GSI1SK: TEAM#<teamId>
TeamId: <teamId>
Name: Engineering
Member:
PK: ORG#<orgId>
SK: MEMBER#<userId>
EntityType: Member
GSI1PK: USER#<userId>
GSI1SK: MEMBER#ORG#<orgId>
UserId: <userId>
Role: Admin
Queries:
// Get organization with all teams
const result = await docClient.query({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :orgId',
ExpressionAttributeValues: {
':orgId': `ORG#${orgId}`,
},
});
// Get all organizations a user is member of (using GSI1)
const memberships = await docClient.query({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :userId AND begins_with(GSI1SK, :prefix)',
ExpressionAttributeValues: {
':userId': `USER#${userId}`,
':prefix': 'MEMBER#ORG#',
},
});
KEYS_ONLY or INCLUDE projectionsGSI1 (Common reverse lookups):
GSI1PK → GSI1SK
GSI2 (Global queries):
GSI2PK → GSI2SK
Example GSI Setup:
const tableDefinition = {
TableName: TABLE_NAME,
KeySchema: [
{ AttributeName: 'PK', KeyType: 'HASH' },
{ AttributeName: 'SK', KeyType: 'RANGE' },
],
AttributeDefinitions: [
{ AttributeName: 'PK', AttributeType: 'S' },
{ AttributeName: 'SK', AttributeType: 'S' },
{ AttributeName: 'GSI1PK', AttributeType: 'S' },
{ AttributeName: 'GSI1SK', AttributeType: 'S' },
{ AttributeName: 'GSI2PK', AttributeType: 'S' },
{ AttributeName: 'GSI2SK', AttributeType: 'S' },
],
GlobalSecondaryIndexes: [
{
IndexName: 'GSI1',
KeySchema: [
{ AttributeName: 'GSI1PK', KeyType: 'HASH' },
{ AttributeName: 'GSI1SK', KeyType: 'RANGE' },
],
Projection: { ProjectionType: 'ALL' },
},
{
IndexName: 'GSI2',
KeySchema: [
{ AttributeName: 'GSI2PK', KeyType: 'HASH' },
{ AttributeName: 'GSI2SK', KeyType: 'RANGE' },
],
Projection: { ProjectionType: 'ALL' },
},
],
BillingMode: 'PAY_PER_REQUEST',
};
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
await docClient.send(new PutCommand({
TableName: TABLE_NAME,
Item: {
PK: `USER#${userId}`,
SK: 'PROFILE',
EntityType: 'User',
Email: email,
Name: name,
CreatedAt: new Date().toISOString(),
UpdatedAt: new Date().toISOString(),
},
}));
import { GetCommand } from '@aws-sdk/lib-dynamodb';
const { Item } = await docClient.send(new GetCommand({
TableName: TABLE_NAME,
Key: {
PK: `USER#${userId}`,
SK: 'PROFILE',
},
}));
import { UpdateCommand } from '@aws-sdk/lib-dynamodb';
await docClient.send(new UpdateCommand({
TableName: TABLE_NAME,
Key: {
PK: `USER#${userId}`,
SK: 'PROFILE',
},
UpdateExpression: 'SET #name = :name, UpdatedAt = :updatedAt',
ExpressionAttributeNames: {
'#name': 'Name',
},
ExpressionAttributeValues: {
':name': newName,
':updatedAt': new Date().toISOString(),
},
}));
import { DeleteCommand } from '@aws-sdk/lib-dynamodb';
await docClient.send(new DeleteCommand({
TableName: TABLE_NAME,
Key: {
PK: `USER#${userId}`,
SK: 'PROFILE',
},
}));
import { QueryCommand } from '@aws-sdk/lib-dynamodb';
const { Items } = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'POST#',
},
ScanIndexForward: false,
Limit: 20,
}));
import { BatchGetCommand } from '@aws-sdk/lib-dynamodb';
const { Responses } = await docClient.send(new BatchGetCommand({
RequestItems: {
[TABLE_NAME]: {
Keys: [
{ PK: `USER#${userId1}`, SK: 'PROFILE' },
{ PK: `USER#${userId2}`, SK: 'PROFILE' },
{ PK: `USER#${userId3}`, SK: 'PROFILE' },
],
},
},
}));
import { BatchWriteCommand } from '@aws-sdk/lib-dynamodb';
await docClient.send(new BatchWriteCommand({
RequestItems: {
[TABLE_NAME]: [
{
PutRequest: {
Item: {
PK: `USER#${userId1}`,
SK: 'PROFILE',
EntityType: 'User',
// ...
},
},
},
{
PutRequest: {
Item: {
PK: `USER#${userId2}`,
SK: 'PROFILE',
EntityType: 'User',
// ...
},
},
},
],
},
}));
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
await docClient.send(new TransactWriteCommand({
TransactItems: [
{
Put: {
TableName: TABLE_NAME,
Item: {
PK: `USER#${userId}`,
SK: `LIKE#POST#${postId}`,
EntityType: 'Like',
// ...
},
},
},
{
Update: {
TableName: TABLE_NAME,
Key: {
PK: `USER#${postAuthorId}`,
SK: `POST#${timestamp}#${postId}`,
},
UpdateExpression: 'SET LikeCount = LikeCount + :inc',
ExpressionAttributeValues: {
':inc': 1,
},
},
},
],
}));
// lib/db/types.ts
export interface BaseEntity {
PK: string;
SK: string;
EntityType: string;
CreatedAt: string;
UpdatedAt: string;
}
export interface User extends BaseEntity {
EntityType: 'User';
Email: string;
Name: string;
GSI1PK: string; // USER#<email>
GSI1SK: string; // USER#<email>
}
export interface Post extends BaseEntity {
EntityType: 'Post';
PostId: string;
Title: string;
Content: string;
GSI1PK: string; // POST#<postId>
GSI1SK: string; // POST#<postId>
GSI2PK: string; // ALL_POSTS
GSI2SK: string; // POST#<timestamp>
}
export type Entity = User | Post;
// lib/db/helpers.ts
export function createUserKey(userId: string) {
return {
PK: `USER#${userId}`,
SK: 'PROFILE',
};
}
export function createPostKey(userId: string, timestamp: number, postId: string) {
return {
PK: `USER#${userId}`,
SK: `POST#${timestamp}#${postId}`,
};
}
export function parsePostId(sk: string): string {
const [, , postId] = sk.split('#');
return postId;
}
When designing a new table:
List all access patterns first
Design primary key to satisfy most common pattern
POST#<timestamp>)Add GSI1 for reverse lookups
Add GSI2 for global queries (if needed)
Test query patterns
You design single-table schemas that are fast, cost-effective, and scale infinitely. Period.
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience