土法炼钢兴趣小组的算法知识备份

【图数据库内核】Neo4j Block format:128 B 主块、动态溢出与 dense B+ 树

文章导航

分类入口
databasegraph
标签入口
#graph-database#neo4j#block-format#store-format#dense-store#locality#property-graph

目录

03 篇把 record 系写成「Node → Rel 链 → Prop 链」。本篇讲产品正在推的另一条路:block 格式用固定主块尽量内联,再用动态记录与 dense 树接住长尾——官方主张是更少 pointer chasing、更少为一次查询装入的页。本站不伪造延迟对比;只钉手册布局与相对第 03 篇改写了哪几步。

本文是「图数据库内核」系列第 4 篇(共 16 篇)。→ 系列目录

篇目 核心内容
第 3 篇 · Record 系布局 15/34/41/25 B 定长链表
第 4 篇 · Block format x1 / xd / big_values / dense 分层
第 5 篇 · Page cache 两种布局如何变成 I/O

版本锚定:Neo4j Operations Manual(Product Version 2026.07 / current)Database internals → Store formatsBLOCK_V1 / store 字符串 block-block-1.1:5.14.0 引入、5.16.0 GA、5.22.0 起为 Enterprise 新建库默认(未指定 db.format 时)。Community 默认仍是 alignedblock 在手册中标为 Enterprise Edition 能力路径——读盘前先确认版本与 edition,勿假定开源 Community 与 EE 同默认。


一、为什么还要一篇 block

问题 Record 系答案(第 03 篇) Block 手册主张
小度数点的属性在哪 另走 nextProp 尽量与节点数据共置在主块 / 动态块
少量关系怎么读 nextRel 逐条(可能随机页) 尽量落在同一 128 B 主块的关系侧
超多关系怎么办 dense → relationship group + 类型链 动态块装不下 → 按类型/方向排序的 dense B+ 树
上限 节点/关系 \(2^{35}\) 量级等 节点 \(2^{48}\);关系/属性手册写 无定义上界

产品时间线(同页 Table 1):推 block 与弃用 standard / high_limit(5.23)是同一战略叙述——存量 record-aligned-* 不会一夜消失,但新 EE 库的默认坐标系已换成 block

手册对 block 的性能表述属于厂商设计目标(更少 page fault / 更好 locality)。本篇把它标为手册主张,不改写成「本站测得快 N 倍」。


二、文件地图:block.* 三层

手册写明:block 格式下图元素落在一组以 block. 为前缀的 store 文件。最重要的三层:

flowchart TB
  X1["Small store<br/>block.x1.db<br/>128 B × nodeId"]
  XD["Dynamic stores<br/>block.node.xd.db<br/>block.relationship.xd.db<br/>block.big_values.db"]
  DN["Dense / huge<br/>block.relationship.dense.db<br/>block.huge.db"]
  X1 -->|"装不下标签/属性/关系"| XD
  XD -->|"关系超过动态块上限"| DN
  X1 -.->|"部分类型仍可留在 x1/xd"| DN

2.1 Small store:block.x1.db

创建节点时,在

[ = 128, ]

分配一块固定 128 B。块内两个 64 B 记录:

半块 内容
Node-data record 标签与节点属性
Relationship-data record 挂在该点上的关系及关系侧数据

编码策略:尽量塞满。手册给出的数量级直觉(随类型与体积剧烈变化,不是保证):

标签多、属性少时标签可多装;关系属性少时关系可多装——与「固定 5 条边」的误解相反。

关键空间事实:即使节点几乎空,这 128 B 也一直占用,直到节点删除。小点数、低填充图会付出固定主块税;这是用空间换共置的显式取舍。

多数数据集上「绝大多数节点装得进 small store」是手册原话级预期——工作负载若符合「中小度数 + 中等属性」,一次点读理想上接近读一个主块

2.2 Dynamic stores

当节点或关系相关数据超过 small store 容量时启用。

