Использование Unwind и Dumping данных в neo4j - Оптимизация запросов - PullRequest
2 голосов
/ 09 июля 2020

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

В моем проекте Только для одного случая у меня есть более 18 000 записей , которые предназначены для хранения в db и будут иметь отношения с целевым узлом. Каждая запись будет сохранена как Узел друга

Отношения похожи на

Target_Node- [r: followed_by] -> Friend_Node

Target_Node- [r: Friends_with] -> Friend_Nod e

Target_Node- [r: Performs_Activity] -> Friend_Node

Мой запрос выполняется для все случаи по отдельности, и весьма вероятно, что между целевым и дружественным узлом могут быть все три отношения.

Я отправляю 20 записей на поток для одной вставки, которая раскручивается по массиву записей и проверяет, запись уже существует в Friend_Node или Target_Node, в противном случае создайте ее как Friend_Node и затем назначьте ей отношение; Если у узла уже есть связь и в запрос передано новое отношение, то между двумя узлами также будет добавлено новое отношение.

Также я проверяю в своем запросе, есть ли у записи Location , затем я создаю узел Location и также назначаю ему связь.

Примечание : переменная create_rel может быть Friends_with, Followed_by или Activity_p

My запрос выглядит следующим образом:

 """UNWIND [{id: "1235" , uid : "0"}] as user

    UNWIND """+ l +""" as c

    OPTIONAL MATCH (n:Target {id : c.id , uid : "0"})

    OPTIONAL MATCH (m:Friend {id : c.id , screen_name:c.screen_name, uid : "0"})

    WITH coalesce(n, m) as node,user,c // returns first non-null value

    CALL apoc.do.when(node is null, "MERGE (n:Friend {id:c.id, name:c.name, profile: c.profile, location:c.location, uid : user.uid}) RETURN n", '', {c:c,user:user}) YIELD value

    with coalesce(node, value.n) as y,user,c

    MERGE (u:Target {id: user.id , uid : user.uid})

    """+create_rel+"""

    foreach (sc in c.cityn | merge(cn:Location {location:sc.location, loc_lower : sc.loc_lower}) merge (y)-[:`located_at`]-(cn))

    """

Db также иногда выдает ошибку TransientError.

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

Заранее спасибо

Ответы [ 2 ]

0 голосов
/ 09 июля 2020
  1. Вам следует избегать одновременного выполнения нескольких запросов записи (которые могут касаться одних и тех же узлов и отношений), поскольку это может вызвать прерывистые TransientError s, как вы видели. (Однако запросы, вызывающие временные ошибки , можно повторить.)

  2. Вы должны передавать user и l в свой запрос как параметры , так что планировщику Cypher нужно будет только скомпилировать запрос один раз и сделать запрос менее подверженным атакам Cypher-инъекции. (Кроме того, нет необходимости в UNWIND списке, который всегда будет содержать только одну карту - вы могли бы напрямую использовать карту через WITH {id: "1235" , uid : "0"} AS user. Но, как я уже упоминал, вы должны просто передать user карту в качестве параметра, чтобы вы могли эффективно изменить пользователя без принудительной перекомпиляции.)

  3. Чтобы избежать перекомпиляции, вам также необходимо сделать строку create_rel постоянной строкой (так, с таким же успехом он может быть прямо в вашей основной строке запроса). Опять же, вы также должны передать любые необходимые для этого переменные в качестве параметров.

  4. Вы должны создать индексы (или ограничения уникальности) для :Target(id) и :Friend(id), для ускорения ваших предложений MATCH и MERGE.

  5. (a) MERGE (u:Target {id: user.id , uid : user.uid}) нужно только выполнить один раз , а не для c значения . Таким образом, он должен быть выполнен перед UNWIND.

    (b) Кроме того, для этого запроса не обязательно создавать u, поскольку ничто в запросе не использует его. . Таким образом, вместо того, чтобы запускать это идентичное предложение MERGE один раз для каждого потока, вам следует подумать о том, чтобы выделить его и запустить в качестве отдельного автономного запроса.

Вот запрос, который объединяет предложения # 2 и # 5a (но об остальных вам придется позаботиться самостоятельно), наряду с некоторым рефакторингом с использованием понимания шаблонов , чтобы избежать ненужных обращений к БД:

MERGE (u:Target {id: $user.id, uid: $user.uid})
WITH u
UNWIND $l as c
WITH u, c, [(n:Target {id : c.id})-[*0]-()|n] AS nodeList
WITH u, c, CASE WHEN SIZE(nodeList) = 0 THEN [(n:Friend {id : c.id})-[*0]-()|n] ELSE nodeList END AS nodeList
CALL apoc.do.when(SIZE(nodeList) = 0, 'MERGE (n:Friend {id: c.id, name: c.name, profile: c.profile, location: c.location, uid: user.uid}) RETURN n', 'RETURN nodeList[0] AS n', {c:c,user:$user,nodeList:nodeList}) YIELD value
WITH u, c, value.n AS node
FOREACH (sc IN c.cityn | MERGE (cn:Location {location: sc.location, loc_lower: sc.loc_lower}) MERGE (node)-[:located_at]-(cn))

// Put your parameterized create_rel code here
0 голосов
/ 09 июля 2020

Я думаю, ваша основная проблема заключается в том, как вы объединяете и сопоставляете узлы. В идеале вы всегда хотите иметь уникальный идентификатор для узлов. Я вижу, что узел Friend имеет свойство id, которое, как я предполагаю, уникально для каждого Friend и Target.

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

CREATE CONSTRAINT ON (f:Friend) ASSERT f.id IS UNIQUE;
CREATE CONSTRAINT ON (f:Target) ASSERT f.id IS UNIQUE;

Вы хотите что-то подобное и для Location узлов. похоже, что вы храните и значение местоположения, и значение местоположения в нижнем регистре, поэтому любое из них должно быть уникальным для каждого узла.

CREATE CONSTRAINT ON (l:Location) ASSERT l.id IS UNIQUE;

Теперь вы можете оптимизировать свой запрос следующим образом:

"""UNWIND [{id: "1235" , uid : "0"}] as user

    UNWIND """+ l +""" as c

    OPTIONAL MATCH (n:Target {id : c.id})

    OPTIONAL MATCH (m:Friend {id : c.id})

    WITH coalesce(n, m) as node,user,c // returns first non-null value

    CALL apoc.do.when(node is null,
    "MERGE (n:Friend {id:c.id}) 
     ON CREATE SET n+= {name:c.name, profile: c.profile, 
           location:c.location, uid : user.uid} 
     RETURN n", '', {c:c,user:user})
    YIELD value
    with coalesce(node, value.n) as y,user,c
    MERGE (u:Target {id: user.id , uid : user.uid})
    """+create_rel+"""

    foreach (sc in c.cityn | 
        merge(cn:Location {location:sc.location}) 
        ON CREATE SET cn.loc_lower = sc.loc_lower 
        merge (y)-[:`located_at`]-(cn))

    """
...