graphql-engine 的 NoSQL Schema Sampling RFC:基于 MongoDB 采样的自动 Schema 生成方案
2026/9/19 16:45:12 网站建设 项目流程

graphql-engine 的 NoSQL Schema Sampling RFC:基于 MongoDB 采样的自动 Schema 生成方案

【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine

导读

本文围绕 Hasura graphql-engine 仓库中的 RFC 文档 rfcs/nosql-schema-sampling/readme.md 展开,深入解析其提出的"基于 NoSQL 采样自动生成 Schema"方案:如何利用mongosh + Variety + Node.js对 MongoDB 集合中的文档进行采样分析,自动推导出字段形状与类型分布,并生成可回写数据库的 JSON Schema 验证规则,从而为 Hasura 的 GraphQL API 提供结构化的 schema 起点。读完本文,你将掌握该 PoC 的完整运行方式、环境变量与采样定制方法,并理解从"文档采样"到"验证 schema 导出"的底层实现链路。

背景与问题:NoSQL 的"无模式"与 GraphQL 的"要结构"

Hasura 的数据库支持矩阵同时涵盖 SQL 与 NoSQL 两大类数据源。相比关系型数据库的强约束,NoSQL 数据库(如 MongoDB)天然缺少预定义 schema——这带来了灵活性,却也带来了一系列工程挑战,RFC 将其归纳为五点:

  • 固有的非结构化本质:MongoDB 等 NoSQL 数据库没有预定义 schema,数据管理、定义与演进都变得复杂;
  • GraphQL 需要结构作为护栏:GraphQL 围绕 NoSQL 数据源引入的是"无固定观点的护栏"(un-opinionated guardrails),只有在获得类型安全与结构之后,才能提升执行性能、提供可预测的 API 并改善编码体验;
  • 上手易用性差:缺少预定义 schema 使得新用户无法像接入 SQL 数据库那样"即插即用"式地把数据源接入 GraphQL,导致 onboarding 流程复杂化;
  • 校验 schema 使用率有限:项目现有的一个解决方案是使用 MongoDB 自带的 validation schema,但该方案并未被用户广泛采用;
  • 功能对齐诉求:让用户能够即时 introspection 并 track MongoDB 中的 Collections 与 Documents,可以显著加速 Hasura 的 onboarding,并让 NoSQL 数据库与 Hasura 支持的其他基于 schema 的数据库达到功能对齐(feature parity)

提案方案:用"采样"推导 schema,作为 GraphQL schema 的起点

针对上述问题,RFC 提出的核心方案是构建一个基于 NoSQL 采样技术的自动 schema 生成工具。其核心思路分三步:

  1. 采样(Sampling):对集合(collection)的全部文档或子集进行采样;
  2. 分析(Analysis):运行分析,掌握文档集合(universe of documents)中出现的字段形状(shapes)与类型分布(types)
  3. 生成(Generation):基于分析结果生成一个 schema,作为 onboarding 起点,用于支撑 Hasura 之上的 GraphQL schema。

生成的 schema 具备可定制性,终端用户可以根据需要调整。RFC 中给出的概念验证(PoC)针对 MongoDB 实现,并明确指出同一套方法可以推广到其他 NoSQL 数据库;后续也可以利用 Hasura 的 logical models 直接生成 Hasura 对 schema 的表示(该点在 RFC 中以脚注形式标注为后续工作)。

开放问题:方案落地前仍需回答的设计决策

RFC 坦诚列出了数个尚未定论的开放问题,这些决策直接决定了工具的形态:

  • 选择器(selectors):需要哪些选择器来挑选合适的文档?例如在 MongoDB 中,可以选择基于查询条件(query)、最大深度(max depth)、集合百分比(percentage of the collection)或最大记录数(max number of records)来选择文档;
  • 冲突类型的取舍:当同一字段出现多种类型时,工具应选择哪种类型?例如某字段大部分是int、少量是string,应取哪种?是否需要做成可配置项?
  • 可选嵌套对象的阈值:编写 schema 时,是否应提供类似"字段必须在 x% 的文档中出现过"('must have been found in x % of documents')的设置,以过滤仅出现在极少数记录中的可选内嵌对象?
  • 工具归属:该能力应实现进核心工具,还是保留灵活性作为外部工具集的一部分?
  • 可复现性:如何以对其他 NoSQL 数据库厂商可复现的方式实现?

这些开放问题在后文 PoC 的analyze.sh与 Variety 的配置参数中其实已经出现了初步的答案雏形(例如 query/limit/maxDepth),读者可以在实践中对照思考。

概念验证(PoC)总体架构:三件套流水线

RFC 提供了一个可实际运行的 MongoDB schema sampler PoC,存放在 rfcs/nosql-schema-sampling 目录下。它由三部分技术组合而成:

组件职责
mongoshMongoDB 官方 Shell,用于连接数据库、枚举集合、执行采样查询并驱动 Variety 脚本
VarietyMongoDB schema 分析器(仓库内为 schema_sampler/variety.js,版本 1.5.1),用于对集合文档做 key/type 统计
Node.js运行 validation_exporter.js,将 Variety 的分析结果转换为 MongoDB validation schema

