I use a Neo4J database with nearly 500k nodes. When I startup my Spring application and do the first query, it takes about 4-5 seconds. This happens just for the first query, so I thought I could do a warmup after spring is initialized to make all subsequent queries faster.
This is my applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- neo4j database -->
<util:map id="config">
<entry key="enable_remote_shell" value="false" />
</util:map>
<bean id="graphDbFactory" class="org.neo4j.graphdb.factory.GraphDatabaseFactory" />
<bean id="graphDbBuilder" factory-bean="graphDbFactory"
factory-method="newEmbeddedDatabaseBuilder">
<constructor-arg value="/path/to/db" />
</bean>
<bean id="graphDbBuilderFinal" factory-bean="graphDbBuilder"
factory-method="setConfig">
<constructor-arg ref="config" />
</bean>
<bean id="graphDatabaseService" factory-bean="graphDbBuilderFinal"
factory-method="newGraphDatabase" destroy-method="shutdown" class="org.neo4j.graphdb.GraphDatabaseService"/>
<context:component-scan base-package="com.app.components" />
</beans>
I saw that one way to wait for Spring beans initialization was to implement an ApplicationListener, and the regular way to warmup the database with Neo was to call the apoc.runtime.warmup() function, so I did as follows:
package com.app.components;
@Component
public class StartupTasks implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOG = LogManager.getLogger(StartupTasks.class);
@Autowired
GraphDatabaseService db;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
executeTestSelect();
}
private void executeTestSelect() {
LOG.info("Warming up Neo4j...");
Transaction tx = db.beginTx();
db.execute("CALL apoc.warmup.run()");
tx.close();
LOG.info("Warmup complete.");
}
}
This one didn't work, everything gets correctly logged but the first neo4j query is still slow.
Since this approach didn't work, I edited the executeTestSelect method to run a real query and process the results like this:
private void executeTestSelect() {
String textToSearch = "a"; // returns almost all nodes, should make neo4j cache them all
Transaction tx = db.beginTx();
Map<String, Object> params = new HashMap<String, Object>();
params.put("textToSearch", textToSearch );
Result resultSet = db.execute("MATCH (n:PROD) WHERE n.description CONTAINS {textToSearch} RETURN n",
params);
Iterator<Node> results = resultSet.columnAs("n");
int count = 0;
while (results.hasNext()) {
results.next();
count++:
}
tx.close();
LOG.info("Neo4j cache done. Processed " + count + " nodes.");
}
This time the startup takes 4-5 seconds just to execute the query, but then it prints
Neo4j cache done. Processed 0 nodes.
This is weird, because the exact same query run when the app is fully initialized returns 450k nodes.
What am I missing? Is it possible that when reaching onApplicationEvent the Neo4j DB hasn't been initialized yet and can't execute queries?
How do I correctly warmup a neo4j database right after the complete spring app initialization?
Ok I discovered this by pure luck.
I removed the ApplicationListener and created a @Service class with the @Autowired annotation on the method that does the warmup, it seems to be working.
I discovered this by removing a class field without seeing the @Autowired annotation left here, and it worked. I tried it several times, the warmup is working now. I don't know if it is documented somewhere in the Spring Documentation.
This is my final class:
package com.app.components;
@Component
public class Neo4jWarmup {
private static final Logger LOG = LogManager.getLogger(StartupTasks.class);
@Autowired
GraphDatabaseService db;
/*
* this did the trick - the method gets called
* after Spring initialization and the DB works as expected
*/
@Autowired
public void neo4jWarmup() {
executeTestSelect();
}
private void executeTestSelect() {
LOG.info("Warming up Neo4j...");
Transaction tx = db.beginTx();
db.execute("CALL apoc.warmup.run()");
tx.close();
LOG.info("Warmup complete.");
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With