OrientDB supports multi-threads access to the database. ODatabase* and OrientGraph* instances are not thread-safe, so you've to get an instance per thread and each database instance can be used only in one thread per time. The ODocument, OrientVertex and OrientEdge classes are non thread-safe too, so if you share them across threads you can have unexpected errors hard to recognize. For more information about how concurrency is managed by OrientDB look at Concurrency.

Since v2.1 OrientDB doesn't allow implicit usage of multiple database instances from the same thread. Any attempt to manage multiple instances in the same thread must explicitly call the method db.activateOnCurrentThread() against the database instance BEFORE you use it.

Multiple database instances point to the same storage by using the same URL. In this case Storage is thread-safe and orchestrates requests from different ODatabase* instances.

ODatabaseDocumentTx-1------+
                           +----> OStorage (url=plocal:/temp/db)
ODatabaseDocumentTx-2------+

The same as for Graph API:

OrientGraph-1------+
                   +----> OStorage (url=plocal:/temp/db)
OrientGraph-2------+

Database instances share the following objects:

  • Schema
  • Index Manager
  • Security

These objects are synchronized for concurrent contexts by storing the current database in the ThreadLocal variable. Every time you create, open or acquire a database connection, the database instance is automatically set into the current ThreadLocal space, so in normal use this is hidden from the developer.

The current database is always reset for all common operations like load, save, etc.

Working with databases

The simplest way to work with multiple threads on the same database/graph is creating a new database/graph instance in the thread's run() method scope. In this way OrientDB will set the new database/graph automatically in the ThreadLocal for further usage.

Example:

OrientGraphFactory factory = new OrientGraphFactory("remote:localhost/mydb").setupPool(10, 20);

new Thread( new Runnable() {
  public void run(){
    OrientBaseGraph graph = factory.getTx();
    try{
      // OPERATION WITH THE GRAPH INSTANCE
      graph.addVertex("class:Account", "name", "Amiga Corporation");
    } finally {
      graph.shutdown();
    }
  }
} ).start();

If you are using a database/graph instance created in another thread, make sure you activate the instance before using it. The API is graph.makeActive() for the Graph API and database.activateOnCurrentThread() for the Document API.

Using multiple databases

When multiple database instances are used by the same thread, it's necessary to explicitely set the database/graph instance before to use it.

Example of using two database in the same thread:

ODocument rec1 = database1.newInstance();
ODocument rec2 = database2.newInstance();

rec1.field("name", "Luca");
database1.activateOnCurrentThread(); // MANDATORY SINCE 2.1
database1.save(rec1); // force saving in database1 no matter where the record came from

rec2.field("name", "Luke");
database2.activateOnCurrentThread(); // MANDATORY SINCE 2.1
database2.save(rec2); // force saving in database2 no matter where the record came from

In version 2.0.x, method activateOnCurrentThread() does not exist, you can use setCurrentDatabaseInThreadLocal() instead.

Get current database

To get the current database from the ThreadLocal use:

ODatabaseDocument database = (ODatabaseDocument) ODatabaseRecordThreadLocal.INSTANCE.get();

Manual control

Beware when you reuse database instances from different threads or then a thread handle multiple databases. In this case you can override the current database by calling this manually:

database.activateOnCurrentThread(); //v 2.1
// for OrientDB v. 2.0.x: database.setCurrentDatabaseInThreadLocal();

Where database is the current database instance. Example:


database1.activateOnCurrentThread();
ODocument rec1 = database1.newInstance();
rec1.field("name", "Luca");
rec1.save();

database2.activateOnCurrentThread();
ODocument rec2 = database2.newInstance();
rec2.field("name", "Luke");
rec2.save();

Custom database factory

Since v1.2 Orient provides an interface to manage custom database management in MultiThreading cases:

public interface ODatabaseThreadLocalFactory {
  public ODatabaseRecord getThreadDatabase();
}

Examples:

public class MyCustomRecordFactory implements ODatabaseThreadLocalFactory {

