Mastra @mastra/dynamodb DynamoDB 单表设计方案落地:pk/sk 主键与 GSI1/GSI2 索引结构、CDK/CloudFormation 建表、TTL 配置与本地开发
2026/9/13 14:14:22 网站建设 项目流程

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)。

GSI 的意义在于:允许在主键之外的属性上进行高效查询,从而支撑 Mastra 各组件所需的不同数据访问模式。所有实体共享这一张表,靠pk/sk的组合(composite)字段区分实体类型与主键,靠gsi1pk/gsi1skgsi2pk/gsi2sk承载二级查询。

GSI 使用明细(结合源码核实)

原始文档给出了索引与实体的对应关系,这里结合仓库中的实体定义逐一核实(均位于stores/dynamodb/src/entities/目录):

GSI1(索引名gsi1被多个实体的常见查询模式复用:

实体索引名查询语义源码中的组合键定义
threadEntitybyResourceresourceId查询gsi1pk = [entity, resourceId]gsi1sk = [createdAt],见 thread.ts
messageEntitybyThreadthreadId查询gsi1pk = [entity, threadId]gsi1sk = [createdAt],见 message.ts
traceEntitybyNamename查询gsi1pk = [entity, name]gsi1sk = [startTime],见 trace.ts
evalEntitybyAgentagent_name查询gsi1pk = [entity, agent_name]gsi1sk = [created_at],见 eval.ts

GSI2(索引名gsi2用于:

实体索引名查询语义源码中的组合键定义
traceEntitybyScopescope查询gsi2pk = [entity, scope]gsi2sk = [startTime],见 trace.ts
workflowSnapshotEntitygsi2run_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 个实体:threadmessageevaltraceworkflow_snapshotresourcescorebackground_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 上的查询字段各不相同(createdAtstartTimecreated_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 }); } }

注意索引名必须严格为gsi1gsi2——源码中 ElectroDB 实体声明的index: 'gsi1'/index: 'gsi2'直接引用这两个物理索引名,改名会导致查询失败。

建表之外:init() 的表校验机制与配置约束

原始文档强调「表必须已通过 CDK/CloudFormation 创建」,这一点在源码中可以得到印证。DynamoDBStoreinit()不会自动建表,而是通过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: true

AWS 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 的实体类型为threadmessagetraceevalworkflow_snapshotresourcescore七种(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,内部一次性组装了四个领域存储:workflowsmemoryscoresbackgroundTasks(见 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

然后把DynamoDBStoreendpoint指向本地实例:

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校验才会通过。

小结:建表核对清单

对照本文内容与源码,落地这张表时可按以下清单核对:

  1. 表包含pk(HASH)+sk(RANGE)主键,以及gsi1gsi1pk/gsi1sk)和gsi2gsi2pk/gsi2sk)两个 GSI,且 GSI 索引名必须为gsi1gsi2
  2. AttributeDefinitions完整声明 6 个键属性;
  3. config.tableName与建表名一致,且满足 3–255 字符、仅含字母/数字/_/./-的校验规则;
  4. 若启用 TTL:表级 TTL 属性名与config.ttl.[entity].attributeName一致(默认ttl),并对messagetrace等需要过期的实体配置enableddefaultTtlSeconds
  5. 本地调试时使用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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询