Apache GraphAr 格式规格与元数据详解

逐字段解读 Apache GraphAr 的三层元数据 YAML(GraphInfo/VertexInfo/EdgeInfo)与 Parquet 数据文件组织:chunk 切分、offset 索引、vertex_count 计数、prefix 命名规则及 CSR/CSC/COO 邻接表布局对比。

Apache GraphArApacheApache GraphAr大数据Parquet图数据库2026/10/14

Apache GraphAr 格式规格与元数据详解

目标:基于真实产出的 GraphAr 数据集,逐字段解读三层元数据 YAML(GraphInfo/VertexInfo/EdgeInfo),并解析数据侧的 Parquet chunk 组织、offset 索引、计数文件与 prefix 命名规则。所有文件内容与回显均来自实测环境(Ubuntu 22.04,Python 3.10.12,graphar 0.13.0.dev1)中真实写出的数据。

一、环境信息

本章解读对象是两份真实 GraphAr 数据集,均在同一实测环境中产生与校验:

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64),Python 3.10.12
  • Apache GraphAr:0.13.0.dev1(PyPI graphar 包,含 C++ 核心库与 CLI 工具)
  • 实测样本一:自建小图(3 个 person 顶点、4 条 knows 边),产出目录 /root/graphar-demo,经 graphar check 校验通过(回显 INFO Graph is valid)
  • 实测样本二:官方测试数据 ldbc_sample(903 个顶点、6626 条边),目录 /opt/incubator-graphar/testing/ldbc_sample/parquet/
  • Parquet 检查工具:独立虚拟环境安装 pyarrow 25.0.1,用于打开数据文件核对内部列

版本与校验状态的复验命令与实测回显:

graphar --version && graphar check -p /root/graphar-demo/demo_graph.graph.yml
GraphAr CLI Version: 0.13.0.dev1
INFO     Graph is valid

💡 说明:graphar check 是解读一份未知 GraphAr 数据前最常用的第一步——先确认元数据自洽,再逐文件解析。

二、格式总览与实测样本

2.1 三层元数据 + 数据文件的模型

GraphAr 把一张图拆成「三层元数据 YAML + 一组数据文件」:

层元数据文件描述内容数据文件
图<图名>.graph.yml(GraphInfo)图名、根前缀、版本、顶点/边子文件引用无
顶点类型<类型名>.vertex.yaml(VertexInfo)类型名、chunk 大小、属性分组、文件类型vertex/<类型>/... 下的 Parquet/CSV 文件
边类型<源>_<边>_<目的>.edge.yaml(EdgeInfo)源/边/目的类型、三种 chunk 大小、邻接表布局、属性分组edge/<源>_<边>_<目的>/... 下的文件

三个关键设计:元数据与数据分离(YAML 纯描述,数据纯列存);顶点与边都按 chunk 切分(并行读写的最小单位);边按邻接表布局组织(决定遍历方向与性能)。

2.2 实测样本全景

自建小图的完整文件清单(实测):

find /root/graphar-demo -type f | sort
/root/graphar-demo/demo_graph.graph.yml
/root/graphar-demo/edge/person_knows_person/ordered_by_dest/adj_list/part0/chunk0
/root/graphar-demo/edge/person_knows_person/ordered_by_dest/creationDate/part0/chunk0
/root/graphar-demo/edge/person_knows_person/ordered_by_dest/edge_count0
/root/graphar-demo/edge/person_knows_person/ordered_by_dest/offset/chunk0
/root/graphar-demo/edge/person_knows_person/ordered_by_dest/vertex_count
/root/graphar-demo/person.vertex.yaml
/root/graphar-demo/person_knows_person.edge.yaml
/root/graphar-demo/vertex/person/firstName_lastName_gender/chunk0
/root/graphar-demo/vertex/person/id/chunk0
/root/graphar-demo/vertex/person/vertex_count

下面三章按「图 → 顶点 → 边」逐层解读这份样本的三层 YAML,第六章解析数据文件。

三、GraphInfo:图级元数据详解

3.1 实测 YAML 全文

自建图的图级元数据 demo_graph.graph.yml 实测内容:

name: demo_graph
prefix: /root/graphar-demo/
vertices:
  - person.vertex.yaml
