在 Neo4j 中创建地段关系时如何提高性能
How to increase performance when creating lots relations in neo4j
我正在研究使用 neo4j graph database in combination with spatie crawler.
分析网站内部 link 结构的爬虫
想法是这样的:
每当抓取 URL 时,将从 DOM 中提取所有链接。对于所有 link,将创建一个节点并添加关系 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)');
我考虑过在单个查询中创建它们而不是循环所有找到的 link,但我只找到了关于如何 create multiple nodes 的文档 - 但没有关于如何创建多个关系的文档。
我也考虑过先把所有东西都存储在另一个store中,然后将这个store批量导入到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
cypher 总体上看起来很慢。但我无法想象与 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})
之前 运行 程序定义索引(或唯一性约束)。
如果仍然太慢,那么您确实需要按批插入关系,每批 1 个查询(使用 UNWIND
调整上述查询)。您将需要试验各种批量大小以确定最佳折衷方案,即导入时间和内存消耗(小批量 => 更少内存 => 总体导入时间更长)。
旁注:标签使用 PascalCase
而不是 UPPER_CASE
,所以 URL
应该写成 Url
(尽管首字母缩略词的界限很模糊)
我正在研究使用 neo4j graph database in combination with spatie crawler.
分析网站内部 link 结构的爬虫想法是这样的:
每当抓取 URL 时,将从 DOM 中提取所有链接。对于所有 link,将创建一个节点并添加关系 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)');
我考虑过在单个查询中创建它们而不是循环所有找到的 link,但我只找到了关于如何 create multiple nodes 的文档 - 但没有关于如何创建多个关系的文档。
我也考虑过先把所有东西都存储在另一个store中,然后将这个store批量导入到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
cypher 总体上看起来很慢。但我无法想象与 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})
之前 运行 程序定义索引(或唯一性约束)。
如果仍然太慢,那么您确实需要按批插入关系,每批 1 个查询(使用 UNWIND
调整上述查询)。您将需要试验各种批量大小以确定最佳折衷方案,即导入时间和内存消耗(小批量 => 更少内存 => 总体导入时间更长)。
旁注:标签使用 PascalCase
而不是 UPPER_CASE
,所以 URL
应该写成 Url
(尽管首字母缩略词的界限很模糊)