Question

In my Neo4j based application I have a classical find-or-create usecase: I ask neo4j if there is already a node with a special relationship to other nodes. If not, then I'll create one within the same transaction. Nothing special... But if I call this method multi-threaded (or n-clients connected to the same database), then, from time to time, I got an exception which says that there are more than one elements matching my query. That means there were two threads few moments before which both have created the same 'pair'-node. Synchronization at java-level is not an option (since requests can come from more than one client/server).

How can I solve such issues other than catching the exception and delete the duplicated node afterwards?

I use Spring Data Neo4j 2.3.3 and Neo4j 1.9.5.

Here is my query:

START expected=node({0}), actual=node({1}) MATCH (expected)<-[:EXPECTED]- (pair) -[:ACTUAL]->(actual) RETURN pair

Btw. The thrown exception in this case is a NoSuchElementException, at least for me this is a bit confusing.

WARN  [00:32.31,962] de.xxx.web.AbstractExceptionHandlers: error.NoSuchElementException
java.util.NoSuchElementException: More than one element in org.neo4j.cypher.PipeExecutionResult$$anon$1@8f0dc23. First element is '{pair=Node[20001]}' and the second element is '{pair=Node[20000]}'
    at org.neo4j.helpers.collection.IteratorUtil.singleOrNull(IteratorUtil.java:122)
    at org.neo4j.helpers.collection.IteratorUtil.singleOrNull(IteratorUtil.java:289)
    at org.springframework.data.neo4j.conversion.QueryResultBuilder$1.singleOrNull(QueryResultBuilder.java:94)
    at org.springframework.data.neo4j.repository.query.GraphRepositoryQuery.dispatchQuery(GraphRepositoryQuery.java:108)
    at org.springframework.data.neo4j.repository.query.GraphRepositoryQuery.execute(GraphRepositoryQuery.java:81)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:337)
    at org.springframework.aop.framework.ReflectiveMethodvocation.proceed(ReflectiveMethodvocation.java:172)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithvocation(TransactionInterceptor.java:96)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
    at org.springframework.aop.framework.ReflectiveMethodvocation.proceed(ReflectiveMethodvocation.java:172)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    at org.springframework.aop.framework.ReflectiveMethodvocation.proceed(ReflectiveMethodvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at com.sun.proxy.$Proxy63.findByAttributes(Unknown Source)
    at de.xxx.core_service.attribute.AttributeManagerImpl.findOrCreatePair_aroundBody32(AttributeManagerImpl.java:212)
    at de.xxx.core_service.attribute.AttributeManagerImpl$AjcClosure33.run(AttributeManagerImpl.java:1)
Was it helpful?

Solution

You have to take a manual lock for that otherwise you get into typical race conditions.

Probably easiest by locking both nodes in question.

There is a lock management API on the Neo4j Transaction, but you can also just remove an non-existent property which also takes a write lock.

We probably have to expose locking in SDN at some point. If you think that's a good idea, please raise a JIRA issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top