edges:
  - person_knows_person.edge.yaml
version: gar/v1

3.2 字段逐项说明

字段实测取值含义与要点
namedemo_graph图名,读取侧 GraphInfo.load() 后用 get_name() 获取
prefix/root/graphar-demo/数据根前缀。Builder 写出与 Collection 读回都基于它做路径拼接,必须与实际目录一致且带尾斜杠(自建图实测踩坑点,见《Apache GraphAr 数据读写实战详解》第七章)
verticesperson.vertex.yaml 列表顶点子元数据文件引用,相对 graph.yml 所在目录解析
edgesperson_knows_person.edge.yaml 列表边子元数据文件引用,相对 graph.yml 所在目录解析
versiongar/v1格式版本号,当前规范统一为 gar/v1

3.3 引用与解析规则(实测确认)

三个实测确认的解析行为,直接影响手工组织数据目录:

  1. 子文件引用相对 graph.yml 所在目录解析:官方 ldbc_sample 的 ldbc_sample.graph.yml 没有写 prefix 字段,加载后前缀自动取 graph.yml 所在目录(实测 graphar show 输出 prefix: "/opt/incubator-graphar/testing/ldbc_sample/parquet/");
  2. save() 固定生成 .yaml 后缀引用:SDK 的 GraphInfo.save() 写出的引用固定是 <类型名>.vertex.yaml、<源>_<边>_<目的>.edge.yaml;官方测试数据用的是 .yml 后缀也能正常加载(加载端两种后缀都认),但自建图必须按 save() 的 .yaml 命名存放子文件,否则重新 load 报 ValueError: IOError: Failed to open local file ...(实测排障记录见实战篇);
  3. show 输出统一为 .yaml 形态:graphar show 打印的是加载后重新序列化的元数据,磁盘上的 .yml 在输出中统一显示为 .yaml(ldbc_sample 实测:磁盘 person.vertex.yml,show 输出 person.vertex.yaml)。

四、VertexInfo:顶点类型元数据详解

4.1 实测 YAML 全文

自建图 person.vertex.yaml 实测内容:

type: person
chunk_size: 100
prefix: vertex/person/
property_groups:
  - properties:
      - name: id
        data_type: int64
        is_primary: true
    file_type: parquet
  - properties:
      - name: firstName
        data_type: string
        is_primary: false
      - name: lastName
        data_type: string
        is_primary: false
      - name: gender
        data_type: string
        is_primary: false
    file_type: parquet
version: gar/v1

4.2 字段逐项说明

字段实测取值含义与要点
typeperson顶点类型名,边元数据通过它引用顶点
chunk_size100每个顶点数据文件最多容纳的顶点行数,决定 chunk 切分
prefixvertex/person/该类型数据在图前缀下的相对目录(实测同样要求带尾斜杠)
property_groups两组属性分组,每组独立落成一个数据文件目录,实现按属性组裁剪读取
properties[].nameid、firstName 等属性名,数据文件中的列名
properties[].data_typeint64、string属性数据类型(bool/int32/int64/float32/float64/string 等)
properties[].is_primarytrue/false是否主键。实测主键属性 id 的 is_nullable 由加载器置为 false,其余属性可空
file_typeparquet该属性组数据文件的物理格式;规范同时支持 csv(官方 modern_graph 实测使用 csv)
versiongar/v1格式版本号

4.3 属性分组设计

实测样本把 4 个属性拆成两组:id(主键组)与 firstName、lastName、gender(姓名组)。分组的意义在磁盘上看得最清楚——vertex/person/ 下实测正好两个数据目录 id/ 与 firstName_lastName_gender/,各自一个 chunk0 文件。读取「只要 id 列」时只需打开 id 组的文件,这就是 GraphAr 属性裁剪能力的物理基础。

4.4 prefix 命名规则(三种实测形态对照)

属性组的数据目录名(组前缀)有三种来源,实测样本恰好各占其一:

形态实测来源写法磁盘结果
显式声明modern_graph(官方)属性组内写 prefix: id/、prefix: name_age/目录与声明一致
隐式推导自建图 person.vertex.yaml(不写组 prefix)按组内属性名以下划线连接,末尾加 /id/、firstName_lastName_gender/
隐式推导ldbc_sample 官方元数据(同样不写组 prefix)同上vertex/person/id/ 等目录与推导一致

