Neo4j-ogm: снижение производительности записи / отображения

В моем проекте я использую spring-data-neo4j 4.2.0.M1 с neo4j-ogm 2.0.4. Первоначально для этого использовался встроенный экземпляр neo4j, но в ходе исследования этой проблемы я перешел на выделенный экземпляр neo4j (работающий на той же машине), используя протокол Bolt.

Я постоянно вставляю данные, в основном, когда они становятся доступными для моего приложения (поэтому я не могу использовать пакетную вставку). После запуска это работает нормально, и сохранение экземпляра моего NodeEntity занимает ~60 мс, что идеально подходит для моего случая использования. Однако это постепенно ухудшается со временем. Через 10-20 минут это замедляется примерно до 2 с за сохранение, что уже не так здорово. Время здесь, кажется, достигает пика и не уменьшается намного больше.

Первоначально я предполагал, что это было вызвано слишком маленьким вложенным экземпляром, так как я видел неоднократные сообщения о паузах GC, о которых сообщает neo4j. Затем я перешел на выделенный экземпляр, который намного больше, и эти предупреждения GC больше не отображаются. Деградация все еще происходит, хотя.

Размеры магазина согласно сообщению neo4j:

Array Store 8.00 KiB
Logical Log 151.36 MiB
Node Store 40.14 MiB
Property Store 1.83 GiB
Relationship Store 742.63 MiB
String Store> Size 120.87 MiB
Total Store Size 4.55 GiB

Экземпляр настраивается следующим образом:

dbms.memory.pagecache.size=5g
dbms.memory.heap.initial_size=4g
dbms.memory.heap.max_size=4g
dbms.jvm.additional=-XX:+UseG1GC

Используя профилировщик YourKit (режим сэмплера!), Я вижу, что большую часть времени, по-видимому, тратит EntityGraphMapper neo4j-ogm, особенно в

org.neo4j.ogm.context.EntityGraphMapper#haveRelationEndsChanged

YourKit Profiler

Сохраняемое NodeEntity обычно имеет около 40 связей с другими узлами, большинство из которых смоделированы как RelationshipEntity. На более раннем этапе я уже заметил, что сохранение сущностей происходило довольно медленно, так как было сопоставлено слишком много связанных (но неизмененных) сущностей. С тех пор я использую глубину 1 при сохранении. Непрерывные операции, которые вызывают сохранение NodeEntitites, используют размер транзакции 200 объектов.

Я еще не убежден, что neo4j-ogm на самом деле является причиной замедления, так как я не вижу, какие изменения по сравнению с хорошими начальными результатами. В таких случаях я обычно подозреваю утечки / загрязнение памяти, но все результаты мониторинга для этого выглядят хорошо в моем приложении. Для экземпляра сервера neo4j я действительно не знаю, где искать такую ​​информацию, кроме debug.log.

В общем, я потратил довольно много времени на изучение этого вопроса и не знаю, на что еще посмотреть. Есть мысли или предложения? Я рад предоставить дополнительную информацию.

Edit: Follwing @ vince, я еще раз взглянул на распределение памяти и обнаружил, что на самом деле Neo4jSession сильно вырос после того, как приложение запустилось в течение ~3h:

Neo4j-ОГМ-память

В то время размер кучи составлял 1,7 ГБ, из которых 70% ссылались на живые данные. Из этого числа около 300 МБ в настоящее время ссылаются (и сохраняются) на Neo4jSession. Это может указывать на то, что он стал слишком большим. Как я могу вручную вмешиваться здесь?

3 ответа

Решение

Сущности остаются в сеансе, пока не соберут мусор. Там может быть некоторое влияние на производительность в haveRelationEndsChanged если вы загружаете много тысяч объектов, возможно, стоит session.clear() между каждой транзакцией и посмотреть, помогает ли это

Надеюсь, еще не поздно помочь с этим вопросом.

Недавно я столкнулся с такой же ситуацией, когда сохранял узел с ~900 связями в наборе и мог заставить его работать от ~5 секунд до 500 мс. Первоначально я использовал neo4j-ogm 2.1.3 и только что перешел на 3.0.0. Несмотря на то, что 3.0.0 намного быстрее, прирост производительности был одинаковым во всех двух версиях.

Вот некоторый псевдокод (сейчас я не могу поделиться реальным кодом):

@NodeEntity(label = "MyNode")
public class MyNode {
    @GraphId
    private Long id;

    @Index(unique = true, primary = true)
    private String myUniqueValue;

    private String value;

    @Relationship(type = "CONNECTS_TO")
    private Set<MyRelationship> relationships;
    // constructors, getters, setters
}

@Relationship(type = "CONNECTS_TO")
public class MyRelationship {

    @GraphId
    private Long id;

    @StartNode
    private MyNode parent;

    @EndNode
    private MyNode child;
    // constructors, getters, setters
}

Заметить, что MyNode имеет индексированное / уникальное поле, где у меня есть полный контроль над значением. neo4j-ogm будет использовать его, чтобы определить, должен ли он выполнить CREATE или же MERGE заявление. В моем случае использования я хочу, чтобы слияние произошло, если узел уже существует.

Создание отношений, с другой стороны, зависит от идентификатора узла (@GraphId поле). Вот небольшой фрагмент сгенерированного оператора, который его создает:

UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId MATCH (endNode) WHERE ID(endNode) = row.endNodeId...

В медленном режиме neo4j-ogm позаботится о том, чтобы проверить, сохранены ли отношения или узлы в нем, и получит идентификаторы, необходимые для создания узла. Это операция, которую вы записали в YourKit.

Пример, который выполняется медленно:

void slowMode() {
    MyNode parent = new MyNode("indexed-and-unique", "some value");
    for (int j = 0; j < 900; j++) {
        MyNode child = new MyNode("indexed-and-unique" + j, "child value" + j);
        parent.addRelationship(new MyRelationship(parent, child));
    }
    session.save(parent); // save everything. slow.
}

Решение, которое я нашел, состояло в том, чтобы разбить эти операции на три части:

  • Сохранить только родительский узел

  • Сохранить дочерние узлы

  • Сохранить отношения

Это намного быстрее:

void fastMode() {
    MyNode parent = new MyNode("indexed-and-unique", "some value");
    for (int j = 0; j < 900; j++) {
        MyNode child = new MyNode("indexed-and-unique" + j, "child value" + j);
        parent.addRelationship(new MyRelationship(parent, child));
    }
    session.save(parent, 0); // save only the parent
    session.save(getAllChildsFrom(parent), 0); // save all the 900 childs
    // at this point, all instances of MyNode will contain an "id". time to save the relationships!
    session.save(parent);
}

Стоит обратить внимание: neo4j-ogm 2.1.3 не выполнил ни одного пакетного оператора при сохранении коллекции узлов (session.save(getAllChildsFrom(parent), 0)) который все еще болтливый и медленный, но не такой медленный, как раньше. Версия 3.0.0 исправляет это.

Надеюсь, поможет!

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

  1. Используйте родной Java-драйвер neo4j вместо spring-data. Прежде всего, это async api, и если доступность данных для select в данный момент не критична, это может помочь.

  2. Используйте транзакции для вставки нескольких записей (например, 1000 вставок на транзакцию). Это ускорит вставку, потому что после любой транзакции neo4j пытается пересчитать индексы с lucene, и это требует времени. В вашем случае (с использованием spring-data) любая вставка выполняется в отдельной транзакции.

Другие вопросы по тегам