mcpField
M
masterdata-storage-strategy

masterdata-storage-strategy

github.com/vtex数据与数据库47722 次安装2 周前更新
代码仓库

关于 masterdata-storage-strategy

Master Data storage strategy 是面向 VTEX 开发者的技能,用于设计或审查 Master Data 实体。它帮助判断 Master Data 是否适用、设计 JSON Schema,并权衡索引、缓存、安全、触发器、容量与生命周期。

主要功能

  1. 01评估 Master Data 与其他存储方案
  2. 02设计 JSON Schema 和实体边界
  3. 03选择索引字段和默认返回字段
  4. 04配置缓存、安全规则和触发器
  5. 05规划 Schema 容量与清理

适用场景

  1. 01为新数据实体选择存储方式
  2. 02根据查询模式审查索引
  3. 03审计公开字段和访问规则
  4. 04规划应用开发中的 Schema 生命周期

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vtex/skills --skill masterdata-storage-strategy

安装后在 Claude Code 中输入/masterdata-storage-strategy确认已加载。

详细介绍

主数据存储策略

适用场景

在创建任何新的主数据实体之前,或审查现有用法时,请使用此技能。它可以帮助你回答以下问题:

  • 主数据是否适合存储这些数据,还是 Catalog、OMS、VBase 或外部数据库更合适?
  • 如何设计兼顾性能和安全性的 JSON Schema?
  • 哪些字段应该建立索引(v-indexed),哪些字段不应该?
  • 应该启用还是禁用缓存(v-cache)?
  • 是否需要触发器(v-triggers),还是事件驱动的 IO 方案更合适?
  • 如何规划架构和文档的容量与生命周期?

以下场景请勿使用此技能:

  • VTEX IO 应用集成模式(MasterDataClient、masterdata builder、代码中的 CRUD)——请使用 vtex-io-masterdata
  • IO 服务的性能模式(LRU、VBase 缓存层)——请使用 vtex-io-application-performance

决策规则

何时使用主数据

在以下条件全部满足时,主数据很适用:

  1. 面向文档的访问——你的数据天然是键值型或文档型(具有可变架构的 JSON 文档)。你按索引字段查询,并获取完整或部分文档。
  2. 与平台集成——你能受益于 VTEX 原生功能:用于自动化工作流的 v-triggers、用于逐字段公开访问控制的 v-security、用于搜索和筛选的 v-indexed,以及用于架构即代码的 masterdata builder。
  3. 中等数据量——实体将包含数千到数百万级别的文档。正确建立索引后,MD 可以很好地处理此类数据量。
  4. 不在购买关键路径上——MD 并未针对低于 10 毫秒的延迟进行优化。如果 MD 响应较慢,在结账、购物车或支付流程中同步读取 MD 可能会影响转化率。
  5. 没有更合适的原生存储——数据不属于 Catalog(商品/SKU 属性)、OMS(订单数据)、CL/AD(客户资料/地址)或 VBase(应用专用缓存/状态)。
何时不应使用主数据
数据类型更合适的存储原因
商品属性、规格Catalog(规格、非结构化规格)原生索引、搜索集成和目录 API
订单数据、订单历史OMS(通过 OMS API + BFF 缓存)单一事实来源;复制到 MD 会导致数据偏差
客户资料、地址CL/AD 原生实体由平台管理,并且已建立索引和缓存
应用专用缓存或临时状态VBase专为每个应用的临时存储而设计,无需承担架构开销
应用日志、调试追踪ctx.vtex.logger结构化日志基础设施,并非数据库
高吞吐量时序数据外部数据库(SQL、NoSQL、时序数据库)MD 并非为每天数百万次写入而设计
需要联接的关系型数据外部 SQL 数据库MD 不支持联接;应对数据进行反规范化,或使用关系型数据库
要求强一致性的数据外部数据库对于已建立索引的字段,MD 采用最终一致性
Schema 设计原则
  • 每个概念对应一个实体 — 不要在同一个实体中混合无关数据。每个实体都应表示一个明确的业务概念(例如 reviews、wishlists、legacyOrders)。
  • 为查询字段建立索引 — 只有 v-indexed 中的字段才能用于 where 子句。但不要过度建立索引:每增加一个索引字段,写入延迟和存储开销都会增加,因为每次文档变更都会更新索引。
  • 尽量精简 v-default-fields — 默认只返回大多数使用者需要的字段。过大的默认响应会浪费带宽。
  • 让 v-cache 与工作负载匹配 — 对读操作密集型实体保留 true(默认值)。对于写入频率较高、且使用者需要在写入后立即读取最新数据的实体,设为 false。
  • 明确设置 v-security — 除非有意允许未经身份验证的列表访问,否则将 allowGetAll 设为 false。仅对必须允许未经身份验证访问的字段使用 publicRead、publicWrite 和 publicFilter。
