Проблемы с производительностью с neo4j - PullRequest
0 голосов
/ 05 июля 2019

На виртуальной машине моего ноутбука есть DataSet:

2 миллиона уникальных покупателей [: VISITED] 40000 уникальных торговцев. Каждый [: VISIT] имеет свойства: количество (двойное) и dt (дата). У каждого клиента есть свойство «pty_id» (целое число). И у каждого Продавца есть свойство mcht_id (String).

Один Клиент может посетить одного Продавца более одного раза. И, конечно же, один клиент может посетить множество продавцов. Таким образом, на моем графике 43 978 539 отношений между покупателями и продавцами.

Я создал индексы:

CREATE INDEX on :Customer(pty_id)
CREATE INDEX  on :Merchant(mcht_id)

Параметры моей ВМ:

Oracle (RedHat) Linux 7 с 2-ядерным i7, 2 ГБ ОЗУ

Параметры моей конфигурации Neo4j 3.5.7:

- dbms.memory.heap.max_size=1024m
- dbms.memory.pagecache.size=512m  

Моя задача:

Получить 10 лучших клиентов, заказанных total_amount, которые потратили свои деньги у НЕ указанного торговца (M), но посетили тех торговцев, которые посещали клиенты, которые посещают этого указанного продавца (M)

Мое решение:

Let’s M will have mcht_id = "0000000DA5"

Тогда запрос CYPHER будет:

MATCH 
  (c:Customer)-[r:VISITED]->(mm:Merchant)<-[:VISITED]-(cc:Customer)-[:VISITED]->(m:Merchant {mcht_id: "0000000DA5"}) 
WHERE 
  NOT (c)-[:VISITED]->(m) 
WITH 
  DISTINCT c as uc 
MATCH 
  (uc:Customer)-[rr:VISITED]->()
RETURN 
  uc.pty_id
  ,round(100*sum(rr.amount))/100 as v_amt 
ORDER BY v_amt DESC
LIMIT 10;

Результат в порядке. Я получаю ответ:

uc.pty_id - v_amt: 1433798 - 348925,94; 739510 - 339169,83; 374933 - 327962,95 и т. Д.

Проблема в том, что этот результат я получил после 437613 мс! Это около 7 минут! Мое предполагаемое время для этого запроса было около 10-20 секунд….

Мой вопрос: что я делаю неправильно ???

Ответы [ 2 ]

0 голосов
/ 10 июля 2019

Я протестировал все оптимизированные запросы на Neo4j и Oracle. Результаты:

Oracle - 2,197 с Neo4j - 5,326 с

Подробности можно посмотреть здесь: http://homme.io/41163#run

А для Neo4j есть больше соответствий на http://homme.io/41721.

0 голосов
/ 06 июля 2019

Здесь есть несколько вещей, которые нужно улучшить.

Во-первых, для запросов в масштабе графа в графе с миллионами узлов и 50 миллионами связей 1G кучи и 512M кэша страниц слишком малы.Мы обычно рекомендуем около 8-10 Гб кучи минимум для средних и больших графиков (это ваша «пустая область» памяти при выполнении запроса) и стараться получить как можно больший размер графика в pagecache, если вы можете свести к минимумукэш пропускает при прохождении графика.Neo4j любит память.Память относительно дешевая.Вы можете использовать neo4j-admin memrec , чтобы получить рекомендацию о том, как настроить параметры памяти, но в целом вам нужно запустить это на машине с большим объемом памяти.

И если мыЕсли говорить о рекомендациях по аппаратному обеспечению, настоятельно рекомендуется использовать твердотельные накопители, когда вам нужно нажать на диск.

Что касается самого запроса, обратите внимание, что в опубликованном вами плане запроса, что ваша операция DISTINCT уменьшает количестворяды от 26-35 миллионов до 153 тысяч строк, это важно.Ваш самый дорогой шаг (WHERE NOT (c)-[:VISITED]->(m)) - это операция Expand(Into) в правой части плана с почти 1 миллиардом ударов.Это происходит слишком рано в запросе - вы должны делать это ПОСЛЕ вашей операции DISTINCT, поэтому она работает только с 153 тыс. Строк вместо 35 млн.

