提高速度建议Neo4j

时间:2016-05-06 13:45:12

标签: php neo4j recommendation-engine graphaware

我正在尝试使用Neo4j和Reco4PHP创建一个简单的推荐引擎。

数据模型由以下节点和关系组成:

  

(用户) - [:HAS_BOUGHT] - >(产品{category_id:int}   ) - [:DESIGNED_BY] - GT;(设计器)

在这个系统中,我想推荐使用与用户已经购买的设计器相同的产品和产品。为了创建推荐,我使用了一个Discovery类和一个Post-Processor类来增强产品。见下文。这有效,但速度很慢。完成需要5秒以上,而数据模型可容纳约1000种产品和约100名设计师。

// Disovery class
    <?php
namespace App\Reco4PHP\Discovery;
use GraphAware\Common\Cypher\Statement;
use GraphAware\Common\Type\NodeInterface;
use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine;

class InCategory extends SingleDiscoveryEngine {

    protected $categoryId;

    public function __construct($categoryId) {
        $this->categoryId = $categoryId;
    }

    /**
     * @return string The name of the discovery engine
     */
    public function name() {
        return 'in_category';
    }

    /**
     * The statement to be executed for finding items to be recommended
     *
     * @param \GraphAware\Common\Type\NodeInterface $input
     * @return \GraphAware\Common\Cypher\Statement
     */
    public function discoveryQuery(NodeInterface $input) {

        $query = "
            MATCH (reco:Card)
            WHERE reco.category_id = {category_id}
            RETURN reco, 1 as score
        ";

        return Statement::create($query, ['category_id' => $this->categoryId]);
    }
}

// Boost shared designers
class RewardSharedDesigners extends RecommendationSetPostProcessor {

    public function buildQuery(NodeInterface $input, Recommendations $recommendations)
    {
        $ids = [];
        foreach ($recommendations->getItems() as $recommendation) {
            $ids[] = $recommendation->item()->identity();
        }

        $query = 'UNWIND {ids} as id
        MATCH (reco) WHERE id(reco) = id
        MATCH (user:User) WHERE id(user) = {userId}
        MATCH (user)-[:HAS_BOUGHT]->(product:Product)-[:DESIGNED_BY]->()<-[:DESIGNED_BY]-(reco)

        RETURN id, count(product) as sharedDesignedBy';

        return Statement::create($query, ['ids' => $ids, 'userId' => $input->identity()]);
    }

    public function postProcess(Node $input, Recommendation $recommendation, Record $record) {
        $recommendation->addScore($this->name(), new SingleScore((int)$record->get('sharedDesignedBy')));
    }

    public function name() {
        return 'reward_shared_designers';
    }
}

我很高兴它有效,但如果计算时间超过5秒,则无法在生产环境中使用。

为了提高我的速度:

  • 在Product:id和Designer中创建索引:id
  • node_auto_indexing = true 添加到neo4j.properties。
  • -Xmx4096m 添加到.neo4j-community.vmoptions 但它并没有真正有所作为。

这些Cypher查询通常需要5秒以上或者是否可以进行一些改进? :)

2 个答案:

答案 0 :(得分:2)

主要问题在于您的后期处理器查询。目标是:

  

根据我购买的产品数量提出建议   设计师设计推荐的项目。

因此,您可以修改您的查询以直接匹配设计器并在其上进行聚合,最好先在MATCH (user) WHERE id(user) = {userId} UNWIND {ids} as productId MATCH (product:Product)-[:DESIGNED_BY]->(designer) WHERE id(product) = productId WITH productId, designer, user MATCH (user)-[:BOUGHT]->(p)-[:DESIGNED_BY]->(designer) RETURN productId as id, count(*) as score 之前找到用户,否则它将在产品的每次迭代中与用户匹配ids:

    public function buildQuery(NodeInterface $input, Recommendations $recommendations)
    {
        $ids = [];
        foreach ($recommendations->getItems() as $recommendation) {
            $ids[] = $recommendation->item()->identity();
        }

        $query = 'MATCH (user) WHERE id(user) = {userId}
        UNWIND {ids} as productId
        MATCH (product:Product)-[:DESIGNED_BY]->(designer)
        WHERE id(product) = productId
        WITH productId, designer, user
        MATCH (user)-[:BOUGHT]->(p)-[:DESIGNED_BY]->(designer)
        RETURN productId as id, count(*) as score';

        return Statement::create($query, ['userId' => $input->identity(), 'ids' => $ids]);
    }

    public function postProcess(Node $input, Recommendation $recommendation, Record $record)
    {
        $recommendation->addScore($this->name(), new SingleScore($record->get('score')));
    }

完整的后处理器如下所示:

MATCH (user) WHERE id(user) = {userId}
UNWIND {ids} as cardId
MATCH (reco:Card)-[:DESIGNED_BY]->(designer) WHERE id(reco) = cardId
MATCH (user)-[:HAS_BOUGHT]->(x)
WHERE (x)-[:DESIGNED_BY]->(designer)
RETURN cardId as id, count(*) as sharedDesignedBy

我创建了一个存储库,我的域名后面有一个功能齐全的实现:

https://github.com/ikwattro/reco4php-example-so

收到数据后更新

enter image description here

在产品和用户之间存在多个相同类型的关系这一事实是为找到的模式数添加了指数。

有两种解决方案:

区分它们并为模式的结尾添加WHERE子句:

USING JOIN

在Neo4j 3.0+中,您可以从MATCH (user) WHERE user.id = 245 UNWIND ids as id MATCH (reco:Card) WHERE id(reco) = id MATCH (user:User)-[:HAS_BOUGHT]->(card:Card)-[:DESIGNED_BY]->(designer:Designer)<-[:DESIGNED_BY]-(reco:Card) USING JOIN ON card RETURN id, count(card) as sharedDesignedBy 用法中受益,并保持与您相同的查询:

discovery

运行这些查询后,我使用您当前的数据集将post processing + <?xml version="1.0" encoding="utf-8"?> <resources> one two three four five six seven eight nine ten </resources> 的时间缩短到了190毫秒。

答案 1 :(得分:0)

我只能对Cypher发表评论,因为你没有包含函数GetItems()或数据(cypher dump)。 但很少有事情突出

  1. 在(重新)上使用标签会更快我认为它是产品吗?
  2. 另外我认为这是可以放入的设计师标签 - [:DESIGNED_BY] - &gt;()&lt; - [:DESIGNED_BY]?
  3. 如果GetItems()任何机会逐个检索项目,那可能 是问题,也是索引所需的地方。顺便说一下为什么不把这个条件放在主查询中?
  4. 我也不懂id上的索引?如果它们是Neo4j id,它们是物理位置,不需要编入索引,如果它们不是你使用id()函数的原因?

    总之,标签可能有所帮助,但如果您的数据集很大,则不要指望奇迹,Neo4j上的聚合速度不是很快。计算没有过滤器的10M记录花了我12秒。