文件 触发与尺寸 能力直觉(手册)
block.node.xd.db 标签/节点属性超过 x1 里 64 B 节点侧;size = X × 128 B,最大 8192 B;x1 存引用;可随增删变尺寸并可能重定位 典型可装「数百」级标签或属性
block.relationship.xd.db 关系数据超过 x1 关系侧 64 B,该节点已有至少一条 dense 关系;自适应尺寸,最大 2047 B;x1 存引用 典型可装「数百」级关系
block.big_values.db 编码后总大小 \(\gtrsim\) 31 B 的属性(常见长串/数组)不内联,放引用;记录 size = X × 64 B,最大 8192 B;更大则多记录链表 把「胖值」踢出热邻接路径

注意关系动态块的第二触发条件:一旦出现 dense 关系,就会在 xd 层参与组织——dense 不是与 x1 完全无关的平行宇宙。

小于约 31 B 的属性仍内联在 small / dynamic 的节点或关系数据里——这是相对 record 系「属性几乎总是另链」的核心差异之一。

2.3 Dense stores 与 huge store

当 small + dynamic 都不够:

文件 结构 规则
block.relationship.dense.db 多根世代 B+ 树每个节点一个 root 关系数据超过动态关系记录上限时溢出到此;树内按关系类型与方向排序;同一类型的全部关系始终在同一 store(不跨 x1/xd 与 dense 拆散同一 type);手册写明单点 dense 关系数无上界
block.huge.db 承接极端标签/属性体积 典型:实体带数千属性,其它 store 都装不下

混合态被手册明确允许:同一节点可以部分类型的关系仍在 x1 / relationship.xd,其它类型在 dense——但按类型原子落点(同 type 不拆 store)。这比 record 系「整点要么 sparse 要么 dense」更细:分界在类型级溢出,而不是单一 Node.dense 比特(第 03 篇)那种整点翻转心智。


三、实体上限:和 aligned 并排看

Table 2(Block format entity limits):

名称 Block 上限
Nodes \(2^{48}\)
Relationships \(\infty\)(无定义上界)
Properties \(\infty\)(无定义上界)
Labels \(2^{31}\)
Relationship types \(2^{30}\)
Property keys \(2^{31}\)

对照第 03 篇 StandardFormatSettings / 手册 aligned 表:节点/关系约 \(2^{35}\),关系类型仅 \(2^{16}\)。从 aligned 迁回或迁到更小上限格式前,必须确认图落在目标格式限制内——手册对 block→aligned 写明「不推荐」且受限于 aligned 更低上限。Token 名长度:block 支持直至 GQL 标识符上限 16 383 字符(手册)。

验证身份(命令语义来自手册;下列结果形态为文档示例,非本机输出):

SHOW DATABASES YIELD name, store

文档示例中出现过 block-block-1.1record-aligned-1.1 并存。离线可用 neo4j-admin database info


四、一次 hop:剧本重写

仍用第 02–03 篇的目标:从节点 \(v\) 取类型 \(t\)、某方向的对端。

4.1 理想路径(数据全在 x1)

  1. nodeId 计算偏移,读 128 B 主块。
  2. 在关系侧 64 B(及编码结构)内解析属于 \(t\) 的关系。
  3. 对端节点 id 与小属性可能已在块内;胖属性再跟 big_values 引用。

相对 record sparse:没有「先读 15 B Node,再随机追 \(d\) 条 34 B Rel,再追 Prop 链」的强制三跳。是否真变成单次 page fault,仍取决于页大小、缓存与块是否跨页——那是第 5 篇的计量问题;布局层已经把逻辑上的共置钉死。