用 graphar show -v person 观察自建图,加载器把推导出的组前缀、可空性一并展示(实测节选):

INFO     chunk_size: 100
         prefix: vertex/person/
         property_groups:
           - file_type: parquet
             prefix: id/
             properties:
               - data_type: int64
                 is_nullable: false
                 is_primary: true
                 name: id
           - file_type: parquet
             prefix: firstName_lastName_gender/

⚠️ 注意:组前缀推导规则随属性名变化——改名或调组会改变磁盘目录名。对接已有系统时建议像 modern_graph 一样显式声明 prefix,避免隐性耦合。

五、EdgeInfo:边类型元数据详解

5.1 实测 YAML 全文

自建图 person_knows_person.edge.yaml 实测内容:

src_type: person
edge_type: knows
dst_type: person
chunk_size: 1024
src_chunk_size: 100
dst_chunk_size: 100
directed: false
prefix: edge/person_knows_person/
adj_lists:
  - ordered: false
    aligned_by: src
    file_type: parquet
  - ordered: true
    aligned_by: src
    file_type: parquet
  - ordered: true
    aligned_by: dst
    file_type: parquet
property_groups:
  - file_type: parquet
    properties:
      - name: creationDate
        data_type: string
        is_primary: false
version: gar/v1

5.2 字段逐项说明

字段实测取值含义与要点
src_type / edge_type / dst_typeperson / knows / person边的类型三元组,GraphInfo 用它引用边元数据
chunk_size1024每个边数据文件(chunk)最多容纳的边行数
src_chunk_size / dst_chunk_size100 / 100源/目的顶点侧的分块大小,决定边数据 part 的划分粒度
directedfalse有向性声明;实测无向边同样只存一份邻接数据
prefixedge/person_knows_person/边数据相对目录(默认按 <src>_<edge>_<dst> 命名)
adj_lists三条声明该边集支持的邻接表布局(可多种并存),每条含 ordered、aligned_by、file_type、prefix
adj_lists[].ordered / aligned_byfalse/true、src/dst是否按对齐侧顶点排序;对齐源为 src、对齐目的为 dst
adj_lists[].prefix声明中未写实测由 ordered 与 aligned_by 推导为 unordered_by_source/、ordered_by_source/、ordered_by_dest/(graphar show -es person -e knows -ed person 回显确认)
property_groupscreationDate 一组边属性分组,语义同顶点
versiongar/v1格式版本号

5.3 邻接表布局:三种声明与实际落盘

EdgeInfo 可以同时声明多种布局,但数据文件只为实际写出的布局存在:自建图实测只写出 ordered_by_dest 一种(find 清单里 edge 目录下只有 ordered_by_dest 子树),而官方 ldbc_sample 三种布局的数据目录全部存在(实测 find edge -type d 可见 unordered_by_source、ordered_by_source、ordered_by_dest 三组并列,各含 adj_list、creationDate 与 offset 目录)。

💡 技术拓展:邻接表布局对应图计算领域的经典存储结构,选择依据如下——

GraphAr 布局经典结构有序性数据文件特征适用场景
unordered_by_sourceCOO(坐标列表)不排序仅 adj_list 表,无 offset 文件顺序扫描全部边、追加写
ordered_by_sourceCSR(压缩稀疏行)按源顶点排序adj_list + offset(每源段一个)沿出边遍历,图计算主流
ordered_by_destCSC(压缩稀疏列)按目的顶点排序adj_list + offset(每目的段一个)沿入边遍历、入度分析

CSR/CSC 的取舍本质是遍历方向:CSR 是「行视图」(按源顶点取邻域),CSC 是「列视图」(按目的顶点取邻域)。本文实测自建图采用 ordered_by_dest(CSC)写出,第六章将用真实文件内容展示其 offset 组织与行排序;性能敏感的图计算场景一般首选 ordered_by_source。

六、数据文件组织:chunk、offset 与计数文件

6.1 顶点数据:属性组目录、chunk 切分与 vertex_count

