对于数据库的使用者而言,索引是一个非常熟悉的概念。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的自动索引是如何工作的。
从模型中可以看出,对于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代表关注时间。
假设一个大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支持的自动索引,可以满足大部分业务场景的查询需求,很大程度上节省了用户接入的时间,同时用户可以根据查询情况定制更多的索引来提升特定场景的性能。图数据库的场景中还可以衍生出更多有意思的索引类型,比如子图匹配场景的索引、针对节点可达性查询的索引等,让用户以更快的速度探索互联数据的奥秘。