VTEX Schema 扩展(v-* 字段)— 参考

Master Data v2 使用 v-* 属性扩展标准 JSON Schema,用于控制索引、缓存、安全、默认值、触发器和 Schema 继承。这些属性是 VTEX 专用的;标准 JSON Schema 验证器会忽略它们。

v-indexed

Master Data 将为其创建二级索引的字段名称数组。

  • searchDocuments 和 scrollDocuments 的 where 子句中只能包含已建立索引的字段。查询未建立索引的字段会触发全量文档扫描,在大型数据集上会超时。
  • 每次写入文档都会更新每个索引。索引过多会按比例增加写入延迟和存储成本。
  • 适合建立索引的字段:用于 where 筛选、排序表达式或 publicFilter 的字段。不适合建立索引的字段:大型文本字段(description、notes)、从不用于查询的字段,或只按文档 ID 读取的字段(建立索引不会为 getDocument 带来任何好处)。
{ "v-indexed": ["email", "status", "createdAt"] }
v-cache

布尔值(默认值为 true)。控制 Master Data 是否缓存单个文档的 GET 响应。

  • true(默认值) — 读操作密集型实体可受益于缓存。大多数实体应保留默认值。
  • false — 适用于写入频率较高、且使用者需要在写入后立即读取最新数据的实体(例如实时计数器、配置标志、类似会话状态的数据)。
{ "v-cache": false }
v-default-fields

当调用方未在 API 请求中指定 fields 参数时返回的字段名称数组。

  • 保持精简 — 默认只包含大多数使用者需要的字段。
  • 减少常见查询的响应大小。
{ "v-default-fields": ["email", "status", "score", "createdAt"] }
v-security

用于控制字段是否允许未经身份验证(公开)访问的对象。默认情况下,所有字段都需要身份验证。

属性类型说明
allowGetAllboolean若为 true,未经身份验证的用户可以列出所有文档。默认值为 false;除非有意开启,否则应保持关闭。
publicReadstring[]无需身份验证即可读取的字段
publicWritestring[]无需身份验证即可写入的字段
publicFilterstring[]无需身份验证即可用于 where 子句的字段(也必须包含在 v-indexed 中)
{
  "v-security": {
    "allowGetAll": false,
    "publicRead": ["status", "displayName", "rating"],
    "publicWrite": [],
    "publicFilter": ["status"]
  }
}

绝不要在 publicRead 或 publicFilter 中包含个人身份信息(PII)(电子邮件、电话、地址)、内部 ID 或业务敏感数据。

v-triggers

触发器对象数组,用于定义在创建或更新文档且满足指定条件时执行的自动化操作。

属性类型描述
namestring唯一的触发器名称
activeboolean启用/禁用触发器
conditionstring类似 where 的筛选条件(例如,"approved=false"、"status=pending AND priority>3")
action.typestring"email"、"http"(webhook)或 "action"
action.providerstring电子邮件提供商名称(用于 email 类型)
action.uristringWebhook URL(用于 http 类型)
action.methodstringWebhook 的 HTTP 方法(用于 http 类型)
retry.timesnumber失败时的重试次数
retry.delayobject重试间隔(例如,{ "addMinutes": 5 })
{
  "v-triggers": [
    {
      "name": "notify-on-creation",
      "active": true,
      "condition": "status=new",
      "action": {
        "type": "email",
        "provider": "default",
        "subject": "New record: {{title}}",
        "to": ["[email protected]"],
        "body": "Record {{id}} created by {{author}}"
      },
      "retry": { "times": 3, "delay": { "addMinutes": 5 } }
    },
    {
      "name": "webhook-on-approval",
      "active": true,
      "condition": "approved=true",
      "action": {
        "type": "http",
        "uri": "https://my-integration.example.com/webhook",
        "method": "POST",
        "headers": { "X-Custom-Header": "value" }
      },
      "retry": { "times": 2, "delay": { "addMinutes": 10 } }
    }
  ]
}
v-canonicalto

指向同一实体中另一个架构的 URL,用于架构继承。当前架构会继承目标架构的属性和约束。