顶点数据按「类型前缀 + 属性组前缀」两级目录组织,按 chunk_size 切成 chunk0、chunk1 等文件。实测 ldbc_sample 的 person 顶点(903 个、chunk_size 100)正好 10 个 chunk:

ls /opt/incubator-graphar/testing/ldbc_sample/parquet/vertex/person/id/
chunk0
chunk1
chunk2
chunk3
chunk4
chunk5
chunk6
chunk7
chunk8
chunk9

每个顶点类型目录下有 8 字节定长计数文件 vertex_count,内容为小端 int64 的顶点总数,od 实测:

od -An -tu8 /opt/incubator-graphar/testing/ldbc_sample/parquet/vertex/person/vertex_count
                  903

数据文件虽无 .parquet 扩展名,实测文件头 4 字节为 Parquet 魔数:

head -c 4 /opt/incubator-graphar/testing/ldbc_sample/parquet/vertex/person/id/chunk0 | od -c
0000000   P   A   R   1
0000004

6.2 顶点 chunk 内部结构(pyarrow 实测)

用 pyarrow 打开自建图的顶点数据文件,观察每列内容(脚本保存为 /tmp/dump_parquet_doc.py 后运行):

import pyarrow.parquet as pq

base = "/root/graphar-demo"
files = [
    "vertex/person/id/chunk0",
    "vertex/person/firstName_lastName_gender/chunk0",
    "edge/person_knows_person/ordered_by_dest/adj_list/part0/chunk0",
    "edge/person_knows_person/ordered_by_dest/creationDate/part0/chunk0",
    "edge/person_knows_person/ordered_by_dest/offset/chunk0",
]
for f in files:
    table = pq.read_table(base + "/" + f)
    print("== " + f)
    print("columns:", table.column_names)
    print("rows:", table.num_rows)
    if f.endswith("offset/chunk0"):
        offsets = table.column("_graphArOffset").to_pylist()
        print("first five:", offsets[:5])
        print("last five:", offsets[-5:])
        print("distinct:", sorted(set(offsets)))
    else:
        print("data:", table.to_pydict())

顶点部分实测回显:

== vertex/person/id/chunk0
columns: ['_graphArVertexIndex', 'id']
rows: 3
data: {'_graphArVertexIndex': [0, 1, 2], 'id': [0, 1, 2]}
== vertex/person/firstName_lastName_gender/chunk0
columns: ['_graphArVertexIndex', 'firstName', 'lastName', 'gender']
rows: 3
data: {'_graphArVertexIndex': [0, 1, 2], 'firstName': ['John', 'Jane', 'Alice'], 'lastName': ['Smith', 'Doe', 'Wonderland'], 'gender': ['male', 'female', 'female']}

要点:每个顶点 chunk 内部第一列是实现写入的 _graphArVertexIndex(chunk 内行号),其余列与元数据声明的属性一一对应;两个属性组文件的行数与行号一一对应,共同还原完整顶点。

6.3 边数据:adj_list、属性、offset、edge_count 四类文件

ordered_by_dest 布局下,自建图边目录实测包含四类文件(见 2.2 清单)。pyarrow 实测回显:

== edge/person_knows_person/ordered_by_dest/adj_list/part0/chunk0
columns: ['_graphArSrcIndex', '_graphArDstIndex']
rows: 4
data: {'_graphArSrcIndex': [1, 0, 2, 0], '_graphArDstIndex': [0, 1, 1, 2]}
== edge/person_knows_person/ordered_by_dest/creationDate/part0/chunk0
columns: ['creationDate']
rows: 4
data: {'creationDate': ['2010-01-01', '2011-01-01', '2013-01-01', '2012-01-01']}
== edge/person_knows_person/ordered_by_dest/offset/chunk0
columns: ['_graphArOffset']
rows: 101
first five: [0, 1, 3, 4, 4]
last five: [4, 4, 4, 4, 4]
distinct: [0, 1, 3, 4]