整个流水线由 schema_sampler/analyze.sh 编排:枚举集合 → 逐个集合跑 Variety 采样分析 → 导出 validation schema JSON → 可选地将 schema 写回 MongoDB。生成的 validation schema 随后可以被 Hasura 用作 MongoDB 数据源之上的 GraphQL schema 生成基础。

快速开始:docker compose 一键运行

PoC 使用 docker-compose.yml 定义了两个服务:

  • mongodbmongo:6镜像,监听宿主机27017端口,启动时通过 sample_data/import.sh 挂载到/docker-entrypoint-initdb.d/自动导入sample_mflix示例数据库;内置 healthcheck(每 5 秒对test库执行db.runCommand("ping").ok,最长等待 10 秒、重试 5 次),保证采样器在数据库就绪后才启动;
  • mongodb_samplernode镜像,挂载./schema_sampler./schema_exports两个卷,command直接执行/schema_sampler/analyze.sh,并通过depends_on: condition: service_healthy等待 MongoDB 健康。

启动只需一条命令:

docker compose up

启动后会自动完成:加载sample_mflix示例数据库 → healthcheck 等待就绪 → sampler 运行archive.sh(应为analyze.sh,见下文源码说明)完成集合 introspection、Variety 分析、validation schema 转换并回写 MongoDB。

说明:RFC 正文提到 sampler 运行/schema_sampler/archive.sh,但仓库实际文件名为 schema_sampler/analyze.sh,且 docker-compose 中command也指向analyze.sh——可以推断正文中的archive.sh为笔误,实际入口以analyze.sh为准。

sample_mflix示例数据位于 sample_data/sample_mflix,包含movies.json(约 2.3 万行)、comments.jsontheaters.jsonusers.jsonsessions.jsonloyalty-table.csv。从movies.json的示例文档可以看到其嵌套结构非常典型——顶层字段包含plotgenres(数组)、runtimecastawards(嵌套对象)、imdb(嵌套对象,含rating/votes/id)、tomatoes(多层嵌套)等,正是展示"嵌套字段采样 + 类型冲突处理"的绝佳素材。

定制采样范围:环境变量一览

mongodb_sampler容器暴露了 5 个环境变量用于控制采样行为(见 docker-compose.yml):

环境变量作用取值示例
MONGO_DATABASEMongoDB 连接字符串(指向目标数据库)mongodb://root:password@mongodb:27017/sample_mflix
MONGO_USERNAMEMongoDB 用户名root
MONGO_PASSWORDMongoDB 密码password
MONGO_SELECT_COLLECTIONS指定要分析采样的集合,逗号分隔''(空,表示全部集合)、movies,comments
MONGO_UPDATE_COLLECTIONS是否将生成的 validation schema 自动写回集合true/false(或留空视为 false)

在 analyze.sh 中可以看到这些变量的实际用法:当MONGO_SELECT_COLLECTIONS为空时,脚本通过mongosh ... --eval "db.getCollectionNames()"动态枚举全部集合,并用tr -d '[\[\]"\ \n]'清理输出后按逗号拆分;否则直接使用环境变量中给定的集合列表。

MONGO_UPDATE_COLLECTIONS=true时,脚本会把导出的 validation schema 通过collMod命令回写为集合的 validator,并设置validationAction: 'warn'——即仅对不符合 schema 的写入发出警告而不拒绝,属于温和的渐进式约束。

深度定制采样方式:改造 analyze.sh 中的 mongosh 查询

如果你需要改变"采样哪些文档",RFC 指出可以编辑 schema_sampler/analyze.sh 文件。第 29 行是数据采样的核心命令:

mongosh ${MONGO_DATABASE} --quiet --eval "var collection = '${collection//\'/}', outputFormat='json'" --username ${MONGO_USERNAME} --password ${MONGO_PASSWORD} --authenticationDatabase=admin /schema_sampler/variety.js > "/schema_exports/analysis/${collection//\'/}.json"

这条命令通过--eval向 Variety 注入collectionoutputFormat='json'两个全局变量,将 variety.js 作为 mongosh 脚本执行,并把 JSON 形式的分析结果写入/schema_exports/analysis/<collection>.json。定制采样方式的方法是修改--eval中的参数,RFC 给出的两个例子:

  • 添加find():例如var query = { version: 2 },只对使用某个 schema 版本的记录采样;
  • 添加limit():例如只返回前 5000 条记录。

这两个参数对应 Variety 内置的配置项。在 variety.js 的readConfig中可以看到 Variety 支持的完整配置清单,除collectionquerylimit外还包括:

  • maxDepth(默认 99):嵌套对象的递归分析深度上限;
  • sort(默认{_id: -1}):采样前的排序方式,影响limit截取的样本;
  • outputFormat(默认ascii,PoC 中设为json):结果输出格式;
  • persistResults/resultsDatabase/resultsCollection:是否将结果持久化到 MongoDB 及目标库表;
  • arrayEscape(默认XX):数组元素在 key 中的转义标记(如genres.XX0XX);
  • excludeSubkeys:需要排除分析的子 key 列表;
  • lastValue:是否记录每个 key 的最后观测值。

