Mastra @mastra/dynamodb DynamoDB 单表设计方案落地:pk/sk 主键与 GSI1/GSI2 索引结构、CDK/CloudFormation 建表、TTL 配置与本地开发
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
Mastra 的@mastra/dynamodb存储包采用「单表设计 + ElectroDB」模式,把 memory、workflows、scores、background tasks 等多个领域的数据全部写入同一张 DynamoDB 表。本文基于仓库中的 TABLE_SETUP.md 展开,系统讲清建表所需的键结构(pk/sk+gsi1/gsi2两组全局二级索引)、各实体对索引的占用方式、CloudFormation 与 AWS CDK 两种建表模板、TTL(Time To Live)的启用与按实体配置方法,以及如何把这张表接入DynamoDBStore并在本地用 DynamoDB Local 调试。读完后你可以独立完成生产环境的建表、验证表结构是否满足所有索引查询需求,并正确配置自动数据过期。
单表设计与表结构要求
@mastra/dynamodb使用单表设计(single-table design)模式,底层通过 ElectroDB 管理实体与索引映射(见 ElectroDB 服务定义)。你只需创建一张DynamoDB 表,结构如下:
- 表名:可任意命名,但必须传给
DynamoDBStore构造函数的config.tableName; - 分区键(Partition Key):
pk(String); - 排序键(Sort Key):
sk(String); - 全局二级索引(GSI):
- GSI1:分区键
gsi1pk(String),排序键gsi1sk(String); - GSI2:分区键
gsi2pk(String),排序键gsi2sk(String)。
- GSI1:分区键
GSI 的意义在于:允许在主键之外的属性上进行高效查询,从而支撑 Mastra 各组件所需的不同数据访问模式。所有实体共享这一张表,靠pk/sk的组合(composite)字段区分实体类型与主键,靠gsi1pk/gsi1sk、gsi2pk/gsi2sk承载二级查询。
GSI 使用明细(结合源码核实)
原始文档给出了索引与实体的对应关系,这里结合仓库中的实体定义逐一核实(均位于stores/dynamodb/src/entities/目录):
GSI1(索引名gsi1)被多个实体的常见查询模式复用:
| 实体 | 索引名 | 查询语义 | 源码中的组合键定义 |
|---|---|---|---|
threadEntity | byResource | 按resourceId查询 | gsi1pk = [entity, resourceId],gsi1sk = [createdAt],见 thread.ts |
messageEntity | byThread | 按threadId查询 | gsi1pk = [entity, threadId],gsi1sk = [createdAt],见 message.ts |
traceEntity | byName | 按name查询 | gsi1pk = [entity, name],gsi1sk = [startTime],见 trace.ts |
evalEntity | byAgent | 按agent_name查询 | gsi1pk = [entity, agent_name],gsi1sk = [created_at],见 eval.ts |
GSI2(索引名gsi2)用于:
| 实体 | 索引名 | 查询语义 | 源码中的组合键定义 |
|---|---|---|---|
traceEntity | byScope | 按scope查询 | gsi2pk = [entity, scope],gsi2sk = [startTime],见 trace.ts |
workflowSnapshotEntity | gsi2 | 按run_id查询 | gsi2pk = [entity, run_id],gsi2sk = [workflow_name],见 workflow-snapshot.ts |
从源码结构看,所有实体的pk组合都包含entity字段前缀(例如 thread 的主键为pk = [entity, id],即pk值形如thread#<id>),ElectroDB 借此在同一张表内实现实体隔离;而gsi1pk/gsi2pk同样以[entity, ...]组合,保证各实体共用 GSI 时互不冲突。仓库中共注册了 8 个实体:thread、message、eval、trace、workflow_snapshot、resource、score、background_task(见 entities/index.ts),它们全部落在这一张表里。
CloudFormation 模板
下面是文档给出的完整 CloudFormation 模板,反映了上述 GSI 使用方式,可直接用于 IaC 流水线:
Resources: MastraSingleTable: Type: AWS::DynamoDB::Table Properties: TableName: mastra-single-table BillingMode: PAY_PER_REQUEST 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 KeySchema: - AttributeName: pk KeyType: HASH - AttributeName: sk KeyType: RANGE GlobalSecondaryIndexes: - IndexName: gsi1 KeySchema: - AttributeName: gsi1pk KeyType: HASH - AttributeName: gsi1sk KeyType: RANGE Projection: ProjectionType: ALL # Suitable for varied query needs of GSI1 - IndexName: gsi2 KeySchema: - AttributeName: gsi2pk KeyType: HASH - AttributeName: gsi2sk KeyType: RANGE Projection: ProjectionType: ALL PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true SSESpecification: SSEEnabled: true要点:AttributeDefinitions必须完整声明主键与两个 GSI 的全部 6 个键属性,否则模板部署会失败;两个 GSI 均使用ProjectionType: ALL,因为各实体在 GSI1 上的查询字段各不相同(createdAt、startTime、created_at),投影必须覆盖全部属性。
AWS CDK 示例
同样的结构用 AWS CDK 表达如下(完整继承自文档):
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; export class MastraDynamoDbStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Consider parameterizing the table name for different environments const tableName = 'mastra-single-table'; // Create the single table const table = new dynamodb.Table(this, 'MastraSingleTable', { tableName: tableName, partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING }, sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING }, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, pointInTimeRecovery: true, encryption: dynamodb.TableEncryption.AWS_MANAGED, }); // Add GSI1 table.addGlobalSecondaryIndex({ indexName: 'gsi1', partitionKey: { name: 'gsi1pk', type: dynamodb.AttributeType.STRING }, sortKey: { name: 'gsi1sk', type: dynamodb.AttributeType.STRING }, // projectionType defaults to ALL in CDK, which is suitable for flexible querying but has cost implications. }); // Add GSI2 (Used by Trace and WorkflowSnapshot) table.addGlobalSecondaryIndex({ indexName: 'gsi2', partitionKey: { name: 'gsi2pk', type: dynamodb.AttributeType.STRING }, sortKey: { name: 'gsi2sk', type: dynamodb.AttributeType.STRING }, // projectionType defaults to ALL in CDK }); } }注意索引名必须严格为gsi1、gsi2——源码中 ElectroDB 实体声明的index: 'gsi1'/index: 'gsi2'直接引用这两个物理索引名,改名会导致查询失败。
建表之外:init() 的表校验机制与配置约束
原始文档强调「表必须已通过 CDK/CloudFormation 创建」,这一点在源码中可以得到印证。DynamoDBStore的init()不会自动建表,而是通过DescribeTableCommand校验表存在且可访问(见 storage/index.ts 中的validateTableExists):
- 表不存在时抛出
Table <name> does not exist or is not accessible. Ensure it's created via CDK/CloudFormation before using this store.; - 权限等其他错误会被包装为
MastraError(错误域STORAGE)向上抛出; - 初始化结果以 Promise 缓存(
hasInitialized),失败时重置以便重试。
此外,构造函数对config.tableName有硬性校验(见 storage/index.ts):
- 必须提供且非空字符串;
- 必须匹配
/^[a-zA-Z0-9_.-]{3,255}$/(3–255 个字母、数字、下划线、点或连字符),不符合会直接抛出MastraError。
从源码结构看,DynamoDBStoreConfig(storage/index.ts)还支持以下字段,官方文档示例未全部展开:
region:AWS 区域,未指定时客户端默认us-east-1;endpoint:自定义端点,本地开发指向 DynamoDB Local 时使用;credentials:显式传入accessKeyId/secretAccessKey;client:直接传入预先配置好的DynamoDBDocumentClient(例如自定义中间件、重试策略);disableInit:设为true时禁用自动初始化,适合 CI/CD 中显式执行迁移、分离部署期与运行期凭据的场景;ttl:按实体配置 TTL,详见下文。
这些字段决定了同一份表结构可以在不同环境(生产 AWS、CI、本地容器)中复用,无需改动表本身。
启用 TTL(Time To Live)
@mastra/dynamodb支持按实体类型配置 TTL,实现数据的自动过期删除。前提是先在表层面启用 TTL。
表级开启
CloudFormation:在表定义中添加:
Resources: MastraSingleTable: Type: AWS::DynamoDB::Table Properties: # ... other properties ... TimeToLiveSpecification: AttributeName: ttl # Must match config.ttl.[entity].attributeName (default: 'ttl') Enabled: trueAWS CDK:建表时开启:
const table = new dynamodb.Table(this, 'MastraSingleTable', { // ... other properties ... timeToLiveAttribute: 'ttl', // Must match config.ttl.[entity].attributeName (default: 'ttl') });AWS CLI:对已有表开启:
aws dynamodb update-time-to-live \ --table-name mastra-single-table \ --time-to-live-specification "Enabled=true, AttributeName=ttl"三处AttributeName/timeToLiveAttribute必须与代码中config.ttl.[entity].attributeName一致,默认值为ttl。
代码中配置 TTL
表级开启后,在DynamoDBStore配置中按实体类型声明 TTL(完整继承自文档示例):
const storage = new DynamoDBStore({ name: 'dynamodb', config: { tableName: 'mastra-single-table', region: 'us-east-1', ttl: { message: { enabled: true, defaultTtlSeconds: 30 * 24 * 60 * 60, // 30 days }, trace: { enabled: true, defaultTtlSeconds: 7 * 24 * 60 * 60, // 7 days }, }, }, });每个实体条目支持三个字段(见 storage/index.ts 的DynamoDBEntityTtlConfig类型定义):
enabled: boolean:该实体是否启用 TTL;attributeName?: string:TTL 属性名,默认'ttl',必须与表级配置的属性名一致;defaultTtlSeconds?: number:自条目创建/更新起的过期时长(秒),例如30 * 24 * 60 * 60表示 30 天。
从源码看,可配置 TTL 的实体类型为thread、message、trace、eval、workflow_snapshot、resource、score七种(DynamoDBTtlEntityName,见 storage/index.ts)。
TTL 的实现细节
TTL 属性写入逻辑集中在 storage/ttl.ts,可以对照源码理解其行为:
calculateTtl()计算过期时间戳:Math.floor(Date.now() / 1000) + ttlSeconds,即「当前时间的 epoch 秒 + 过期时长」——DynamoDB 的 TTL 值是 Unix 时间戳(秒),不是毫秒;- 若某实体未配置
enabled: true、或defaultTtlSeconds未提供/非正数,则不写入 TTL 属性,该条目永不过期; getTtlProps()返回形如{ [attributeName]: ttlValue }的对象,由各实体写入记录时展开(spread)进去;- 支持在调用侧传入
customTtlSeconds覆盖默认时长(customTtlSeconds ?? entityConfig.defaultTtlSeconds)。
注意(与文档一致):DynamoDB TTL 是在条目过期后48 小时内由后台进程删除;在真正被删除前,过期条目仍然可以被查询到。
使用这张表:接入 DynamoDBStore
表创建完成后,将其接入 Mastra 应用(完整继承自文档示例):
import { Memory } from '@mastra/memory'; import { DynamoDBStore } from '@mastra/dynamodb'; import { PineconeVector } from '@mastra/pinecone'; const storage = new DynamoDBStore({ name: 'dynamodb', config: { region: 'us-east-1', tableName: 'mastra-single-table', // use the name you chose when creating the table }, }); const vector = new PineconeVector({ id: 'dynamodb-pinecone', apiKey: process.env.PINECONE_API_KEY, }); const memory = new Memory({ storage, vector, options: { lastMessages: 10, semanticRecall: true, }, });从源码看,DynamoDBStore继承自MastraCompositeStore,内部一次性组装了四个领域存储:workflows、memory、scores、backgroundTasks(见 storage/index.ts)。因此这一张表同时承担会话/消息持久化(memory)、工作流快照持久化(workflowSnapshotEntity走 GSI2 的run_id查询)、评估数据与后台任务存储,无需为每个组件单独建表。所有领域共享同一个由getElectroDbService(client, tableName)创建的 ElectroDBService实例(entities/index.ts)。
本地开发:DynamoDB Local
文档建议本地开发直接使用 AWS 官方的 DynamoDB Local Docker 镜像:
docker run -p 8000:8000 amazon/dynamodb-local然后把DynamoDBStore的endpoint指向本地实例:
const storage = new DynamoDBStore({ name: 'dynamodb', config: { region: 'us-east-1', tableName: 'mastra-single-table', endpoint: 'http://localhost:8000', // Local DynamoDB endpoint }, });region在本地模式下只是占位参数(本地端点不校验区域)。仓库内还附带了 stores/dynamodb/docker-compose.yml,可以直接用它拉起本地 DynamoDB 环境,效果与上述docker run命令等价。注意本地库与线上一样需要先建好pk/sk/gsi1/gsi2结构的表,init()的DescribeTableCommand校验才会通过。
小结:建表核对清单
对照本文内容与源码,落地这张表时可按以下清单核对:
- 表包含
pk(HASH)+sk(RANGE)主键,以及gsi1(gsi1pk/gsi1sk)和gsi2(gsi2pk/gsi2sk)两个 GSI,且 GSI 索引名必须为gsi1、gsi2; AttributeDefinitions完整声明 6 个键属性;config.tableName与建表名一致,且满足 3–255 字符、仅含字母/数字/_/./-的校验规则;- 若启用 TTL:表级 TTL 属性名与
config.ttl.[entity].attributeName一致(默认ttl),并对message、trace等需要过期的实体配置enabled与defaultTtlSeconds; - 本地调试时使用
endpoint: 'http://localhost:8000'指向 DynamoDB Local,或参考仓库附带的 docker-compose.yml。
以上结构即 TABLE_SETUP.md 所定义的全部要求,与stores/dynamodb/src/entities/下各实体的索引声明一一对应,可直接用于生产环境的 IaC 部署与本地开发。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考