  public ODatabaseRecord getDb(){
   return ODatabaseDocumentPool.global().acquire(url, "admin", "admin");
  }
}


public class MyCustomObjectFactory implements ODatabaseThreadLocalFactory {
  public ODatabaseRecord getThreadDatabase(){
   return OObjectDatabasePool.global().acquire(url, "admin", "admin").getUnderlying().getUnderlying();
  }
}

Registering the factory:

ODatabaseThreadLocalFactory customFactory = new MyCustomRecordFactory();
 Orient.instance().registerThreadDatabaseFactory(customFactory);

When a database is not found in current thread it will be called the factory getDb() to retrieve the database instance.

Close a database

What happens if you are working with two databases and close just one? The Thread Local isn't a stack, so you loose the previous database in use. Example:

ODatabaseDocumentTx db1 = new ODatabaseDocumentTx("local:/temo/db1").create();
ODatabaseDocumentTx db2 = new ODatabaseDocumentTx("local:/temo/db2").create();
...

db2.close();

// NOW NO DATABASE IS SET IN THREAD LOCAL. TO WORK WITH DB1 SET IT IN THE THREAD LOCAL
db1.activateOnCurrentThread();
...

Multi Version Concurrency Control

If two threads update the same record, then the last one receive the following exception: "OConcurrentModificationException: Cannot update record #X:Y in storage 'Z' because the version is not the latest. Probably you are updating an old record or it has been modified by another user (db=vA your=vB)"

This is because every time you update a record, the version is incremented by 1. So the second update fails checking the current record version in database is higher than the version contained in the record to update.

This is an example of code to manage the concurrency properly:

Graph API

for( int retry = 0; retry < maxRetries; ++retry ) {
  try{
    // APPLY CHANGES
    vertex.setProperty( "name", "Luca" );
    vertex.addEdge( "Buy", product );

    break;
  } catch( ONeedRetryException e ) {
    // RELOAD IT TO GET LAST VERSION
    vertex.reload();
    product.reload();
  }
}

Document API

for( int retry = 0; retry < maxRetries; ++retry ) {
  try{
    // APPLY CHANGES
    document.field( "name", "Luca" );

    document.save();
    break;
  } catch( ONeedRetryException e ) {
    // RELOAD IT TO GET LAST VERSION
    document.reload();
  }
}

The same in transactions:

for( int retry = 0; retry < maxRetries; ++retry ) {
  db.begin();
  try{
    // CREATE A NEW ITEM
    ODocument invoiceItem = new ODocument("InvoiceItem");
    invoiceItem.field( "price", 213231 );
    invoiceItem.save();

    // ADD IT TO THE INVOICE
    Collection<ODocument> items = invoice.field( items );
    items.add( invoiceItem );
    invoice.save();

    db.commit();
    break;
  } catch( OTransactionException e ) {
    // RELOAD IT TO GET LAST VERSION
    invoice.reload();
  }
}

Where maxRetries is the maximum number of attempt of reloading.

What about running transaction?

Transactions are bound to a database, so if you change the current database while a tx is running, the deleted and saved objects remain attached to the original database transaction. When it commits, the objects are committed.

Example:

ODatabaseDocumentTx db1 = new ODatabaseDocumentTx("local:/temo/db1").create();

db1.begin();

ODocument doc1 = new ODocument("Customer");
doc1.field("name", "Luca");
doc1.save(); // NOW IT'S BOUND TO DB1'S TX

ODatabaseDocumentTx db2 = new ODatabaseDocumentTx("local:/temo/db2").create(); // THE CURRENT DB NOW IS DB2

ODocument doc2 = new ODocument("Provider");
doc2.field("name", "Chuck");
doc2.save(); // THIS IS BOUND TO DB2 BECAUSE IT'S THE CURRENT ONE

db1.activateOnCurrentThread();
db1.commit(); // WILL COMMIT DOC1 ONLY

results matching ""

    No results matching ""