Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using gremlin, calling Vertex.addEdge(String,Vertex) causes occasional OCommandExecutionException with message "Class xxx already exists" #10329

Open
matthewadams opened this issue Oct 4, 2024 · 3 comments
Milestone

Comments

@matthewadams
Copy link
Contributor

matthewadams commented Oct 4, 2024

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.

@tglman tglman added this to the 3.2.x milestone Oct 5, 2024
@tglman
Copy link
Member

tglman commented Oct 9, 2024

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

@matthewadams
Copy link
Contributor Author

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?

@tglman
Copy link
Member

tglman commented Oct 16, 2024

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 orientdb@tglman.org

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants