2
votes

Using SDN 3.3.1 and Neo4j 2.1.7 in external server setup.

I am having java.net.SocketTimeoutException: Read timed out whenever I do the following execution pattern :

  1. Retrieve an entity : MyEntity entity = myRepository.findByUuid(uuid);
  2. Delete a relationship of that entity : neo4jOperations.query("match (e{uuid:"theuuid"})<-[r:THE_RELATION]-() delete r);
  3. Change a property of the entity : entity.setTitle("New title");
  4. Save the entity : myRepository.save(entity); --> waiting a long time then got the SocketTimeoutException

After some analysis, it appears to me that 1. does http calls inside AND outside the transaction as you can see in this Neo4j http log extract :

127.0.0.1 - - [20/août/2015:10:47:57 +0200] "POST /db/data/transaction HTTP/1.1" 201 659 "-" "neo4j-rest-graphdb/0" 
127.0.0.1 - - [20/août/2015:10:47:57 +0200] "GET /db/data/node/135/relationships/in/WITH_ROLE HTTP/1.1" 200 391 "-" "neo4j-rest-graphdb/0" 
127.0.0.1 - - [20/août/2015:10:47:57 +0200] "GET /db/data/node/149 HTTP/1.1" 200 1780 "-" "neo4j-rest-graphdb/0" 
127.0.0.1 - - [20/août/2015:10:47:57 +0200] "GET /db/data/node/135/relationships/out/DEFAULT_ENTITY HTTP/1.1" 200 401 "-" "neo4j-rest-graphdb/0" 
127.0.0.1 - - [20/août/2015:10:47:57 +0200] "GET /db/data/node/227 HTTP/1.1" 200 1884 "-" "neo4j-rest-graphdb/0" 
127.0.0.1 - - [20/août/2015:10:47:57 +0200] "GET /db/data/node/135/relationships/in/WITH_PENDING_ROLE HTTP/1.1" 200 2 "-" "neo4j-rest-graphdb/0"

So my first question would be :

  • Why an operation like fetching a entity generates queries that uses the transaction endpoints and others that do not ?

Then, the delete operation in 2. is correctly done inside the transaction :

127.0.0.1 - - [20/août/2015:10:47:58 +0200] "POST /db/data/transaction/2720 HTTP/1.1" 200 254 "-" "neo4j-rest-graphdb/0" 

The point 3. obviously generates no http request.

And at the end, the point 4. which generate a SocketTimeoutException after being "blocked" for a time (30 sec to be precise), finally generate 2 http requests :

127.0.0.1 - - [20/août/2015:10:48:28 +0200] "DELETE /db/data/transaction/2720 HTTP/1.1" 200 26 "-" "neo4j-rest-graphdb/0"
127.0.0.1 - - [20/août/2015:10:48:28 +0200] "PUT /db/data/node/135/properties HTTP/1.1" 204 0 "-" "neo4j-rest-graphdb/0"

The delete is because the transaction is rollbacked, and I think the PUT corresponds to the save()

In my opinion, a deadlock is happening because of the PUT being outside of the transaction ! So, the main question is :

  • Why is the save() making a PUT outside of the transaction and not a POST inside the transaction ? Is there a way to force the entity update to be done using the transactional endpoint ?

Thanks :)

EDIT : Added SDN config

<beans ...>
  <tx:annotation-driven />
  <context:component-scan base-package="com.myproject.mw.neo4j.service,com.myproject.mw.neo4j.aspect" />
  <aop:aspectj-autoproxy />
  <!-- External database config -->
  <bean id="graphDatabaseServiceExternal" class="org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase"
    lazy-init="true">
      <constructor-arg index="0" value="${neo4j.path}" />
  </bean>

  <alias name="graphDatabaseService${neo4j.mode}" alias="graphDatabaseService" />
  <neo4j:config base-package="com.myproject.mw.neo4j.domain"
    graphDatabaseService="graphDatabaseService" />
  <neo4j:repositories base-package="com.myproject.mw.neo4j.repository" />
</beans>

EDIT 2 : Added entities definition and simple test to reproduce

Note that my Neo4j server is on localhost.

The test :

@Test
@Transactional
@Rollback(false)
public void testEntityNotFound() {
    BusinessCard card = genericRepository.findByUuid("4bce6162-bc6f-bce2-bc71-bc70b63dff00");
    Entity entity = genericRepository.findByUuid("04947df8-0e9e-4471-a2f9-9af509fb5889");
    neo4jOperations.createRelationshipBetween(neo4jOperations.getNode(entity.getId()),
            neo4jOperations.getNode(card.getId()), "FAKE2", null);
    neo4jOperations.save(card);
}

