GDB索引:更快的速度探索互联数据的奥秘

对于数据库的使用者而言,索引是一个非常熟悉的概念。Wikipedia中对数据库索引的定义:

A database index is a data structure that improves the speed of data retrieval operations on a database table at the cost of additional writes and storage space to maintain the index data structure.

用户创建索引的期望是能够避免全量扫描,通过索引来减少IO和CPU开销从而加速查询,是典型的空间换时间的优化方式。大部分数据库会默认创建主键/RowKey索引,二级索引等其它类型索引需要用户根据实际查询情况自定义地创建。
阿里云图数据库GDB对于用户点边的每一个属性都会自动创建索引信息,减少用户额外的索引操作。同时,用户也可以根据访问场景通过GDB支持的标准图查询语言Gremlin动态地创建复合索引、唯一索引和点中心索引等其它类型的索引来加速特定的查询。

自动索引

LDBC SNB是面向图数据管理系统的Benchmark,越来越受到图数据库厂商的重视,下面以LDBC SNB的数据模型为例来说明图数据库GDB的自动索引是如何工作的。

GDB索引:更快的速度探索互联数据的奥秘

从模型中可以看出,对于Person这种Label的节点,可能会有firstName、lastName、creationDate等多种属性。当用户使用Gremlin DSL插入某个节点的数据时:

g.addV('Person').property('firstName', 'Foo').property('lastName', 'Bar').property('creationDate', 20200225)

图数据库GDB会自动为每一个属性fistName、lastName、creationDate创建索引数据。这样查询firstName为Foo的人时,查询语句会自动利用对应的索引来获取结果,避免遍历操作:

g.V().hasLabel('Person').has('firstName', 'Foo')
# 下面的这些查询也会利用自动索引
g.V().hasLabel('Person').has('firstName', TextP.startingWith('Fo'))
g.V().hasLabel('Person').has('creationDate', gte(20200101))

如果需要根据fistName、lastName来进行查找:

g.V().hasLabel('Person').has('firstName', 'Foo').has('lastName', 'Bar')

图数据库GDB会根据内部统计信息来决定使用firstName索引还是lastName索引。但是,如果图数据库中firstName为Foo以及lastName为Bar的节点数据都非常多时,通过自动索引扫描出来的数据也会非常多,这个时候就需要手动创建复合索引了。

复合索引

上面一节提到了同时对firstName和lastName进行过滤的查询需要创建复合索引,在阿里云图数据库GDB中,我们可以方便地使用Gremlin DSL来进行添加索引的操作:

gremlin> g.withSideEffect("GDB#createVertexLabelIndex", "{label: 'Person', properties: ['firstName', 'lastName'], unique: false}").inject(1)
==>GraphDbIndex
Index created, id: 1

创建之后可以通过Gremlin DSL查询对应的索引的状态:

gremlin> g.withSideEffect("GDB#queryIndex", "all").inject(1)
==>GraphDbIndex
Id: 1, Desc: {  type: 0,  label: Person,  property_list: [ firstName, lastName ], unique: false }, Status: Installed

当索引状态从Populating变为Installed之后,对应的索引就能够被查询语句利用到了。

点中心索引

点中心索引是针对“一跳场景”过滤非常实用的一种索引类型。下图是社交网络场景的例子,Vertex是注册用户,Edge是关注关系,Edge上有一个属性since代表关注时间。

GDB索引:更快的速度探索互联数据的奥秘

假设一个大V有100w+的粉丝,现在需要找出大V的粉丝中“关注时间在10年以上的”,在没有索引可以利用的时候,我们需要遍历100w+的粉丝进行过滤。这时候就会有一个问题:能否针对这个大V的100w+粉丝进行排序,按照since来进行排序 ,这样这不需要进行全量的扫描了。这个问题的答案就是阿里云图数据库GDB的点中心索引。

下面还是使用LDBC SNB的数据为例来说明点中心索引如何创建和使用,LDBC SNB的数据模型中Person节点会创建到其它Person节点数的边,类型为knows,边上有属性creationDate代表开始时间。现在需要查询某一个Person节点最近认识的10个人,返回他们之间的边和对应的creationDate,对应的查询语句如下:

g.V('p4398046512014').bothE('knows').
order().by('creationDate', desc).
project('relation', 'date').
by(identity()).
by(values('creationDate')).
limit(10)

基于这里的数据看下上面这条Gremlin DSL运行的时间:

gremlin> g.V('p4398046512014').bothE('knows').
......1> order().by('creationDate', desc).
......2> project('relation', 'date').
......3> by(identity()).
......4> by(values('creationDate')).
......5> limit(10).profile()
==>Traversal Metrics
Step                                                               Count  Traversers       Time (ms)    % Dur
=============================================================================================================
GraphDbGraphStep(vertex,[p4398046512014])                              1           1           0.263     1.61
VertexStep(BOTH,[knows],edge)                                        426         426           4.964    30.34
OrderGlobalStep([[value(creationDate), desc]])                        11          11          10.593    64.74
RangeGlobalStep(0,10)                                                 10          10           0.032     0.20
ProjectStep([relation, date],[[IdentityStep, Pr...                    10          10           0.509     3.11
  IdentityStep                                                        10          10           0.007
  PropertiesStep([creationDate],value)                                10          10           0.277
                                            >TOTAL                     -           -          16.362        -

现在对knows类型的边加上点中心索引,操作方式和上面添加复合索引的方式类似:

gremlin> g.withSideEffect("GDB#createVertexCentricIndex", "{label: 'knows', properties: ['creationDate'], direction: 'BOTH'}").inject(1)
==>GraphDbIndex
Index created, id: 2

同样的语句再来看一下运行时间:

gremlin> g.V('p4398046512014').bothE('knows').
......1> order().by('creationDate', desc).
......2> project('relation', 'date').
......3> by(identity()).
......4> by(values('creationDate')).
......5> limit(10).profile()
==>Traversal Metrics
Step                                                               Count  Traversers       Time (ms)    % Dur
=============================================================================================================
GraphDbVertexCentricStep(edge,[java.lang.Object...                    11          11           2.854    78.41
RangeGlobalStep(0,10)                                                 10          10           0.047     1.29
ProjectStep([relation, date],[[IdentityStep, Pr...                    10          10           0.739    20.30
  IdentityStep                                                        10          10           0.030
  PropertiesStep([creationDate],value)                                10          10           0.442
                                            >TOTAL                     -           -           3.640        -

对于这个测试数据集,同样查询语句的延迟从16.3ms缩短到了3.64ms,从Profile的结果可以看到添加了点中心索引,GDB增加了定制过的GraphDbVertexCentricStep。当然,这个测试数据集中对应的点只有426条边,对于真实业务而言,添加合适的点中心索引可能会带来数十倍的性能提升。

结束语

阿里云图数据库GDB支持的自动索引,可以满足大部分业务场景的查询需求,很大程度上节省了用户接入的时间,同时用户可以根据查询情况定制更多的索引来提升特定场景的性能。图数据库的场景中还可以衍生出更多有意思的索引类型,比如子图匹配场景的索引、针对节点可达性查询的索引等,让用户以更快的速度探索互联数据的奥秘。

引用

上一篇:Neo4j数据模型设计


下一篇:Neo4j导入Aminer论文数据