如何在neo4j中创建大量关系时提高性能



我正在做一个爬虫来分析网站的内部链接结构,使用neo4j图形数据库结合空间爬虫。

这个想法是这样的:

无论何时抓取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),
]
);
}

What I tried:

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

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

我想在单个查询中创建它们,而不是循环所有找到的链接,但我只找到了关于如何创建多个节点的文档-但没有关于如何创建多个关系的文档。

我还考虑过首先将所有内容存储在另一个存储中,然后将该存储批量导入neo4j。但是,关于csv导入的文档使用完全相同的逻辑来创建关系,因此这也没有帮助:

// 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数据库结构或密码查询以提高性能有任何建议吗?

你应该替换:

$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})上定义索引(或唯一性约束)运行程序

如果它仍然太慢,那么你确实需要按批插入关系,每批插入一个查询(用UNWIND调整上面的查询)。您需要尝试不同的批处理大小,以确定在导入时间和内存消耗方面的最佳折衷方案(小批处理=>内存更少=>整体较长的导入时间)

旁注:标签使用PascalCase而不是UPPER_CASE,所以URL应该写成Url(尽管这条线在缩略词方面是模糊的)

最新更新