逐项解读:

  1. adj_list 本体:每行一条边,列是源/目的顶点在各自 chunk 内的行号索引。实测 4 行的顺序为 (1,0)、(0,1)、(2,1)、(0,2)——先按目的顶点升序(0、1、1、2),同目的内再按源顶点升序,这正是 CSC 排序语义(与实战篇读回顺序完全一致);
  2. 边属性文件:目录按属性组前缀命名(creationDate),行数与 adj_list 严格对齐——第 i 行属性属于 adj_list 第 i 行的边。实测 4 个日期与写入时的边一一对应;
  3. offset 索引:CSC 式前缀和,offset[i] 等于目的顶点索引小于 i 的边数。实测目的顶点 0、1、2 分别有 1、2、1 条入边,前缀和为 0、1、3、4;文件共 101 行 = dst_chunk_size 100 + 1,超出实际顶点数的行以最后一个前缀和值 4 填充。每个目的顶点段(part)对应一个 offset chunk 文件——ldbc_sample 实测 ordered_by_source 布局的 offset 目录恰好 10 个文件,对应 10 个源顶点 chunk;
  4. 计数文件:edge_count0 为该 part 的边数(实测自建图 4、ldbc ordered_by_source part0 为 667),vertex_count 为对齐侧顶点数(实测 ordered_by_source 下为 903、ordered_by_dest 下同样为 903)。三个值均由 od 实测确认,文件同样为 8 字节小端 int64。

计数文件 od 实测(ldbc knows,ordered_by_source 布局):

od -An -tu8 /opt/incubator-graphar/testing/ldbc_sample/parquet/edge/person_knows_person/ordered_by_source/vertex_count /opt/incubator-graphar/testing/ldbc_sample/parquet/edge/person_knows_person/ordered_by_source/edge_count0
                  903                  667

6.4 csv 文件类型的实测形态

file_type 并不限于 parquet:官方 modern_graph 的元数据实测声明 file_type: csv(并显式声明组前缀 id/、name_age/)。打开其数据文件即普通 CSV,首行为内部索引列与属性列(实测):

head -3 /opt/incubator-graphar/testing/modern_graph/vertex/person/id/chunk0
_graphArVertexIndex,id
0,2
1,6

💡 说明:csv 形态下 _graphArVertexIndex 与属性列同样成对出现,且属性值(2、6)独立于行号——主键值与 chunk 内行号是两个概念,这是排查「id 不连续」类问题时最容易混淆的点。

6.5 prefix 命名规则汇总

综合三层元数据的实测行为,GraphAr 目录命名规则可归纳为:

对象默认命名规则实测示例
顶点类型目录元数据 prefix 字段,惯例 vertex/<类型名>/vertex/person/
顶点属性组目录显式 prefix,或组内属性名下划线连接id/、firstName_lastName_gender/
边类型目录元数据 prefix 字段,惯例 edge/<src>_<edge>_<dst>/edge/person_knows_person/
边布局目录按 ordered 与 aligned_by 推导ordered_by_dest/
边 part 目录part<源或目的顶点 chunk 序号>part0 到 part9
chunk 文件chunk<chunk 序号>chunk0 到 chunk9
计数文件顶点 vertex_count;边 edge_count<part 序号>vertex_count、edge_count0

七、注意事项

  1. 三层 YAML 缺一不可:GraphInfo 引用子文件失败会直接 load 报错;自建图按 save() 规则命名(.yaml 后缀 + 类型名组合)最稳妥;
  2. prefix 是纯路径拼接:图级、类型级 prefix 与 Builder 目录参数都按字符串拼接,任何一处漏掉尾斜杠都会静默写偏路径且写出端不报错;
  3. 声明与数据可不对齐:EdgeInfo 可声明三种布局而数据只写其一(自建图实测即如此),graphar check 校验元数据自洽、不检查数据文件存在性,评估数据完整性要用 find 逐目录核对;
  4. 行对齐关系:边属性文件与 adj_list 文件按行严格对齐,offset 前缀和与对齐侧顶点段一一对应,改写这两个文件的行序或行数会直接破坏读取语义;
  5. 内部列约定:_graphArVertexIndex、_graphArSrcIndex、_graphArDstIndex、_graphArOffset 是实现写入的内部索引列,外部工具读取数据时应保留其行序语义;
  6. 计数文件是定长二进制:vertex_count/edge_count 为 8 字节小端 int64,不是文本、也不是 Parquet,直接 cat 会得到乱码,用 od 或按 int64 解析。

八、参考链接