【问题标题】:How to increase performance when creating lots relations in neo4j在 Neo4j 中创建地块关系时如何提高性能
【发布时间】:2021-08-18 11:48:34
【问题描述】:

我正在开发一个爬虫来分析网站的内部链接结构,使用neo4j graph database 结合spatie crawler

这个想法是这样的:

每当抓取一个 URL 时,所有的链接都会从 DOM 中提取出来。对于所有链接,将创建一个节点并添加一个关系foundOn->target

// UrlCrawledListener.php

public function handle($event) 
{
//...
    // Extract all links on the page
    $linksOnPage = collect((new DomCrawlerService())->extractLinksFromHtml($event->getResponse()->getBody(), $event->getUrl()));
    // For all links, create nodes and add relation
    $linksOnPage->each(fn(Link $link) => $neo4jService->link($link, $event->getUrl()));
//...
}

// Neo4JService.php

public function link(Link $link, UriInterface $foundOnUrl): void
{
    $targetUrl = new Uri($link->getUri());

    if (!$this->doesNodeExist($targetUrl)) {
        $this->createNode($targetUrl);
    }

    if (!$this->doesNodeExist($foundOnUrl)) {
        $this->createNode($foundOnUrl);
    }

    // When this method call is disabled, the crawler is A LOT faster
    $this->createRelation($foundOnUrl, $targetUrl);
}

// ...

protected function createNode(UriInterface $uri): void
{
    // Todo: Add atttributes
    $this->runStatement(
        'USE ' . $this->getDB() . ' CREATE (n:URL {url: $url, url_hash: $hash})',
        [
            'url'  => $uri->__toString(),
            'hash' => CrawlUrl::getUrlHash($uri),
        ]
    );
}

// ...

protected function createRelation(UriInterface $from, UriInterface $to): void
{
    $this->runStatement(
        '
             USE ' . $this->getDB() . '
             MATCH (a:URL), (b:URL)
             WHERE a.url_hash = $fromURL AND b.url_hash = $toURL
             CREATE (a)-[rel:Link]->(b)
             ',
        [
            'fromURL' => CrawlUrl::getUrlHash($from),
            'toURL'   => CrawlUrl::getUrlHash($to),
        ]
    );
}


我尝试了什么:

我尝试向节点添加索引以提高 MATCH 查询的性能,但这并没有产生明显的影响:

$this->runStatement('USE ' . $this->getDB() . ' CREATE INDEX url_hash_index FOR (n:URL) ON (n.url_hash)');

我曾考虑在单个查询中创建它们,而不是循环所有找到的链接,但我只找到有关如何create multiple nodes 的文档 - 但没有找到有关如何创建多个关系的文档。

我还考虑过先将所有内容存储在另一个商店中,然后将该商店批量导入 neo4j。但是,documentation on csv imports 使用完全相同的逻辑来创建关系,所以这也无济于事:

// create relationships
LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
MATCH (e:Employee {employeeId: row.employeeId})
MATCH (c:Company {companyId: row.Company})
MERGE (e)-[:WORKS_FOR]->(c)

我已经将 ->doesNodeExist() 逻辑转移到 SQL,因为这会导致同样的问题。 MATCH 密码总体上似乎非常慢。但我无法想象与 SQL 数据库相比,针对几百个节点进行匹配会那么慢。

您对如何改进算法本身或 neo4j 数据库结构或密码查询以提高性能有任何建议吗?

【问题讨论】:

    标签: php neo4j cypher


    【解决方案1】:

    你应该替换:

        $targetUrl = new Uri($link->getUri());
    
        if (!$this->nodeExist($targetUrl)) {
            $this->createNode($targetUrl);
        }
    
        if (!$this->nodeExist($foundOnUrl)) {
            $this->createNode($foundOnUrl);
        }
    
        $this->createRelation($foundOnUrl, $targetUrl);
    

    类似:

    $this->runStatement('
        USE ' . $this->getDB() . '
        MERGE (a:URL {url_hash: $fromURL})
        MERGE (b:URL {url_hash: $toURL})
        CREATE (a)-[rel:Link]->(b)',
        [
            'fromURL' => CrawlUrl::getUrlHash($from),
            'toURL'   => CrawlUrl::getUrlHash($to),
        ]
    );
    

    确保在运行程序之前(:URL {url_hash})上定义索引(或唯一性约束)。

    如果仍然太慢,那么您确实需要按批次插入关系,每批次 1 个查询(使用 UNWIND 调整上述查询)。您需要试验各种批量大小,以确定在导入时间和内存消耗方面的最佳折衷方案(小批量 => 更少内存 => 总体上更长的导入时间)。

    旁注:标签使用PascalCase 而不是UPPER_CASE,因此URL 应写为Url(尽管在首字母缩略词方面这条线很模糊)

    【讨论】:

    • 谢谢!不幸的是,前 10-20 个节点仍然完成得相当快,但随后它会急剧减慢到一切都超时的地步。我会试试UNWIND
    • 展开到您提供的MERGE 就可以了。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多