Nodejs neo4j-driver Попытка прочитать и вернуть набор результатов из более чем 4 млн записей - PullRequest
0 голосов
/ 01 августа 2020

[ОБНОВЛЕНИЕ] У меня здесь было несколько хороших рекомендаций на форуме сообщества Neo4j: https://community.neo4j.com/t/nodejs-neo4j-driver-trying-to-read-and-return-result-set-of-over-4m-records/22705/13

Предложение 1) Выполняйте поиск только по кампаниям с идентификатором отрасли - поэтому добавьте метка для узлов Campaign с идентификатором отраслевого сектора HasIndustrySector.

Предложение 2) Разделите запрос на две части: 1-я часть, захватите все кампании с отраслевым сектором. 2-я часть, объедините взаимосвязи, которые необязательно будут искать вместе, как таковые:

MATCH (c:Campaign)
WHERE c.industrySectorId IS NOT NULL
RETURN id(c) as campaignId

MATCH (c)-[r:SENT_EMAIL|OPENED|CLICKED]-(p)
WHERE id(c) = $campaignId
WITH c, type(r) as type, count(distinct p) as count LIMIT 1000
WITH c, {type: type, count: count} as data
WITH c, collect(data) as totals
RETURN c.campaignId AS campaignId, c.industrySectorId AS industrySectorId, c.senddate AS sendDate, c.subjectLine AS subject, c.preHeader AS preHeader, totals

Это фактически привело к завершению запроса. Результат профиля по этому запросу:

134178426 общее количество обращений к базе данных за 5 мс (в нем указано 5 мсек, но определенно потребовалось около 50+ секунд для фактического завершения и возврата результата профиля).

введите описание изображения здесь

[ОРИГИНАЛЬНЫЙ ВОПРОС]

У нас есть новый сервер, на котором запущен выделенный корпоративный Neo4j.

Недавно импортированные данные из базы данных MS SQL, производящей около 100 м узлов и больше количество связей.

Я подключил neo4j-драйвер для nodejs штраф и успешно выполнил много запросов.

Однако я нахожусь в точке, где мне нужно читать / передавать около 4 миллионов записей из конкретной метки узла.

Я использовал как сеанс, так и реактивный сеанс, чтобы попытаться сделать это, но результаты никогда не появляются, даже если я оставляю его на несколько часов. Однако, если я помещаю в запрос предложение LIMIT, я получаю хорошие результаты для всего до 500 000. Однако при превышении этого ответа просто нет никакого ответа, а также я ничего не вижу в журналах.

Вот запрос:

cypher runtime=slotted 
MATCH (c:Campaign)
OPTIONAL MATCH (c)<-[:HAS]-(i:IndustrySector)
WITH c,i
OPTIONAL MATCH (c {campaignId: c.campaignId})-[sent:SENT_EMAIL]->(sp:Person)
OPTIONAL MATCH (c {campaignId: c.campaignId})<-[opened:OPENED]-(op:Person)
OPTIONAL MATCH (c {campaignId: c.campaignId})<-[clicked:CLICKED]-(cp:Person)
WITH c, i, COUNT(DISTINCT(sp)) AS totalSent, COUNT(DISTINCT(op)) AS totalOpened, COUNT(DISTINCT(cp)) AS totalClicked
RETURN c.campaignId AS campaignId, i.industrySectorId AS industrySectorId, c.senddate AS sendDate, c.subjectLine AS subject, c.preHeader AS preHeader, totalSent, totalOpened, totalClicked

Как видите, у меня установите время выполнения шифра для использования слотами. Я пробовал без этого, но похоже, что он отправляет все результаты обратно в одном пакете, а не по очереди.

Вот две версии, которые я пробовал, первая - нормальный сеанс. Во втором случае используется реактивный сеанс:

// Normal Session
session.run(query)
.subscribe({
onKeys: keys => {
},
onNext: record => {
},
onCompleted: () => {
},
onError: error => {
}});

// Reactive Session
rxSession.run(query)
.records()
.pipe(map(record => record.toObject()))
.subscribe({
...same as above
})