4.2 中度数(关系进 relationship.xd

  1. 读 x1,发现关系侧溢出引用。
  2. 读动态关系记录(\(\le 2047\) B)。
  3. 在动态块内按类型过滤——块内扫描替代跨页指针链。

4.3 超节点类型(进 relationship.dense

  1. 读 x1(及可能的 xd 元数据)。
  2. 进入该节点的 dense root,按类型与方向在 B+ 树中定位。
  3. 扫描/探测该类型叶子上的关系条目。

与第 03 篇 relationship group 对照:

Record dense Block dense
索引结构 Group 链 → 每类型双向 Rel 链 每节点一棵(多根森林中的)B+ 树,键含类型与方向
类型过滤 跳过其它 group / 不读其它类型 Rel 树查找直接落到类型
同点混合 整点 dense 后走 group 允许部分类型留在 x1/xd,部分在 dense;同 type 不拆
度数上限 受关系 id 空间等约束 手册:dense 内关系数无上界

两边都把「按类型找边」升成一等操作;block 用树替代「group 记录 + 定长 Rel 链」,并与主块内联同一产品故事。

4.4 属性路径

编码后 ≲ 31 B  → 内联在 x1 或 node/rel xd
更大           → block.big_values.db(可链表)
极端多属性     → block.huge.db

Record 系默认「属性在旁路链」;block 默认「小属性跟拓扑走,胖值才旁路」。读「带小属性的一跳」时,block 少付的正是第 03 篇第六节那种 \(\mathrm{Cost}(\text{prop chain})\)


五、空间与迁移:选型旋钮

5.1 固定主块税

\(N\) 个节点,仅 x1 就有基线

[ _{x1} = 128, N ]

(另加动态/dense/大值)。空节点或极瘦节点比例高时,这份基线刺眼;度数与属性「刚刚好塞进 64 B+64 B」时,这份基线买的是局部性。

5.2 迁移方向(手册)

创建时可在 CREATE DATABASE … OPTIONS { storeFormat: 'block' }db.format、或 neo4j-admin database import/copy --format=block 等路径指定(手册 How to set the database store format)。具体命令以你安装版本文档为准。


六、争论与开放问题

  1. 内联 vs 链表:A 侧(block)押中小度数共置与更少追逐;B 侧(record / 边表)押简单记录模型与 SQL/工具链熟悉度。Neo4j EE 产品票投给 A;Community 默认仍停在 record 家族——edition 分裂本身就是争论的工程落点
  2. 固定 128 B 是否浪费:对超大规模「空壳节点」图不友好;对「真实业务节点带几条边几个属性」友好。应用建模(要不要物化大量无边点)会反馈到存储税。
  3. 开放问题:dense B+ 树与 page cache 的交互计量(第 5 篇);写入时 x1→xd→dense 的重定位与空间回收(第 6 篇);开源版能力边界随版本变化——正文只引用当前手册,不猜测未文档化行为。

谱系上,block 仍是属性图「邻接一等公民」传统的延续,只是把第 03 篇的指针链换成分阶记录 + 每点一树。Angles & Gutierrez 停在模型层;工业争论转移到「共置粒度」与「超节点用链还是用树」。


七、回指系列坐标系

坐标系 本篇贡献
邻接代价(02) 小度数 \(\mathrm{Cost}_{\mathrm{block}}\) ≈ 单主块解析;dense ≈ 类型上的树探
Record 布局(03) 对照物:逐条 Rel/Prop 指针
Store format(本篇) x1 / xd / big_values / dense / huge 分层钉死
Page cache(05) 下一篇:共置如何变成命中率
Expand / 计划(09–10) 类型落在树键上,过滤更早;不消灭输出基数爆炸

八、来源与实验台账(本篇)

结论 等级 来源
x1 128 B、双 64 B、偏移公式、典型装填直觉、空块仍占 128 B A Operations Manual Store formats → Small store
node xd / rel xd / big_values 尺寸与触发条件 A 同上 → Dynamic stores
dense 多根世代 B+ 树、按类型方向排序、同 type 同 store、无上界;huge store A 同上 → Dense stores
BLOCK_V1 版本表与实体上限表 A 同上 Table 1–2
EE 默认 / CE aligned;弃用与迁移建议 A 同上格式总览与 Format deprecations
本机 migrate / SHOW DATABASES 输出 未跑 只引用手册示例形态

九、小结

  1. Block = 128 B 主块内联 + 动态溢出 + 每点 dense 树:消的是 record 系上「Node / Rel / Prop 分店」的指针税,不是多跳基数本身。
  2. 分层触发要记清:$$31 B 属性内联;节点/关系侧超 64 B 进 xd;关系再爆进 dense;极端属性进 huge。
  3. 与第 03 篇对读:sparse 链 ↔︎ x1/xd 扫描;relationship group ↔︎ dense B+;整点 dense 比特 ↔︎ 类型级落点。
  4. 下一篇:page cache 如何放大或抹平这两种布局的局部性差异,以及超节点在缓存计量里长什么样。

系列目录 · 上一篇:Record 系布局 · 下一篇:Page cache

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .