第 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 formats。
BLOCK_V1/ store 字符串block-block-1.1:5.14.0 引入、5.16.0 GA、5.22.0 起为 Enterprise 新建库默认(未指定db.format时)。Community 默认仍是aligned。block在手册中标为 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 | 挂在该点上的关系及关系侧数据 |
编码策略:尽量塞满。手册给出的数量级直觉(随类型与体积剧烈变化,不是保证):
- 大约最多约 10 个标签;
- 大约 6–7 个属性;
- 大约最多约 5 条关系。
标签多、属性少时标签可多装;关系属性少时关系可多装——与「固定 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.1 与
record-aligned-1.1 并存。离线可用
neo4j-admin database info。
四、一次 hop:剧本重写
仍用第 02–03 篇的目标:从节点 \(v\) 取类型 \(t\)、某方向的对端。
4.1 理想路径(数据全在 x1)
- 按
nodeId计算偏移,读 128 B 主块。 - 在关系侧 64 B(及编码结构)内解析属于 \(t\) 的关系。
- 对端节点 id 与小属性可能已在块内;胖属性再跟
big_values引用。
相对 record sparse:没有「先读 15 B Node,再随机追 \(d\) 条 34 B Rel,再追 Prop 链」的强制三跳。是否真变成单次 page fault,仍取决于页大小、缓存与块是否跨页——那是第 5 篇的计量问题;布局层已经把逻辑上的共置钉死。
4.2 中度数(关系进
relationship.xd)
- 读 x1,发现关系侧溢出引用。
- 读动态关系记录(\(\le 2047\) B)。
- 在动态块内按类型过滤——块内扫描替代跨页指针链。
4.3 超节点类型(进
relationship.dense)
- 读 x1(及可能的 xd 元数据)。
- 进入该节点的 dense root,按类型与方向在 B+ 树中定位。
- 扫描/探测该类型叶子上的关系条目。
与第 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 迁移方向(手册)
- EE 存量
standard/high_limit/aligned→ 推荐迁block(high_limit在下一 LTS 后硬阻断,见第 01 篇时间线)。 block→aligned/high_limit:手册写不推荐;且须落在目标格式更严的实体上限内。- Community
读者:本章是对照坐标系与运维阅读材料;默认库仍可能是
record-aligned-*,指针故事回第 03 篇。
创建时可在
CREATE DATABASE … OPTIONS { storeFormat: 'block' }、db.format、或
neo4j-admin database import/copy --format=block
等路径指定(手册 How to set the database store
format)。具体命令以你安装版本文档为准。
六、争论与开放问题
- 内联 vs 链表:A 侧(block)押中小度数共置与更少追逐;B 侧(record / 边表)押简单记录模型与 SQL/工具链熟悉度。Neo4j EE 产品票投给 A;Community 默认仍停在 record 家族——edition 分裂本身就是争论的工程落点。
- 固定 128 B 是否浪费:对超大规模「空壳节点」图不友好;对「真实业务节点带几条边几个属性」友好。应用建模(要不要物化大量无边点)会反馈到存储税。
- 开放问题: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 输出 |
未跑 | 只引用手册示例形态 |
九、小结
- Block = 128 B 主块内联 + 动态溢出 + 每点 dense 树:消的是 record 系上「Node / Rel / Prop 分店」的指针税,不是多跳基数本身。
- 分层触发要记清:$$31 B 属性内联;节点/关系侧超 64 B 进 xd;关系再爆进 dense;极端属性进 huge。
- 与第 03 篇对读:sparse 链 ↔︎ x1/xd 扫描;relationship group ↔︎ dense B+;整点 dense 比特 ↔︎ 类型级落点。
- 下一篇:page cache 如何放大或抹平这两种布局的局部性差异,以及超节点在缓存计量里长什么样。
→ 系列目录 · 上一篇:Record 系布局 · 下一篇:Page cache
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【图数据库内核】图引擎全景:属性图作为一等公民的存储与遍历
定位属性图相对行存/LSM/向量/GraphRAG 的生态位;钉住邻接代价、Neo4j store format(record→block)、page cache、索引与 Expand、Cypher 计划五条坐标系,并以 Angles & Gutierrez 谱系与原生图争论收束系列路线。
【图数据库内核】属性图 · Store Format · Expand · Cypher 计划边界
补齐站内缺失的图拓扑存储与遍历内核:属性图模型、Neo4j record/aligned/block 布局、page cache 与 dense 节点、标签索引、Expand 与 Cypher 计划边界,以及 TinkerPop/JanusGraph 对照与 GraphRAG 接口。
【图数据库内核】邻接的代价模型:边表 JOIN、CSR 与原生指针为何不是同一件事
把同一逻辑图落成边表+索引、CSR、原生关系链与 Neo4j block 内联四条路径,用统一代价语言比较一次 hop 与 k 跳扩张;钉住幂律超节点与局部性,为后续 record/block 布局篇垫底座。
【图数据库内核】Neo4j record 系布局:节点、关系链、属性链与 dense group
以 Neo4j 5.26 record-storage-engine 源码钉住 standard/aligned 固定记录:15B 节点、34B 关系、41B 属性、25B relationship group;讲清 sparse 链、dense 阈值与一次 hop 的指针路径。