Еще я пробовал использовать SKIP и LIMIT для запроса, обрабатывая его партиями по 10 тыс. Записей. Это отлично работает, пока не достигнет 520 000 записей, а затем снова зависнет.

Был бы очень благодарен, если бы кто-нибудь мог указать мне, где я ошибаюсь, или лучший способ достичь того, чего я хочу. Я просмотрел библиотеку apo c, такую ​​как apo c .periodi c .iterate, однако это применимо только для выполнения операций записи.

Спасибо за любую помощь.

1 Ответ

1 голос
/ 06 августа 2020

Некоторые наблюдения:

  1. Вы не должны использовать DISTINCT в COUNT(DISTINCT x) без необходимости, так как это заставляет запрос накапливать в памяти набор всех отдельных значения, найденные на данный момент, и для проверки каждого нового значения по этому списку, чтобы он мог дать вам точный счет. В вашем запросе значения sp, op и cp в любом случае должны быть естественно различны (при условии, что у вас никогда не бывает нескольких отношений одного типа между одной и той же парой узлов). Итак, если бы вы просто использовали COUNT(x) вместо этого, запрос мог бы просто увеличить счетчик, чтобы получить каждый счет. Однако в приведенном ниже запросе вовсе не обязательно использовать COUNT.
  2. По возможности следует избегать обхода отношений или получения узлов, если это не нужно. В вашем случае вы могли бы получить количество очень быстро , используя шаблон Cypher, например SIZE((x)-[:FOO]->()), который использует внутреннюю операцию GetDegree () .
  3. The industrySectorId значение полностью не зависит от количества единиц. Таким образом, вам не следует прилагать усилия, чтобы получить все значения для каждого industrySectorId, так как количество никогда не изменится между industrySectorId s. Приведенный ниже запрос просто собирает все значения industrySectorId в списке и подсчитывает каждое только один раз.
  4. (c {campaignId: c.campaignId}) излишне мучительно. Это должно было быть (c).

Этот запрос должен быть намного быстрее:

MATCH (c:Campaign)
RETURN
  c.campaignId AS campaignId,
  c.senddate AS sendDate, c.subjectLine AS subject, c.preHeader AS preHeader,
  [(c)<-[:HAS]-(i:IndustrySector) | i.industrySectorId] AS industrySectorIds,
  SIZE((c)-[:SENT_EMAIL]->()) AS totalSent,
  SIZE((c)<-[:OPENED]-()) AS totalOpened,
  SIZE((c)<-[:CLICKED]-()) AS totalClicked

Он использует быстрые проверки GetDegree () для получения подсчитывает без фактического прохождения каких-либо отношений или получения каких-либо узлов на другом конце. Он также просто помещает все значения industrySectorId в один industrySectorIds. Таким образом, каждая строка результатов содержит все требуемые данные для каждого Campaign.

[ОБНОВЛЕНИЕ]

Поскольку вы указали, что действительно может быть несколько отношений одного типа между Campaign и те же Person, # 1 и # 2, указанные выше, к вам не относятся.

Следующий запрос должен быть быстрее, чем ваш запрос, поскольку он использует # 3 (чтобы избежать повторения одного и того же набора дорогостоящих запросов на IndustrySector) и # 4, и выполняет MATCH/COUNT шаги по одному, чтобы избежать декартовых произведений:

MATCH (c:Campaign)
OPTIONAL MATCH (c)-[:SENT_EMAIL]->(sp:Person)
WITH c, COUNT(DISTINCT sp) AS totalSent
OPTIONAL MATCH (c)<-[:OPENED]-(op:Person)
WITH c, totalSent, COUNT(DISTINCT op) AS totalOpened
OPTIONAL MATCH (c)<-[clicked:CLICKED]-(cp:Person)
WITH c, totalSent, totalOpened, COUNT(DISTINCT cp) AS totalClicked
RETURN
  c.campaignId AS campaignId,
  c.senddate AS sendDate, c.subjectLine AS subject, c.preHeader AS preHeader,
  [(c)<-[:HAS]-(i:IndustrySector) | i.industrySectorId] AS industrySectorIds,
  totalSent,
  totalOpened,
  totalClicked
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...