Вы также можете улучшить это, чтобы у вас даже не былочтобы попасть на график, чтобы сделать этот шаг фильтрации.Вместо того, чтобы использовать WHERE NOT <pattern> подход, вы можете предварительно сопоставить клиентов, которые посетили первого продавца, собрать их в список и сохранить их, вместо использования отрицания шаблона (где он должен фактически расширяться).все: ПОСЕТИЛИ отношения с этими клиентами и проверили, был ли кто-либо из них первоначальным продавцом), вместо этого мы проводим проверку членства в списке и проверяем, что они не являются одними или около того клиентами, которые посетили первоначального продавца.Это произойдет в памяти, так как мы уже собрали этот список, поэтому он не должен попадать в график.В любом случае вы должны выполнить DISTINCT перед этой проверкой.

В вашем RETURN вы выполняете агрегирование по уникальному свойству узла, поэтому вы оплачиваете стоимость проецирования этого свойства на 4 миллиона строк ДОколичество элементов уменьшается от агрегации до 153 тыс. строк, что означает, что вы проецируете это свойство избыточно через множество дубликатов: клиентские узлы, прежде чем они станут отличными от агрегации.Это избыточный и дорогой доступ к свойствам, которого вы можете избежать, агрегируя вместо узла, а затем выполняйте доступ к свойству после агрегации, а также после сортировки и ограничения, поэтому вам нужно только спроецировать 10 свойств.

Итак, собрав все это вместе, попробуйте это:

MATCH 
  (cc:Customer)-[:VISITED]->(m:Merchant {mcht_id: "0000000DA5"}) 
WITH m, collect(DISTINCT cc) as visitors
UNWIND visitors as cc
MATCH (uc:Customer)-[:VISITED]->(mm:Merchant)<-[:VISITED]-(cc)
WHERE 
  mm <> m
WITH 
  DISTINCT visitors, uc 
WHERE NOT uc IN visitors
MATCH 
  (uc:Customer)-[rr:VISITED]->()
WITH 
  uc, round(100*sum(rr.amount))/100 as v_amt 
ORDER BY v_amt DESC
LIMIT 10
RETURN uc.pty_id, v_amt;

РЕДАКТИРОВАТЬ

Хорошо, давайте попробуем что-нибудь еще.Я подозреваю, что во время расширения мы сталкиваемся с множеством дубликатов (многие посетители, возможно, посещали одних и тех же продавцов).Cypher не удалит дубликаты во время обхода, если вы явно не попросите об этом (поскольку эта информация может потребоваться для выполнения агрегации, такой как подсчет вхождений), и этот запрос сильно зависит от получения различных узлов во время расширения.

Если вы можете установить Процедуры APOC , мы можем использовать некоторые процедуры расширения, которые позволяют нам изменять способ расширения Cypher, посещая каждый отдельный узел только один раз по всем путям.Это может улучшить сроки здесь.По крайней мере, это покажет нам, связано ли замедление, которое мы наблюдаем, с дедупликацией узлов во время расширения, или это что-то еще.

MATCH (m:Merchant {mcht_id: "0000000DA5"}) 
CALL apoc.path.expandConfig(m, {uniqueness:'NODE_GLOBAL', relationshipFilter:'VISITED', minLevel:3, maxLevel:3}) YIELD path
WITH last(nodes(path)) as uc
MATCH 
  (uc:Customer)-[rr:VISITED]->()
WITH 
  uc
  ,round(100*sum(rr.amount))/100 as v_amt 
ORDER BY v_amt DESC
LIMIT 10
RETURN uc.pty_id, v_amt;

Несмотря на то, что это более сложный подход, следует отметить, что с уникальностью NODE_GLOBAL (обеспечивающей посещение каждого узла только один раз по всем расширенным путям) и расширением bfs нам не нужно включать WHERE NOT (c)-[:VISITED]->(m), поскольку это естественно быть исключенным; мы бы уже посетили каждого посетителя m, и поскольку они уже были посещены, мы не сможем посетить их снова, поэтому ни один из них не появится в конечном наборе результатов с 3 прыжками.

Попробуйте и запустите его пару раз, чтобы поместить его в pagecache (или, насколько это возможно ... с 512MB pagecache, вы не сможете получить всю пройденную структуру в память).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...