Neo4j统计数据存储

以下内容参考Neo4j Community Edition 3.5.3
最早注意到Neo4jCount Store是试图分析Neo4j的数据文件的时候。

Neo4j < Graph Databases > 一书中介绍自己的存储是Native Graph Storage。其中一点特性是Node/Relationship/Property记录都是定长存储,可以直接通过id来查找到相应的记录,所以很多存储文件都伴随着一个以".id"为后缀的文件。

1
2
3
4
5
6
7
8
9
10
11
neostore
neostore.counts.db.a
neostore.id
neostore.labelscanstore.db
neostore.labeltokenstore.db
neostore.labeltokenstore.db.id
neostore.labeltokenstore.db.names
neostore.labeltokenstore.db.names.id
neostore.nodestore.db
neostore.nodestore.db.id
...

上面的列表中有一个文件比较特殊:"neostore.counts.db.a",这就是接下来要介绍的Neo4j Count Store

作用

Neo4j Count Store有两点用途:满足特定的查询、供执行计划选择器来调用。

满足特定查询

Neo4j Browser上可以显示Node/Relationship的总数,这类查询能容易通过Count Store来满足。

1
2
3
4
5
// 查询Node的总数
MATCH (n) RETURN COUNT(n)
// 查询Relationship的总数
MATCH (n)-[r]->() RETURN COUNT(r)

供执行计划选择器来调用

Neo4j对于用户输入的Cypher会生成执行计划,这个时候也会参考某种Label的Node总数,来选择不同的执行计划。

原理

更新

执行下面这条Cypher语句:

1
2
3
4
CREATE (a:Person { name: 'mobing' })
CREATE (b:Car { name: 'golf' })
CREATE (a)-[r:OWN]->(b)
RETURN a, r, b

会在KernelTransactionImplementation.commit中调用RecordStorageEngine.createCommands生成所有的Command,其中就会包含对Count Store操作的9条 Command

上面是逻辑上的示例,在Neo4j中,所有的Node Label / Relationship Type / Property Type都是使用固定的id来表示的,下面是Neo4j中实际使用的Command

Rotation

RotationNeo4j Count Store里面的比较有意思的机制。Count Store在代码上的实现是CountsTracker这个类。这个类的Javadoc里面有这么一句话:

The counts store is a key/value store, where key/value entries are stored sorted by the key in ascending unsigned (big endian) order. These store files are immutable, and on store-flush the implementation swaps the read and write file in a {@linkplain Rotation.Strategy#LEFT_RIGHT left/right pattern}.

Node/Relationship/Property的存储方式不同,Count Store使用KV存储,存储文件是immutable的,写入是先Apply到内存当中的结构。那么存储文件是如何进行更新的呢。

开头提到的neostore.counts.db.a就是Count Store使用的存储文件,执行完Rotation之后,我们会看到一个新的文件:neostore.counts.db.b。每个文件中会记录当前这个文件数据中包含的最大的事务Id,这样在做恢复的时候,只需要把比这个事务id要新的事务产生的更改Apply到内存中即可。

整个Rotation的过程如下:

  • 在没有进行Rotation时,Count Store是两层的结构,内存中ConcurrentMap类型的changes和磁盘上KeyValueStoreFile类型的store,读取时会先读changes,如果没有,会从KeyValueStoreFile中把相应的数据加载到changes中;写入只会写入到changes中。changes相当于store的写回策略的读写cache。
  • 在进行Rotation时,原来的整个state会作为preState,然后会新生成一个postStatepostStatestore是指向preState的,相当于一个三层的结构。开始Rotation的时候会获取当前的已经提交的最大的commitId,在真正执行Rotation之前,需要等到这个提交的commitId执行apply(在中间阶段是会做双写的,即同时写preStatepostState)。在这之后,就可以开心地执行Rotation了,注意,整个流程都不会阻塞正常的读写哦。Rotation就是把上图中neostore.counts.db.a和它对应的changes中所有的记录都写入到neostore.counts.db.b中。
  • 所有记录写入到neostore.counts.db.b中之后,就可以把前面的postState中的changesneostore.counts.db.b一起组成新的两层结构。

参考资料