进阶阅读约 3 分钟

DynamoDB 中的 Type 属性

在 SQL 里,一行所在的表 就是 它的类型——documents 表里的一行就是一个文档。而 DynamoDB 单表把所有实体混在同一套 schema 下,所以一个 item 本身并不自带"这是什么?" 的答案。

Type 属性把这个答案补了回来:在每个 item 上放一个普通字符串,命名它所代表的实体。

DynamoDB 中的 Type 属性是什么?

Type 属性是你在每个 item 上打的一个普通字符串——比如 EntityType: "Document"——用来命名该 item 所代表的实体。由于单表把多种实体混在同一套 schema 下,item 本身不自带类型信息。Type 把这个答案补回来,让你的代码能够识别行、把一个 GSI 过滤到单一实体,并在未来的迁移中全身而退。

  • 每次写入都打上 Type。 在每个 item 上放一个属性——EntityType: "Document"—— 无一例外。它只花几个字节,却能在之后帮你省事。
  • 它在混合分区里识别实体。 一次 Query 会把 workspace、document 和 comment 一起 返回,Type 让你的代码不用解析 key 前缀就知道哪个是哪个。
  • 它支撑在 GSI 上做单一实体过滤。 把 Type 投影进索引,你就能把一个被重载的索引 收窄到恰好一种实体类型。
  • 它是迁移时的逃生口。 当你导出去重新建模、或把某个实体挪到它自己的表时,Type 就是你用来切分的那一列。

为什么混合表会丢掉类型

单表设计把每个实体都存进同一张表,背后是 PKSK 这样的通用 key。这正是它的全部意义所在——一次 Query 就能把父项和它的子项一起返回。 但这也意味着一个分区是异构的。

拿一个 SaaS 文档协作应用来说。一个 workspace 分区里同时装着 workspace 记录、它的 document,以及那些 document 上的 comment:

PKSKattributes
WS#acmeMETAname, plan, seats
WS#acmeDOC#a1#METAtitle, owner, wordCount
WS#acmeDOC#a1#CMT#0007author, body, createdAt
WS#acmeDOC#a1#CMT#0008author, body, createdAt

Query PK = "WS#acme" 在一次计费读取里把这四个 item 都返回给你。现在你的代码拿到了 一串原始 item,却没有可靠的办法说出哪个是 document、哪个是 comment——除非去对 SK 做字符串匹配,而一旦你的 key 格式变了,这种做法立刻就脆了。

在每个 item 上打 Type

修法是每次写入都加一个属性,命名实体:

PKSKEntityTypetitle
WS#acmeMETAWorkspace
WS#acmeDOC#a1#METADocumentQ3 Roadmap
WS#acmeDOC#a1#CMT#0007Comment

item.EntityType === "Document" 做分支判断是一个稳定的相等检查。而 SK.startsWith("DOC#") && SK.includes("#CMT#") 这种解析只是猜测,一旦你改了 key 就会 失效。Type 把你的读取逻辑和 key 编码解耦——这才是真正的收益。

Query PK = 'WS#acme'混合分区EntityType: 'Workspace'EntityType: 'Document'EntityType: 'Comment' Type 路由

一次读取返回三种实体类型,Type 属性把每个 item 路由到正确的处理器,完全不用碰 key。

把一个 GSI 收窄到单一实体

Type 在索引上才真正发挥价值。假设你加了一个 GSI,以 GSI1PK = WS#acmeGSI1SK = updatedAt 为 key,用来列出"这个 workspace 里最近改动过 的所有东西,最新的排在前面"。一个被重载的索引会把 document comment 都扫进来——但 一个信息流 UI 可能只想要 document。

有两种收窄方式,差别就在钱上:

做法它的代价何时用
在 Type 上用 FilterExpression读取所有匹配的 item,按全部计费,读完之后再丢掉不匹配的结果里混合实体很少;想快速上线
稀疏索引(Type 放进 GSI1PK只有你想要的实体才会落进索引某一种实体占主导;你想要零浪费

FilterExpression 在 item 被读取 之后、容量被消耗 之后 才运行——AWS 明确说过 过滤并不会降低读取成本 (DynamoDB 开发者指南:FilterExpression)。 在 Type 上过滤是诚实的,但不是免费的:你为那些被你丢掉的 comment 付了钱。

要把信息流收窄到 document,query 就带上一个对 Type 属性的条件。用 DynamoDB 表达式构建器FilterExpression、 names 和 values 组装好——它会输出 #t = :doc 占位符,省得你手滑写错保留字。

KeyConditionExpression     GSI1PK = :ws
FilterExpression           #t = :doc
ExpressionAttributeNames   { "#t": "EntityType" }
ExpressionAttributeValues  { ":ws": "WS#acme", ":doc": "Document" }

想让索引 装 document、彻底跳过过滤?那就只在 document item 上写 GSI1PK——这就是 稀疏索引。没有 GSI key 的 item 永远不会复制进索引,所以读取只触及 document。Type 属性正是告诉你的写入端哪些 item 符合条件的东西。

让取值保持稳定且唯一

值只挑一次,并把它当作枚举。Document,绝不要一会儿 Doc 一会儿 document——一个 漂移的值比没有值还糟,因为你的相等检查在一种大小写下通过,却悄悄漏掉另一种。

每个 item 一个 Type。如果一个 item 感觉像两个实体,那通常是建模的坏味道——它应该是两个 item,各自在自己的集合或 sort key 区间里,而不是一行身兼 两职。

迁移的回报

要在需要它之前就打上 Type,原因就是:重新建模。推荐的重建路径是导出、转换、重新导入—— AWS 为这种离线重塑专门记录了批量导出到 S3 的做法 (导出 DynamoDB 到 S3)。

那一天到来时,Type 就是你拿来 GROUP BY 的那一列。想把 comment 抽进它们自己的表,或把 导出的数据重新规范化成按实体分的文件,喂给一个分析仓库?你就在 EntityType 上切分这份 转储。没有它,你又得在数百万行里反向工程 key。

下一步

Type 属性是廉价的保险:在一次混合读取里识别实体、过滤一个被重载的 GSI,并在重建模时干净 地切分。从第一天起就在每次写入时打上它——事后给一张在线表补上它,意味着一次完整的回填。

延伸阅读:单表设计了解它服务的这种混合分区模式, GSI vs LSI了解如何为稀疏索引选择索引形态,以及 Query vs Scan了解为什么 FilterExpression 永远帮你省不了读取 成本。

DynamoDB 表达式构建器在 Type 上构建过滤条件,并 试用 DynoTable,浏览一张真实的混合实体表,看看 Type 列在每个 item 上如何对齐。

更新于