Noms 数据库深入解析:Git 式版本化、可同步、可分叉的声明式数据库
发布时间:2026/9/28 3:24:44 锦皓数字建站

数据库版本控制后端【免费下载链接】nomsThe versioned, forkable, syncable database项目地址https://gitcode.com/gh_mirrors/no/noms点击查看免费下载Noms 是一个以 Git 版本控制思想为哲学渊源的去中心化数据库将版本化、可同步、可分叉等特性与结构化数据存储、高效索引、原子事务等传统数据库能力融为一体。本文以仓库 README.md 为核心脉络结合 技术总览、CLI 指南、NBS 存储层说明 与对应源码系统讲解 Noms 的 Merkle DAG 数据模型、类型系统、Prolly Trees 索引结构、安装运行流程与 CLI 操作帮助你快速上手并理解其底层原理。Noms 是什么Noms是一个哲学上源自 Git 版本控制系统的去中心化数据库。与 Git 一样Noms 具备两大核心特性版本化Versioned默认保留数据库的所有历史版本。你可以轻松追踪数据库如何演化到当前状态高效比较任意两个版本甚至从任意历史版本回滚或分叉。可同步Synchronizable同一个 Noms 数据库的多个实例可以长时间断连之后能够高效且正确地调和彼此的变化。与 Git 不同Noms 是一个数据库因此它还具备以下能力主要存储结构化数据而非文件和目录依托 Noms 类型系统大规模扩展性好可支撑大量数据与并发客户端支持原子事务单实例 Noms 是 CP 的生产环境通常以 S3 为后端运行此时表现为effectively CA支持高效索引依赖 Prolly Trees 概率型 B 树提供灵活的查询模型实验性的 GraphQL 桥接层 ngql。一个 Noms 数据库既可以存在于文件系统中也可以存在于云端内置的 NBSChunkStore实现提供了两个后端——文件系统后端与 S3 bucket 后端为 Noms 数据库提供持久化。最后由于 Noms 是内容寻址content-addressed的它带来了一种非常愉悦的编程模型使用 Noms 是声明式declarative的。你不需要INSERT新数据、UPDATE既有数据或DELETE旧数据只需声明数据此刻应当是什么样子。如果两次提交相同的数据内容寻址会让它被自动去重如果提交的数据几乎相同则只有不同的部分会被写入。这一特性从 value_store.go 等底层实现中可以观察所有值均以内容哈希寻址存储相同哈希意味着相同值。核心概念Merkle DAG、数据库与数据集数据是一棵 Merkle DAG与 Git、比特币、以太坊、IPFS 等系统一样Noms 将数据建模为有向无环图DAG每个节点都有一个由节点内编码的值以及传递性地由该节点可达的所有节点中的值推导出的hash。换言之一个 Noms 数据库就是一棵巨大的 Merkle DAG。两个节点拥有相同 hash 时它们代表完全相同的逻辑值各自可达的子图在拓扑上等价反过来也成立一个逻辑值有且只有一个 hashhash 不同即逻辑值不同。正是这种性质使得快速 diff、sync、merge 成为可能——比较两个值只需要比较它们的 hash。当前 Noms 使用 sha2-512 哈希的前 20 个字节 作为内容哈希。为何这样选择hash 包注释 给出了明确理由sha-1 已不再被推荐sha-3 太新、平台支持不足blake 不常用而在 sha-2 家族中 sha-512 在 64 位平台上比 sha-256 更快。截断到 20 字节则是碰撞抵抗力与树扇出fan-out之间的平衡——数据库场景下更大的 hash 意味着每个 chunk 中数据更少、树层级更深、迭代与搜索更慢而 20 字节正好对应 32 个 base32 字符StringLen 32即20 * 8 / log2(32)。哈希的文本序列化使用大端 base32、字母表为{0-9,a-v}不含特殊字符可在 GUI 中双击选中且排序后的哈希文本序一致便于人工扫描。值得注意哈希函数是序列化版本的一部分在整个数据库的生命周期内恒定不变因此客户端无需担心同一数据库内出现多种哈希函数。数据库Database与数据集Dataset数据库是 Noms 的顶层抽象承担两项职责一是为内容寻址的 chunk 数据提供存储二是跟踪零个或多个数据集dataset。Noms 数据库可以构建在任何提供键值存储、且至少具备可选乐观并发控制的底层存储之上——乐观并发只用于存储每个数据集的当前值chunk 本身是不可变的。仓库中的实现包括自有的文件后端存储 Noms Block Store (NBS)通常本地使用自有的 HTTP 协议用于连接远程数据库Amazon DynamoDB内存存储主要用于测试。一个数据集不过是 DAG 中的一个具名指针。下面的命令把数据库内名为foo的数据集复制为barnoms sync http://localhost:8000::foo http://localhost:8000::bar这个操作几乎是零 IO 的Noms 先在http://localhost:8000中解析数据集foo得到 hash再检查该 hash 是否已存在于目标数据库本例中与源数据库相同发现存在后只需新增一个指向该 chunk 的数据集即可。若目标数据库已经拥有全部或大部分所需 chunk跨数据库的同步也可以同样高效。时间与不可变性Noms 中所有数据都是不可变的一旦存储就永不改变。为了表达状态变化Noms 使用一系列Commit结构与 Git 一样提交通常有一个parent时间上的前一提交而在合并场景下可以有多个 parent。当值被存储时会被拆成一个或多个 chunk。chunk 边界通常是隐式创建的用于高效存储大型集合见下文 Prolly Trees程序员也可以使用Ref类型显式创建 chunk 边界。每个 chunk 编码单个逻辑值并在持久化层中由其所编码值的 hash 寻址。类型系统Noms 是类型化系统每个 Noms 值都属于以下类型之一BooleanNumber任意精度二进制Stringutf-8 编码Blob原始二进制数据SetTListTMapK,VUnionsT|U|V|...RefT显式的行外引用Struct用户自定义记录类型如Struct Person { name: String, age?: Number }Type存储一个 Noms 类型的值Blob、set、list、map 可以非常巨大——Noms 会把它们内部chunk成合理大小的片段以高效地存储、搜索与更新。String、number、union、struct 则不会被 chunk应仅用于大小合理的值需要强制某值放入不同 chunk 时使用Ref。类型在 Noms 中承担多重职责详见 intro.md数据自描述对任意 NomsValue调用types.TypeOf无论多大都能得到整个值及其可达值的精确描述使不同软件无需事先约定即可互操作社区内标准化用户可自定义结构体并发布使用它们的数据在相似数据的社区中形成临时标准结构性使用程序可按要求类型检查传入数据——若传入的根 chunk 匹配该类型或其超集程序即可确定所有可访问数据的形状从而支持 schema 随时间扩展未来计划为数据集增加类型约束类似于传统数据库的 schema 校验。Ref 与 Hash 的区别hash 只是一串标识更大值的字节Noms 中每个值都有 hash而Ref是类型系统的一部分是一个值——你可以把RefT提交到数据集但无法提交裸 hash。区别在于Ref除 hash 外还携带目标类型这使得包含Ref的提交可以被高效校验。Type Accretion类型增生作为不可变数据库schema 如何演化答案是类型增生向只含Number的Set插入字符串后结果值的类型是SetNumber|String。数据集层面同理提交SetNumber时产生的 commit 类型为Struct Commit { Value: SetNumber Parents: SetRefCycleCommit }随后提交SetString时该 commit 的类型会增生为同时描述当前与历史类型Struct Commit { Value: SetString Parents: SetRefCycleCommit | RefStruct Commit { Value: SetNumber Parents: CycleCommit } } }类型增生的好处包括可以在不重写任何既有数据的情况下拓宽容器类型SetStruct { name: String }增宽后既有数据全部复用可以做到其他数据库不允许的拓宽如SetNumber→SetNumber|String无论向哪个方向变宽或变窄改变数据集类型数据集都能自描述其当前与历史类型。Prolly Trees概率型 B 树为什么需要它Noms 的关键不变量是历史无关性history-independence同一个 Noms 值无论经历过怎样的逻辑变更序列最终都由同一组物理 chunk、同样的 hash 表示。这是快速 diff、sync、merge 的基础——两个值只需比较 hash 即可判定相等。但 Noms 同时也是数据库需要高效地搜索、扫描、变更大型集合。经典的 B-Tree 与 LSM Tree 无法直接使用因为它们的内部状态依赖于变更历史不具备历史无关性。为此 Noms 引入了Prolly Trees。结构与构建Prolly Tree 是一种搜索树其中每个节点存储的值的数量由存储在树中的数据概率性地决定。它和 B-Tree 相似但每个节点中的值数量是概率均值而非强制的上下界每个节点中的值集合由对值做滚动哈希rolling hash的输出决定而非通过超出上下界时的 split/join 操作。构建 Prolly Tree 使用变体的内容切片content-slicing技术类似 bup、rsync、Camlistore 的做法将大型有序序列的序列化数据按固定大小窗口逐字节滑动在每个位置计算窗口内字节的哈希当哈希中出现具有已知出现概率的模式时该位置即为boundary。窗口滑到所在项的末尾写出上一个 boundary 到当前 boundary 之间的新chunk并存入内容寻址存储。Noms 中寻找的模式是 12 个高位全为 1defaultChunkPattern 112 - 1其出现概率为 1/2^12因此Noms 的平均 chunk 大小约为 4KB。窗口大小为 67 字节注释说明2 字节在随机数据下配合 4KB 目标已足够更大窗口是为了在低熵输入上有更好的分布素数选择则为重复输入提供更好的分布任何单字节改变移动一个边界的概率约为 67/4KB ≈ 1.6%。切出第一轮 chunk 后为每个 chunk 的内容构建索引再对索引的序列化重复切片如此递归直到得到一个不再切分的节点——即树的根。变更与性质变更一棵 Prolly Tree 时概念上是从头构建一棵新树但窗口之外的子树可以原样复用。约 1.6% 的概率一次写入会移动 chunk 边界导致该层多写一个 chunk这可能在每一层发生因此变更一棵树的期望操作数约为1.016 * 树深。一棵 4 层的 Prolly Tree 可容纳约4096^4 ≈ 281TB数据对它做单次变更只需约 4 次 4KB 写入。Prolly Tree 与传统结构的对比n树叶数据总量k平均块大小w窗口宽度操作B-TreesPatricia 树† / HAMTsProlly Trees1 次随机读O(logk(n))O(logk(n))O(logk(n))1 次随机写O(logk(n))O(2·logk(n))O((1k/w)·logk(n))顺序扫描大小为 z 的单个项O(z/k)不支持O(z/k)计算大小为 d 的 diffO(n)O(d)O(d)验证、证明不支持支持支持结构化共享不支持支持支持†假设已哈希键未哈希会破坏性能。由于 Prolly Trees 是有序的Boolean、Number、String 键按自然序排序其他键类型按 hash 排序Noms 集合可以被当作高效索引使用方式与传统数据库的主索引、二级索引相同。例如构建MapNumber, SetPerson即可快速约 logk(n) 次 seek查找精确年龄的人群也能高效进行年龄区间扫描对两个集合求交如年龄与发色的交集也可高效实现。安装与运行安装与版本验证安装 Noms CLI 的方式是下载最新 release 并解压后加入$PATH。安装完成后验证$ noms version format version: 7.18 built from developer build这里的版本号来自 go/constants/version.goNomsVersion 7.18NomsGitSHA developer build由 noms_version.go 子命令输出。导入数据并浏览以纽约市公开数据为例先通过go install安装示例程序再下载 CSV 并导入go install github.com/attic-labs/noms/samples/go/csv/csv-import curl https://data.cityofnewyork.us/api/views/kku6-nxdu/rows.csv?accessTypeDOWNLOAD /tmp/data.csv csv-import /tmp/data.csv /tmp/noms::nycdemo注意第二个参数/tmp/noms::nycdemo是 Noms 的拼写spelling规范database::dataset——数据库为本地路径/tmp/noms数据集名为nycdemo。随后浏览noms show /tmp/noms::nycdemo输出会展示一个struct Commit包含meta日期、输入文件等元数据、parentsset {}首提交无父以及value236 行的struct Row列表每行含各人口统计字段。csv-import 是完整的示例工具其 importer.go 展示了丰富的参数--delimiter分隔符默认逗号、--header表头行、--lowercase列名转小写、--name行结构体名默认Row、--column-types逗号分隔的每列类型缺省全为 String、--path导入 Noms Blob 而非文件、--dest-typelist或map:pkpk 为用于唯一标识行的列头或 0 基索引列表、--skip-records、--limit-records、--commit默认 true提交到数据集 head否则只写入数据集、--append追加到数据集 head 的 list仅兼容 list 导入、--invert按列主序而非行主序导入等。CLI 实战数据集管理、日志、展示、同步与差异不带参数运行noms会列出全部子命令diff、ds、log、serve、show、sync、version等。这些子命令在 cmd/noms/noms.go 中注册包括nomsBlob、nomsCommit、nomsConfig、nomsDiff、nomsDs、nomsList、nomsLog、nomsMerge、nomsJSON、nomsMap、nomsRoot、nomsServe、nomsSet、nomsShow、nomsStats、nomsStruct、nomsSync、splore.Cmd、nomsVersion。使用noms help [command]可查看具体命令说明。noms ds列出数据库内的数据集。例如noms ds http://demo.noms.io会显示sf-film-locations/raw、sf-film-locations等。noms log查看数据集的历史。输出每个 commit 的 hash、Parent、Date 等。注意 Noms 是类型化系统这里显示的并非文本而是两个数据集间 diff 的序列化。noms show展示数据库中任意对象的完整序列化例如noms show http://demo.noms.io::#aprsmg0j2eegk8eehbgj7cd3tmmd1be8会输出struct Commit的类型定义与实际值1,241 行的Liststruct Row等。从 noms_show.go 可以看到它还支持--raw二进制格式 dump、--stats值统计信息二者互斥、--tz时间注释时区默认 local等选项内部通过config.NewResolver()解析路径再用types.WriteEncodedValue输出。noms sync在数据库之间或内部移动数据集。与 Git 不同Noms不区分 push 和 pull两个方向是同一操作 noms sync http://demo.noms.io::sf-film-locations /tmp/noms::films noms ds /tmp/noms films同步到本地后即可断连工作——例如导出 CSV、编辑、再重新导入 go install github.com/attic-labs/noms/samples/go/csv/... csv-export /tmp/noms::films /tmp/film-locations.csv # 编辑 /tmp/film-location.csv 后 csv-import --column-typesString,String,String,String,String,String,String,String,Number,String,String \ /tmp/film-locations.csv /tmp/noms::filmscsv-export 的 exporter.go 会读取数据集 head 值识别List或Map类型后写出 CSV仅支持这两类根值否则 panic 提示Expected ListKind or MapKind。noms diff展示任意两个值的差异例如修改前后的对比 noms diff http://demo.noms.io::sf-film-locations /tmp/noms::films ./.meta { - date: 2016-07-25T18:51:230000 date: 2016-07-25T22:51:140000 inputFile: /tmp/film-locations.csv - inputPath: http://demo.noms.io::sf-film-locations/raw.value ./.parents { - pckdvpvr9br1fie6c3pjudrlthe7na18 q4jcc2i7kntkjiipvjgpr5r02ldroj0g } ./.value[0] { - Locations: Epic Roasthouse (399 Embarcadero) Locations: Epic Roadhouse (399 Embarcadero) }存储后端 NBS文件系统与 AWSNBSNoms Block Store是为 Noms 优化的水平可扩展存储层详见 go/nbs/README.md可运行在两种配置本地磁盘后端或 Amazon AWS 后端后者由 NBS-on-AWS.md 说明。本地磁盘后端在 Noms 的典型工作负载下显著快于 LevelDB并支持完整的多进程并发。AWS 后端将数据主要存放在 S3外加单个 DynamoDB 条目使 Noms effectively CANoms 始终一致NomsNBS 的可用性与 DynamoDB 和 S3 相当同时获得 S3 的成本结构。NBS 的关键设计为内容寻址的 DAG 提供存储恰有一个根每个节点编码为字节序列由字节序列的 20 字节 hash 寻址没有update或delete——只有insert、update root和garbage collect插入任何新字节序列仅在更新根之后才持久化支持文件级多进程并发多个写入者使用乐观锁写入者无需担心重写重复 chunkNBS 会高效检测并丢弃绝大多数重复项。仓库给出的本地后端与 AWS 后端使用示例# 本地 NBS ./csv-import foo.csv /Users/bob/csv-store::data # AWS 后端通过 aws: scheme ./csv-import foo.csv aws:table/bucket/database::data路径拼写Spelling规范Noms 众多命令与 API 都接受数据库、数据集或值的规格参数规范见 doc/spelling.md数据库protocol[:path]。http(s)为 HTTP 远程数据库 URLmem为内存数据库path 必须为空nbs为本地 NBS 数据库path 为磁盘上的目录如nbs:/tmp/noms-data在 Go 中nbs:可省略aws为直接由 AWSDynamoDB S3支撑的远程 NBS格式为aws:dynamo-table/s3-bucket/database。数据集database::datasetdataset 匹配正则^[a-zA-Z0-9\-_/]$。例如/tmp/test-db::my-dataset、http://localhost:8000::registered-businesses。值database::rootpath。root以#开头时解释为 hash否则作为数据集名。path相对 root用.field选择 struct 字段如.value取 Commit 的 value 字段、.meta取 meta 字段用[...]索引 list/map/set如.value[42]、.value[0]、list 支持负索引.value[-1]表示末尾复杂键可用 hash 索引.value[#hash]用key获取 map 的键而非值用at(index)按稳定序定位元素list 中与[index]等价set/map 中at(0)恒为最小元素。注意 shell 转义应使用单引号包裹整个参数否则如noms show ...value[0000024-02-999].Ownership_Name这类带双引号的路径会报错Invalid index。实验性查询ngqlGraphQL 桥接Noms 的查询语言尚不完整团队曾尝试引入 GraphQL即 ngql但仍处于实验阶段。ngql 是 Noms 与 GraphQL 之间的实验性桥梁提供 Noms 类型/值与 GraphQL 类型/值的相互转换 API以及一些可用于实现 GraphQL 端点的函数。类型转换规则要点详见 go/ngql/README.md 与 go/ngql/types.go除原始类型Bool、Number、String外的几乎所有 Noms 值都以 GraphQL struct 表示TypeConverter提供NomsTypeToGraphQLType与NomsTypeToGraphQLInputType输入类型不允许 union 与循环 structBool/Number/String 分别映射为非空Bool/Float/StringList 映射为含values、size字段的 struct支持at起始索引默认 0、count返回数量默认全部参数Set 额外支持key起始值、through结束值含、keys仅返回匹配键的值参数Map 映射为含values、keys、entrieskey/value字段的 entry struct、size的 struct参数同 SetStruct 映射为带额外hash字段的 GraphQL structNoms 可选字段映射为可空类型Ref 映射为含targetHash、targetValue字段的 struct。目前不支持的类型包括 Blob、Type、含非 Struct 成员的 Union且 GraphQL 输入类型不支持 union限制了可用的输入类型。状态与已知局限Noms 目前没有活跃维护README 明确建议除娱乐或研究外不应使用若需要类似功能的活跃项目可关注 Noms 的 fork——Dolt。在依赖它构建系统前应知晓以下主要未决问题长提交链下的同步性能issue #2233迁移机制issue #3363垃圾回收issue #3374查询语言尚未定型GraphQL 桥接不完整其他各类较小的 bug 与改进。更多学习资源去中心化数据库面向去中心化 Web 的 Noms 应用技术总览基础概念CLI 指南命令行界面导览Go SDK 导览Go API 使用路径拼写规范对象与数据集参数写法FAQ常见问题。Noms 采用 Apache License 2.0Attic Labs, Inc.授权。若对 Noms 的源码级细节感兴趣建议从 cmd/noms/noms.goCLI 注册、go/types/rolling_value_hasher.goProlly Tree 切片核心、go/hash/hash.go哈希策略以及 go/nbs 目录存储层入手阅读。赞分享数据库版本控制后端【免费下载链接】nomsThe versioned, forkable, syncable database项目地址https://gitcode.com/gh_mirrors/no/noms点击查看免费下载相关推荐Noms数据库Git启发的版本化分布式数据库革命Noms数据库Git启发的版本化分布式数据库革命 Noms是一个革命性的分布式数据库系统由Attic Labs团队开发旨在解决传统数据库在数据版本控制、分数据库版本控制后端5分钟快速上手PingFangSC字体免费开源的中文Web排版终极方案5分钟快速上手PingFangSC字体免费开源的中文Web排版终极方案 你是否在为网站的中文显示效果而烦恼不同设备上字体渲染不一致商业字体授权费用昂贵这前端Noms 技术纵览Merkle DAG 与 Prolly Tree 支撑的版本化、可同步去中心化数据库Noms 技术纵览Merkle DAG 与 Prolly Tree 支撑的版本化、可同步去中心化数据库 本文基于仓库 doc/intro.md https:/数据库版本控制后端上一篇E7Helper终极指南第七史诗自动化助手完整使用教程下一篇InfluxDB StudioWindows平台时间序列数据库图形化管理工具完整教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。