BusinessCard entity :

public class BusinessCard extends GenericNode {
    @Indexed
    private String title;
    @RelatedTo(type = RelationNames.WITH_ROLE, direction = Direction.INCOMING)
    private User user;
    @RelatedTo(type = RelationNames.WITH_PENDING_ROLE, direction = Direction.INCOMING)
    private User pendingUser;
    @RelatedTo(type = RelationNames.DEFAULT_ENTITY)
    private Entity defaultEntity;
}

And the Entity entity :

public class Entity extends GenericNode {
    private String logo;
    @Indexed
    private String entityName;
    @RelatedTo(type = RelationNames.IS_IN_CITY)
    private CityNode mainCity;
    @RelatedTo(type = RelationNames.IS_IN_COUNTRY)
    private CountryNode mainCountry;
    @RelatedTo(type = RelationNames.IS_IN_COUNTRIES)
    private Set<CountryNode> countries;
    @Fetch
    @RelatedToVia(type = RelationNames.TRANSLATION)
    private Set<EntityToEntityTranslatedContent> entityTranslatedContent;
    @Fetch
    @RelatedTo(type = RelationNames.HAS_KEY_STAGES)
    private KeyStage lastKeyStage;
    @GraphProperty(propertyType = Long.class)
    private Date lastProfileUpdateDateTime;
    private Integer profilCompletionIndice;
    @RelatedToVia(type = RelationNames.WORKS_AT, direction = Direction.INCOMING)
    private Set<BusinessCardWorkAt> employees = new HashSet<>();
...+34 more fields...
}
3
The save operation should not use a PUT, it should use Cypher as the rest of the operations.Michael Hunger
Could you get the full stack trace (thread dump) while it is hanging? That would be super helpful. Please send it to me too michael at neo4j.comMichael Hunger
I sent you the thread dumps. Thank you for your help.Gaël N.

3 Answers

1
votes

I assume you are using org.springframework.data.neo4j.rest.SpringRestGraphDatabase, which is now deprecated, and doesn't support transactions properly. (see this code that generates your PUT)

There is a new cypher based database org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase, which might do what you want.

You also want to look into 4.x version, which is an overhaul of SDN focused on using neo4j remotely over rest, in a more efficient way. It is supposed to go GA soon.

Some more explanation is provided on this blog http://jexp.de/blog/2014/12/spring-data-neo4j-improving-remoting-performance/

0
votes

Something is off with your entities then, it seems to spend a lot of time writing data to the server. From your thread dumps:

at org.neo4j.rest.graphdb.ExecutingRestRequest.put(ExecutingRestRequest.java:155)
at org.neo4j.rest.graphdb.RestAPIImpl.setPropertiesOnEntity(RestAPIImpl.java:633)
at org.neo4j.rest.graphdb.entity.RestEntity.flush(RestEntity.java:189)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyPropertiesTo(SourceStateTransmitter.java:109)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.write(Neo4jEntityConverterImpl.java:170)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister$CachedConverter.write(Neo4jEntityPersister.java:179)

and for dump 2:

at org.neo4j.rest.graphdb.ExecutingRestRequest.put(ExecutingRestRequest.java:155)
at org.neo4j.rest.graphdb.RestAPIImpl.setPropertiesOnEntity(RestAPIImpl.java:633)
at org.neo4j.rest.graphdb.entity.RestEntity.flush(RestEntity.java:189)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyPropertiesTo(SourceStateTransmitter.java:109)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.write(Neo4jEntityConverterImpl.java:170)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister$CachedConverter.write(Neo4jEntityPersister.java:179)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:247)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:235)
at org.springframework.data.neo4j.support.Neo4jTemplate.save(Neo4jTemplate.java:365)

There must be also something other off, because

org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase

should NOT use the normal Rest endpoints with GET,PUT,DELETE etc. but just plain Cypher.

You should also check the latency and bandwidth to your server, SDN3 is still quite chatty.

So you should share the code for your entities, what you exactly do before/when you called that save and how much data is sent to the server.

For a better SDN + Server experience I recommend to have a look at SDN4 which was written from scratch for that purpose. SDN4 RC2 was released last Friday:

http://docs.spring.io/spring-data/neo4j/docs/4.0.0.RC2/reference/html/

0
votes

This was fixed in SDN 3.4 (see https://jira.spring.io/browse/DATAGRAPH-699). This jira ticket content is a different topic but you can rely on the modified title "Implement missing operations for Cypher based REST-API separately". Since 3.4 there is no more out of transaction REST requests when using @Transactional.