这些参数为"按查询过滤、按数量截断、按深度限制"等采样策略提供了底层支撑,也正是 RFC 开放问题中"选择器"的一种具体化实现。

原理纵深:从 Variety 分析结果到 $jsonSchema

Variety 的分析过程

Variety(schema_sampler/variety.js)的核心逻辑分四步:

  1. 序列化(serializeDoc):将每个文档递归展平为parentKey.key形式的一维 key 映射,数组元素以arrayEscape + 索引 + arrayEscape(默认XX0XX)的形式命名,如cast.XX0XX,同时受maxDepth限制递归深度;
  2. 类型判定(varietyTypeOf):对每个值判定类型,输出包括StringNumberNumberLongBooleanDateObjectIdBinData-<subtype>ArrayObjectnull等;
  3. 合并统计(mergeDocument):跨文档累积每个 key 出现的类型计数(types)与出现总次数(totalOccurrences);
  4. 结果转换(convertResults):生成形如{ _id: { key }, value: { types }, totalOccurrences, percentContaining }的条目数组,其中percentContaining表示该字段在多少百分比的文档中出现——这正是 RFC 开放问题中"字段出现频率阈值"的原始数据来源。

validation_exporter.js 的转换逻辑

validation_exporter.js 读取/schema_exports/analysis/<collection>.json,将其转换为 MongoDB$jsonSchema格式的 validation schema,转换规则值得逐条解读:

  • .拆分 Variety 的扁平 key,还原嵌套结构,逐层构建properties树;
  • 类型冲突处理:若某字段检测到多种类型(typeKeys.length > 1),一律保守地降级为string(代码第 19-23 行);
  • 特殊类型映射:objectid转为objectId(BSON 类型),object类型会继续作为嵌套层展开其子属性;
  • 结果写入/schema_exports/validation_schema/<collection>.json,顶层包裹为{ $jsonSchema: { bsonType: "object", required: [""], properties: {...} } }

一个值得注意的细节:转换器默认将"出现多类型"的字段归为string,这是一种保守的"最小公分母"策略——宁可放宽类型也不让 schema 过早拒绝数据。这恰好对应 RFC 开放问题 2 中"冲突类型如何取舍"的讨论,PoC 选择了最简单直接的默认策略,而"是否可配置"则留待后续迭代。

从 validation schema 到 Hasura GraphQL schema

RFC 明确指出,回写后的 MongoDB validation schema 可以成为 Hasura 生成 GraphQL schema 的依据:生成的 schema 先写回 MongoDB(collMod+ validator),再由 Hasura 基于该数据源生成 GraphQL schema;同时 RFC 标注了后续方向——利用 Hasura 的 logical models 直接生成 Hasura 表示的 schema,从而让整个"采样 → 分析 → 生成"流水线与 Hasura 的类型系统无缝衔接。

验证与调试

  • 查看容器日志:运行docker logs mongodb_sampling可以看到采样容器内的执行过程——analyze.sh中大量使用 emoji 标注步骤(如📥安装依赖、🕵️枚举集合、🔍分析、💿转换、🔄回写),方便快速定位流水线卡在哪一步;
  • 检查中间产物./schema_exports卷包含两个子目录:
    • schema_exports/analysis/:Variety 的原始分析结果(每个集合一个 JSON);
    • schema_exports/validation_schema/:转换后的$jsonSchema验证 schema(每个集合一个 JSON)。

你可以直接查看这些 JSON 文件验证"采样 → 分析 → 导出"每个环节的输出是否符合预期。

局限与未来方向

从源码结构看,该 PoC 有几个明显的边界,读者在参考时应留意:

  • 类型冲突策略硬编码:多类型字段统一降级为string的逻辑写死在 validation_exporter.js 中,尚不支持配置化;
  • 依赖外部工具:Variety 是 MIT 许可的第三方分析器(schema_sampler/variety.js 文件头注释注明版权与许可证),mongosh 则在 analyze.sh 中通过 apt 动态安装;
  • 尚未进入 Hasura 核心:RFC 开放问题 4 中"核心工具 vs 外部工具集"的归属问题尚未定论,当前 PoC 以独立 docker-compose 形式存在,并未合入 graphql-engine 主服务。

RFC 中展望的未来方向包括:将同一采样方法复用于其他 NoSQL 数据库厂商、通过 logical models 直接生成 Hasura schema 表示,以及在类型冲突、字段频率阈值等方面引入可配置策略。如果你正在为 NoSQL 数据源设计自动 schema 推导工具,这个 PoC 的"采样 → 分析 → 生成 → 回写"流水线是一份可以直接借鉴的完整参考实现。

【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询