orientdb
orientdb copied to clipboard
Using gremlin, calling Vertex.addEdge(String,Vertex) causes occasional OCommandExecutionException with message "Class `xxx` already exists"
OrientDB Version: 3.2.32, 3.2.33, 3.2.34 (using official OrientDB containers)
Java Version: openjdk version "21.0.4" 2024-07-16 LTS, OpenJDK Runtime Environment Corretto-21.0.4.7.1 (build 21.0.4+7-LTS), OpenJDK 64-Bit Server VM Corretto-21.0.4.7.1 (build 21.0.4+7-LTS, mixed mode, sharing)
OS: macOS Sequoia 15.0.1 (24A348)
Expected behavior
Vertex.addEdge(String,Vertex) should succeed even if the class already exists.
Actual behavior
Vertex.addEdge(String,Vertex) throws
com.orientechnologies.orient.core.exception.OCommandExecutionException: Class `RESULTED_IN` already exists DB name="ANALYTICS" DB name="ANALYTICS"
Relevant stack trace:
org.apache.tinkerpop.gremlin.orientdb.OrientGraph.createClass(OrientGraph.java:646)
org.apache.tinkerpop.gremlin.orientdb.OrientGraph.createClass(OrientGraph.java:621)
org.apache.tinkerpop.gremlin.orientdb.OrientGraph.createEdgeClass(OrientGraph.java:609)
org.apache.tinkerpop.gremlin.orientdb.OrientVertex.addEdge(OrientVertex.java:135)
It appears that in OSchemaRemote.java, method createClass, should change line 104 from
cmd.append('`');
to
cmd.append('` IF NOT EXISTS ');
Further, perhaps line 96 of the same file should simply return silently, too, short-circuiting to just returning the OClass instead, going from
if (classes.containsKey(key))
throw new OSchemaException("Class '" + className + "' already exists in current database");
to
if (classes.containsKey(key))
return classes.get(key);
Steps to reproduce
This issue is intermittent, but happens frequently enough that it's causing our tests to fail and our CI pipelines to fail. We're using the OrientDB java driver in a Spring Boot 3.3.3 + Kotlin 2.0.20 environment, and the OrientDB driver doesn't appear to offer asynchronous APIs, which is problematic. There could be simultaneous activities (not necessarily multiple threads, but multiple coroutines on a single thread) that are causing this to fail. Hard to tell, but making OSchemaRemote.createClass(ODatabaseDocumentInternal database, final String className, int[] clusterIds, OClass... superClasses) more lenient would prolly work around the issue.
Hi,
Thanks to report this, yes as today you may get this error if concurrently the same class is created, and this can be fixed with at "create if not exists " implementation, you can workaround for now creating ahead of time the classes that do not exists.
Regards
Sorry, but my reading of the code means that your workaround will fail. We already create all classes up front before any vertices or edges are created, and the code path executed does not short circuit if the class already exists, AFAICT. Can you please fix and publish a patch release ASAP?
Hi,
I went trough again the code and the fix you propose, the createClass method is supposed to fail if the class exists, not return the class silently, for do that we do have an API getOrCreateClass but anyway checking the code of the createClass in OrientGraph:
public void createClass(final String className, final OClass superClass) {
makeActive();
OSchema schema = database.getMetadata().getSchema();
OClass cls = schema.getClass(className);
if (cls == null) {
try {
executeOutsideTx(
(db) -> {
OSchema s = db.getMetadata().getSchema();
s.createClass(className, superClass);
return null;
});
if (this.tx().isOpen()) schema.reload();
} catch (OException e) {
throw new IllegalArgumentException(e);
}
logger.info("created class '%s' as subclass of '%s'", className, className);
} else {
if (!cls.isSubClassOf(superClass)) {
throw new IllegalArgumentException(
"unable to create class '"
+ className
+ "' as subclass of '"
+ superClass
+ "'. different super class.");
}
}
}
This already check ahead of time if the class exists, and create it only if does not exists, so to get this issue I think is only possible if the class is not defined ahead and get defined concurrently.
And yes also label of the edges in OrientDB are classes and need to be defined ahead of time.
I do think though that this can be improved implementing an APIs in OrientDB that support the creation of the class if it does not exists in a thread safe way.
In any case fixes and releases happen in a "volunteer" way if you need a more timely response feel free to contact me at [email protected]