JanusGraph 数据模型
JanusGraph 以邻接表格式存储图,这意味着图被存储为顶点及其邻接表的集合。顶点的邻接表包含该顶点的所有关联边(和属性)。
通过以邻接表格式存储图,JanusGraph 确保了顶点的所有关联边和属性都紧凑地存储在存储后端中,这加快了遍历速度。缺点是每条边必须存储两次——对于边的每个端点顶点存储一次。
此外,JanusGraph 维护每个顶点的邻接表按排序键和边标签的排序顺序进行排序。排序顺序能够使用以顶点为中心的索引高效检索邻接表的子集。
JanusGraph 将图的邻接表表示存储在任何支持 Bigtable 数据模型的存储后端中。
Bigtable 数据模型

在Bigtable 数据模型下,每个表都是行的集合。每行由一个键唯一标识。每行由任意(大但有限的)数量的单元格组成。一个单元格由列和值组成。在一个给定行中,一个单元格由一个列唯一标识。Bigtable 模型中的行被称为“宽行”,因为它们支持大量的单元格,并且这些单元格的列不必像关系数据库中那样预先定义。
JanusGraph 对 Bigtable 数据模型有额外的要求:单元格必须按其列排序,并且由列范围指定的单元格子集必须能够高效检索(例如,通过使用索引结构、跳表或二分查找)。
此外,特定的 Bigtable 实现可能会保持行按其键的顺序排序。JanusGraph 可以利用这种键序来有效地划分图,从而为非常大的图提供更好的加载和遍历性能。然而,这不是必需的。
JanusGraph 数据布局

JanusGraph 将每个邻接表存储为底层存储后端中的一行。(64位)顶点 ID(JanusGraph 唯一分配给每个顶点)是指向包含顶点邻接表的行的键。每条边和属性都作为行中的单个单元格存储,这允许高效插入和删除。因此,特定存储后端中每行允许的最大单元格数也是 JanusGraph 可以支持该后端中顶点的最大度数。
如果存储后端支持键序,则邻接表将按顶点 ID 排序,JanusGraph 可以分配顶点 ID,从而有效地划分图。ID 的分配方式使得经常共同访问的顶点具有绝对差较小的 ID。
单个边的布局

每条边和属性都作为其相邻顶点行中的一个单元格存储。它们被序列化,以便列的字节顺序遵循边标签的排序键。使用可变 ID 编码方案和压缩对象序列化来尽可能减小每条边/单元格的存储空间。
考虑上面图形顶行所示的单个边的存储布局。深蓝色框表示使用可变长度编码方案编码的数字,以减少它们占用的字节数。红色框表示一个或多个属性值(即对象),这些值使用在关联属性键中引用的压缩元数据进行序列化。灰色框表示未压缩的属性值(即序列化对象)。
边的序列化表示以边标签的唯一 ID(由 JanusGraph 分配)开头。这通常是一个小数字,并且通过可变 ID 编码压缩良好。该 ID 的最后一位被偏移以存储这是入边还是出边。接下来,存储构成排序键的属性值。排序键与边标签一起定义,因此排序键对象的元数据可以引用到边标签。之后,存储相邻顶点的 ID。JanusGraph 不存储实际的顶点 ID,而是存储与拥有此邻接表的顶点 ID 的差值。差值很可能是一个比绝对 ID 更小的数字,因此压缩效果更好。顶点 ID 之后是此边的 ID。JanusGraph 为每条边分配一个唯一 ID。这构成了边单元格的列值。边单元格的值包含边的签名属性(由标签的签名键定义)的压缩序列化,以及已添加到边的任何其他属性的未压缩序列化。
属性的序列化表示更简单,并且仅在列中包含属性的键 ID。属性 ID 和属性值存储在值中。但是,如果属性键定义为list(),则属性 ID 也存储在列中。