{
  "v-canonicalto": "https://{host}/api/dataentities/{entity}/schemas/{base-schema}"
}
additionalProperties

标准 JSON Schema 属性,但值得注意的是:将其设为 false 可拒绝未在 properties 中声明的字段。默认情况下,Master Data 会保留额外字段而不进行验证。

硬性约束

约束:仅为 where 子句或排序表达式中使用的字段创建索引

v-indexed 中的每个字段都会创建一个二级索引,并在每次写入文档时更新。为从未查询过的字段创建索引会浪费写入吞吐量和存储空间。

为何重要 — 对写入频繁的实体过度创建索引(例如,索引 15 个字段,但只查询 3 个),可能会使写入延迟增加一倍或三倍。对于拥有数百万份文档的实体,不必要的索引也会增加存储成本。

检测方式 — 将 v-indexed 字段与代码库中实际使用的 where 子句进行比较。未在 where 或排序中引用的已索引字段很可能是不必要的。

正确做法 — 仅为用于筛选或排序的字段创建索引。

{
  "properties": {
    "email": { "type": "string" },
    "status": { "type": "string" },
    "score": { "type": "integer" },
    "notes": { "type": "string" },
    "createdAt": { "type": "string", "format": "date-time" }
  },
  "v-indexed": ["email", "status", "createdAt"]
}

错误做法 — “以防万一”而为每个字段都创建索引。

{
  "properties": {
    "email": { "type": "string" },
    "status": { "type": "string" },
    "score": { "type": "integer" },
    "notes": { "type": "string" },
    "createdAt": { "type": "string", "format": "date-time" }
  },
  "v-indexed": ["email", "status", "score", "notes", "createdAt"]
}
约束:不要通过 v-security publicRead 暴露敏感字段

v-security.publicRead 数组中的字段无需任何身份验证即可访问。切勿在此列表中包含 PII(电子邮件、电话号码、地址)、内部 ID 或业务敏感数据。

为何重要 — 任何知道实体名称以及文档 ID 或搜索查询的人都可以访问公开字段。暴露 PII 会违反数据保护法规并造成安全漏洞。

检测方式 — 检查 v-security.publicRead 和 publicFilter,确认其中没有包含用户数据、内部引用或任何需要身份验证才能访问的字段。

正确做法 — 仅公开非敏感且面向展示的字段。

{
  "v-security": {
    "allowGetAll": false,
    "publicRead": ["status", "displayName", "rating"],
    "publicWrite": [],
    "publicFilter": ["status"]
  }
}

错误做法 — 公开暴露 PII 和内部字段。

{
  "v-security": {
    "allowGetAll": true,
    "publicRead": ["email", "phone", "cpf", "internalScore", "organizationId"],
    "publicWrite": ["email"],
    "publicFilter": ["email", "phone"]
  }
}
约束:遵守每个实体 60 个 schema 的限制

Master Data v2 实体的 schema 数量硬性上限为 60 个。masterdata 构建器会为每个已关联或安装的应用版本创建一个新 schema。达到上限后,新版本将无法部署。

为何重要 — 在活跃开发期间,频繁执行 vtex link 会迅速累积 schema。达到上限后,部署会被阻止,直到手动删除旧 schema。

检测 — 经历过多次 link/publish 周期的应用。通过 GET /api/dataentities/{entity}/schemas 检查 schema 数量。

正确做法 — 定期清理未使用的 schema。在 CI/CD 中自动执行清理。

# List schemas to identify stale ones
curl "https://{account}.vtexcommercestable.com.br/api/dataentities/{entity}/schemas" \
  -H "X-VTEX-API-AppKey: {key}" -H "X-VTEX-API-AppToken: {token}"

# Delete unused schemas
curl -X DELETE "https://{account}.vtexcommercestable.com.br/api/dataentities/{entity}/schemas/{old-schema}" \
  -H "X-VTEX-API-AppKey: {key}" -H "X-VTEX-API-AppToken: {token}"

错误做法 — 开发期间从不清理 schema,直到达到上限、部署被阻止。

推荐模式

