上周,我们发布了 RedisGraph 1.2.0。如果您一直在使用 RedisGraph 或正在考虑试用该模块,现在是查看我们一直在做的事情的好时机,因为我们一直在不断增强其图处理功能。让我们回顾一下新版本中一些更值得注意的添加。
在此版本之前,通过类型为 R 的边连接节点 A 到节点 B,然后在两者之间形成另一个相同类型的连接是不可能的。因为后者会覆盖前者,因为连接在 RedisGraph 中的表示方式。
对于每个关系类型,例如 `friend`、`works_at` 或 `owns`,RedisGraph 维护一个映射矩阵 M,其中 M[I,J] = EdgeID,其中 I 和 J 分别是源节点和目标节点 ID。在 A 和 B 之间形成第一个类型为 R 的连接将设置 M[I,J] 为 X,引入两者之间第二个类型为 R 的边将用 Y 覆盖 M[I,J]。
在 1.2.0 版本中,RedisGraph 现在允许同一类型的多个边。我们的映射矩阵 M 可以保存指向边 ID 数组的指针,M[I,J] = [ID0, ID1, … IDn],指定连接 I 到 J 的每个类型为 R 的边。
为了避免高内存碎片,请考虑一个包含 5 亿条边的图,这些边都没有共享源节点或目标节点,即任何两个节点之间都没有相同类型的多个边。一种简单的方法是分配 5 亿个长度为 1 的数组,这会显着增加我们的内存消耗。为了解决这个问题,我们的映射矩阵条目类型保持不变(UINT64)。从一个空图开始 - M[I,J] 为空 - 当形成第一个边 E 时,连接 I 到 J,M[I,J] = ID(E) 是一个简单的常量,不会分配额外的内存。在某个时刻,I 再次使用与 E 类型相同的边 X 连接到 J,M[I,J] = [ID(E), ID(X)],将分配一个数组并将指针放置在 M[I,J] 中。
每当创建一个图实体(节点、边)时,都会为其分配一个内部唯一 ID。此 ID 在整个实体生命周期中都是不变的,您可以使用 ID 函数检索实体 ID:MATCH (N) RETURN N, ID(N) LIMIT 10.同样,通过其 ID 查找节点:“MATCH (N) WHERE ID(N) = 9 RETURN N”过去执行全扫描,检查图中的每个节点。
我们最新的 RedisGraph 版本引入了一种优化,用单个 `seek node by id` 操作替换了这些全扫描和过滤操作,该操作在恒定时间内运行。
在 RedisGraph 中,遍历通过矩阵乘法执行。例如,考虑搜索模式:(A)-[X]->(B)-[Y]->(c)。为了找到与目标节点 (C) 连接的所有源节点 (A),我们只需将矩阵 [X] 乘以矩阵 [Y],X*Y=Z,并且 Z 的行和列分别表示 A 和 C。
如果我们只对少数 C 节点感兴趣,我们希望避免执行X*Y,因为这些矩阵可能非常大。因此,我们将添加一个小的过滤矩阵 F,(F*X)*Y。通过首先乘以F*X,我们正在减少任何计算的中间矩阵的维度,从而显着减少计算时间。
根据F*X*Y的结果,我们可能发现自己正在计算一个不同的过滤矩阵 F 并重新评估此表达式以获取额外的 C 节点。在获得足够的数据之前,此过程可能会重复多次。现在,每当搜索模式包含从左到右和从右到左的边时:(A)-[X]->(B)<-[Y]-(C),我们必须转置 Y,(F*X)*Transpose(Y) = Z。
以前版本的 RedisGraph 在每次评估此表达式时都转置 Y,这很昂贵。现在,从 1.2.0 版本开始,我们只会在第一次评估时转置一次。尽管这会导致每个查询的内存消耗更高,但延迟增益绝对值得。
总体而言,RedisGraph 1.2.0 包含了一些过期的功能和一些有趣的优化, 试一试让我们知道您的想法!