gremlin-driver,我们可以实现各类 自定义的strategy,可以对我们的 gremlin query 语句进行优化;
比如,在baidu-hugegraph 里面,我们可以对多个 has 进行优化;
例如: 我们的查询:
g.V().limit(1).out().has('name','xx').has('eid','xx')
如果不做优化,我们可能就是需要 两次has查询数据库;
这个是最原始解析:
[HugeGraphStep(vertex,[]), RangeGlobalStep(0,1), HugeVertexStep(OUT,vertex), NoOpBarrierStep(2500), HasStep([name.eq(xx), eid.eq(xx)])]
百度的优化:
public static void extractHasContainer(HugeGraphStep<?, ?> newStep,
Traversal.Admin<?, ?> traversal) {
Step<?, ?> step = newStep;
do {
step = step.getNextStep();
if (step instanceof HasStep) {
HasContainerHolder holder = (HasContainerHolder) step;
for (HasContainer has : holder.getHasContainers()) {
if (!GraphStep.processHasContainerIds(newStep, has)) {
newStep.addHasContainer(has);
}
}
TraversalHelper.copyLabels(step, step.getPreviousStep(), false);
traversal.removeStep(step);
}
} while (step instanceof HasStep || step instanceof NoOpBarrierStep);
}
可以把HasStep 给去掉,然后合并到 一个 VertexStep 中;
[HugeGraphStep(vertex,[]), RangeGlobalStep(0,1), HugeVertexStep(OUT,Vertex,[name.eq(xx), eid.eq(xx)]), NoOpBarrierStep(2500)]
那么在 HugeVertexStep的查询中,就可以 以一个 复合索引方式来查询了;对性能有一定的提升;
我们再看看,hugegraph 里面对其他两个步骤的重构;
for (GraphStep originStep : steps) {
HugeGraphStep<?, ?> newStep = new HugeGraphStep<>(originStep);
TraversalHelper.replaceStep(originStep, newStep, traversal);
TraversalUtil.extractHasContainer(newStep, traversal);
// TODO: support order-by optimize
// TraversalUtil.extractOrder(newStep, traversal);
TraversalUtil.extractRange(newStep, traversal, false);
TraversalUtil.extractCount(newStep, traversal);
}
rangeSetp:
g.V().range(5,10)
编译之后变成了range(0,5),
/*
- NOTE: keep the step to limit results after query from DB
- due to
limit
(in DB) may not be implemented accurately. - but the backend driver should ensure
offset
accurately.
*/
5作为offset,放在了 g.V(),hugegraphGraphSetp 上。
也就是 由 不同的底层存储,管理offset; 然后在内存里面实现 range limit;
这里的优化是正确性保证, offset 一般都是正确实现。
我们再看看,extractCount() 这个功能的优化;
public static void extractCount(Step<?, ?> newStep,
Traversal.Admin<?, ?> traversal) {
Step<?, ?> step = newStep;
do {
step = step.getNextStep();
if (step instanceof CountGlobalStep) {
QueryHolder holder = (QueryHolder) newStep;
holder.setCount();
}
} while (step instanceof CountGlobalStep ||
step instanceof FilterStep ||
step instanceof IdentityStep ||
step instanceof NoOpBarrierStep);
}
主要是把相应的信息回写到 QueryHolder里面;注意 每个 VertexStep/GraphStep 在Hugegraph里面都实现了 queryHolder接口;
最后看一下, tinkpop作者提供的一个例子: Then check this cool strategy out: g.V().count() is O(1) time for TinkerGraph:
对于优化的地方,需要结合后端实现的backend,如果百度只专注于做一个存储后端,那么他的性能应该可以更好,这也正是我们优化的地方;
进一步的优化
- count的优化,根据不同的backend使用不同的 countStep,有些数据库是支持直接查询count的
- out().out(),这样的step的优化; 可以使用 KoutStep来做; 在查询数据库的时候可以使用batch-get,并发去查询;
我们看到 hugegraph 在提供的算法包里面,包含了kout,但是依旧是使用的是 单步的邻居节点查询;应该也是考虑到了通用性的考虑; - 其他耗时的step我们都可以做 定制化的step 优化;但是会牺牲通用性;我觉得nebula 之所有快,应该是做了很多优化;针对特型的存储,使用 并行+ 下推 等方式;
网友评论