包含所有 VTEX 扩展的完整 schema 示例
{
  "$schema": "http://json-schema.org/schema#",
  "title": "product-review-v1",
  "type": "object",
  "properties": {
    "productId": { "type": "string" },
    "author": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "rating": { "type": "integer", "minimum": 1, "maximum": 5 },
    "title": { "type": "string", "maxLength": 200 },
    "text": { "type": "string", "maxLength": 5000 },
    "approved": { "type": "boolean" },
    "createdAt": { "type": "string", "format": "date-time" }
  },
  "required": ["productId", "rating", "title", "text"],
  "v-indexed": ["productId", "approved", "rating", "createdAt"],
  "v-default-fields": [
    "productId",
    "author",
    "rating",
    "title",
    "approved",
    "createdAt"
  ],
  "v-cache": true,
  "v-security": {
    "allowGetAll": false,
    "publicRead": [
      "productId",
      "author",
      "rating",
      "title",
      "text",
      "approved"
    ],
    "publicWrite": [],
    "publicFilter": ["productId", "approved", "rating"]
  },
  "v-triggers": [
    {
      "name": "notify-moderator",
      "active": true,
      "condition": "approved=false",
      "action": {
        "type": "email",
        "provider": "default",
        "subject": "New review pending moderation",
        "to": ["[email protected]"],
        "body": "Review for product {{productId}} by {{author}}: {{title}}"
      },
      "retry": {
        "times": 3,
        "delay": { "addMinutes": 5 }
      }
    }
  ]
}
触发器:何时使用,何时不使用

在以下情况下使用触发器:

  • 需要在文档变更时发送电子邮件通知(例如审核提醒)
  • 需要在文档满足条件时调用外部 webhook
  • 操作简单,触发后无需等待结果,也不需要复杂的错误处理

在以下情况下不要使用触发器:

  • 需要复杂的编排、带退避的重试或错误恢复——改用 IO 事件
  • 需要在变更发生后亚秒级响应——触发器有内置延迟
  • 操作会链式修改其他 MD 实体——存在触发器级联循环的风险
  • 需要比 where 风格筛选器更复杂的条件逻辑
无需完整获取即可统计文档数量

使用 REST-Content-Range 标头,高效获取文档数量:

# Count documents without fetching them
curl "https://{account}.vtexcommercestable.com.br/api/dataentities/{entity}/search?_fields=id" \
  -H "REST-Range: resources=0-0" \
  -H "X-VTEX-API-AppKey: {key}" -H "X-VTEX-API-AppToken: {token}"
# Response header: REST-Content-Range: resources 0-0/12345
# The number after "/" is the total document count
Search 与 Scroll
用途使用场景最大页面大小
searchDocuments有界结果集、UI 分页、已知的小规模数据集每页 100 个
scrollDocuments大规模导出、批量操作、无界迭代可配置批次大小

常见故障模式

  • 索引过多 — 在高写入量实体上为 10 个以上字段建立索引。每次写入都会更新所有索引,增加延迟和存储开销。
  • 缺少索引 — 查询未建立索引的字段会触发全表扫描。在有 100 个文档的开发环境中可正常运行,但在生产环境中面对 100k 个文档时会超时。
  • 默认 v-cache: false — 在读操作较多的实体上禁用缓存,会导致每次 GET 都访问数据库。仅对高写入量实体禁用缓存。
  • 包含 PII 的 allowGetAll: true — 未经身份验证的用户可以列出所有文档,包括敏感数据。
  • Schema 累积 — 开发周期产生的 60 个 schema 会阻止生产部署。
  • 触发器链 — 触发器 A 修改实体 B,而实体 B 的触发器又修改实体 A——形成无限循环。
  • 将 MD 用作日志存储 — 实体随流量增长而无限扩张。请改用 ctx.vtex.logger。
  • MD 位于关键路径上 — 在结账流程中同步读取 MD,却没有超时或回退机制。

审查清单

  • 是否已完成存储适用性审查?(MD、Catalog、OMS、VBase 还是外部数据库)
  • v-indexed 中是否仅包含会被查询的字段?是否有不必要的索引?
  • v-cache 是否根据实体的读写比例正确设置?
  • v-security 是否将公开访问限制为仅包含非敏感字段?
  • 除非明确需要,allowGetAll 是否设为 false?
  • 触发器是否简单且不会形成链式调用?是否不存在触发器循环的风险?
  • 是否制定了符合 60 个 schema 限制的 schema 清理策略?
  • 实体是否不在购买关键路径上(结账、购物车、支付)?
  • 对于大型数据集(100k+ 个文档),是否使用 scrollDocuments 而非分页搜索?
  • v-default-fields 是否保持精简(默认不返回所有内容)?

相关技能

参考资料

评论

支持 Markdown