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
Сохраняемое NodeEntity обычно имеет около 40 связей с другими узлами, большинство из которых смоделированы как RelationshipEntity. На более раннем этапе я уже заметил, что сохранение сущностей происходило довольно медленно, так как было сопоставлено слишком много связанных (но неизмененных) сущностей. С тех пор я использую глубину 1 при сохранении. Непрерывные операции, которые вызывают сохранение NodeEntitites, используют размер транзакции 200 объектов.
Я еще не убежден, что neo4j-ogm на самом деле является причиной замедления, так как я не вижу, какие изменения по сравнению с хорошими начальными результатами. В таких случаях я обычно подозреваю утечки / загрязнение памяти, но все результаты мониторинга для этого выглядят хорошо в моем приложении. Для экземпляра сервера neo4j я действительно не знаю, где искать такую информацию, кроме debug.log.
В общем, я потратил довольно много времени на изучение этого вопроса и не знаю, на что еще посмотреть. Есть мысли или предложения? Я рад предоставить дополнительную информацию.
Edit: Follwing @ vince, я еще раз взглянул на распределение памяти и обнаружил, что на самом деле Neo4jSession сильно вырос после того, как приложение запустилось в течение ~3h:
В то время размер кучи составлял 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.
Используйте родной Java-драйвер neo4j вместо spring-data. Прежде всего, это async api, и если доступность данных для select в данный момент не критична, это может помочь.
Используйте транзакции для вставки нескольких записей (например, 1000 вставок на транзакцию). Это ускорит вставку, потому что после любой транзакции neo4j пытается пересчитать индексы с lucene, и это требует времени. В вашем случае (с использованием spring-data) любая вставка выполняется в отдельной транзакции.