Параллельные операции графа OrientDB в Java

Я пытаюсь использовать orientdb (v2.1.2) в многопоточной среде (Java 8), где я обновляю вершину из нескольких потоков. Я знаю, что orientdb использует MVCC, поэтому эти операции могут завершиться неудачно и их придется выполнять снова.

Я написал небольшой модульный тест, который пытается спровоцировать такие ситуации, ожидая циклического барьера в потоках, которые я разветвляю. К сожалению, тест терпит неудачу с неясным исключением, которое я не понимаю:

Sep 21, 2015 3:00:24 PM com.orientechnologies.common.log.OLogManager log
INFO: OrientDB auto-config DISKCACHE=10,427MB (heap=3,566MB os=16,042MB disk=31,720MB)
Thread [0] running 
Thread [1] running 
Sep 21, 2015 3:00:24 PM com.orientechnologies.common.log.OLogManager log
WARNING: {db=tinkerpop} Requested command 'create edge type 'testedge_1442840424480' as subclass of 'E'' must be executed outside active transaction: the transaction will be committed and reopen right after it. To avoid this behavior execute it outside a transaction
Sep 21, 2015 3:00:24 PM com.orientechnologies.common.log.OLogManager log
WARNING: {db=tinkerpop} Requested command 'create edge type 'testedge_1442840424480' as subclass of 'E'' must be executed outside active transaction: the transaction will be committed and reopen right after it. To avoid this behavior execute it outside a transaction
Exception in thread "Thread-4" com.orientechnologies.orient.core.exception.OSchemaException: Cluster with id 11 already belongs to class testedge_1442840424480
    at com.orientechnologies.orient.core.metadata.schema.OSchemaShared.checkClustersAreAbsent(OSchemaShared.java:1264)
    at com.orientechnologies.orient.core.metadata.schema.OSchemaShared.doCreateClass(OSchemaShared.java:983)
    at com.orientechnologies.orient.core.metadata.schema.OSchemaShared.createClass(OSchemaShared.java:415)
    at com.orientechnologies.orient.core.metadata.schema.OSchemaShared.createClass(OSchemaShared.java:400)
    at com.orientechnologies.orient.core.metadata.schema.OSchemaProxy.createClass(OSchemaProxy.java:100)
    at com.tinkerpop.blueprints.impls.orient.OrientBaseGraph$6.call(OrientBaseGraph.java:1387)
    at com.tinkerpop.blueprints.impls.orient.OrientBaseGraph$6.call(OrientBaseGraph.java:1384)
    at com.tinkerpop.blueprints.impls.orient.OrientBaseGraph.executeOutsideTx(OrientBaseGraph.java:1739)
    at com.tinkerpop.blueprints.impls.orient.OrientBaseGraph.createEdgeType(OrientBaseGraph.java:1384)
    at com.tinkerpop.blueprints.impls.orient.OrientBaseGraph.createEdgeType(OrientBaseGraph.java:1368)
    at com.tinkerpop.blueprints.impls.orient.OrientBaseGraph.createEdgeType(OrientBaseGraph.java:1353)
    at com.tinkerpop.blueprints.impls.orient.OrientVertex.addEdge(OrientVertex.java:928)
    at com.tinkerpop.blueprints.impls.orient.OrientVertex.addEdge(OrientVertex.java:832)
    at com.gentics.test.orientdb.OrientDBTinkerpopMultithreadingTest.lambda$0(OrientDBTinkerpopMultithreadingTest.java:31)
    at com.gentics.test.orientdb.OrientDBTinkerpopMultithreadingTest$$Lambda$1/1446001495.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

Тест использует простую базу данных в памяти. Я не понимаю, почему orientdb проверяет некоторые действия кластера:

Cluster with id 11 already belongs to class testedge

Почему-то эта проблема возникает только тогда, когда я пытаюсь создать два ребра с одной и той же меткой.

private OrientGraphFactory factory = new OrientGraphFactory("memory:tinkerpop").setupPool(5, 20);

@Test
public void testConcurrentGraphModifications() throws InterruptedException {
    OrientGraph graph = factory.getTx();
    Vertex v = graph.addVertex(null);
    graph.commit();
    CyclicBarrier barrier = new CyclicBarrier(2);

    List<Thread> threads = new ArrayList<>();

    // Spawn two threads
    for (int i = 0; i < 2; i++) {
        final int threadNo = i;
        threads.add(run(() -> {
            System.out.println("Running thread [" + threadNo + "]");
            // Start a new transaction and modify vertex v
            OrientGraph tx = factory.getTx();
            Vertex v2 = tx.addVertex(null);
            v.addEdge("testedge", v2);
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            tx.commit();
        }));
    }

    // Wait for all spawned threads
    for (Thread thread : threads) {
        thread.join();
    }
}

protected Thread run(Runnable runnable) {
    Thread thread = new Thread(runnable);
    thread.start();
    return thread;
}

В общем, я был бы очень благодарен за пример, демонстрирующий, как справляться с конфликтами MVCC при использовании orientdb во встроенной многопоточной среде Java.


Обновление:

Я заметил, что проблема больше не возникает, когда я перезагружаю вершину в своем потоке через tx.getVertex(vertex.getId()) (а не через .reload()). Я получаю различные ошибки, когда просто передаю ссылку на объект вершины в свой поток и использую ее там. Я предполагаю, что класс OrientVertex не является потокобезопасным.


person Jotschi    schedule 21.09.2015    source источник


Ответы (1)


  1. Вы правы, все элементы графа не являются потокобезопасными.
  2. Причина вашего исключения заключается в следующем: когда вы создаете край, вы под базой данных графа создаете документ с классом, который равен метке края. Если класс отсутствует, транзакция фиксируется автоматически и создается новый класс внутри схемы. Каждый класс сопоставляется с кластером в базе данных (это похоже на таблицу), когда вы одновременно добавляете ребра, вы одновременно создаете один и тот же класс, и в результате создается один и тот же кластер. Таким образом, один поток выигрывает, другой терпит неудачу, за исключением того, что кластер с данным именем уже создан. На самом деле я предлагаю вам создать все классы, также известные как метки ребер, если это возможно, прежде чем вы добавите ребра во время выполнения.

Еще одно предложение. Вы должны думать об экземпляре OrientGraph, как о соединении с сервером. Лучшее использование следующее:

  1. Настройка пула в OrientGraphFactory
  2. Получить экземпляр графа перед транзакцией.
  3. Выполнить транзакцию.
  4. Вызовите .shutdown(), не создавайте долгоживущие экземпляры графа.
person Andrey Lomakin    schedule 02.10.2015