Wiki

Clone wiki

bitsy / OptimisticConcurrency

Optimistic Concurrency Control

Bitsy implements optimistic concurrency control and does not lock on any vertex or edge when it is queried or updated. Instead, every queried vertex or edge is retrieved along with its version number. When a vertex or edge is modified, the version number is checked before the element is committed. If the version matches the latest version for that vertex/edge in the database, the commit goes through. Otherwise, the Bitsy database throws a BitsyRetryException.

Note: The version numbers are at the vertex and edge level. So if two different properties are updated concurrently by two transactions, one of them will fail with the BitsyRetryException.

Isolation levels

Starting with version 1.1, Bitsy offers two isolation levels, viz. REPEATABLE_READ and READ_COMMITTED. In the REPEATABLE_READ isolation level, all vertices and edge objects with the same ID are guaranteed to be the same objects. Furthermore, a vertex/edge accessed within a transaction once is guaranteed to return the same properties throughout the remainder of the transaction.

In the READ_COMMITTED mode, vertex/edge queries return different objects each time with the latest committed information from the database. This mode consumes less memory in the transaction context and is preferred for transactions that perform large queries.

The default transaction isolation level is REPEATABLE_READ. You can adjust the settings using these the following methods available in BitsyGraph:

  • getDefaultIsolationLevel(), setDefaultIsolationLevel(BitsyIsolationevel): These methods get/set the default isolation level for all future transactions, excluding the current one.

  • getTxIsolationLevel(), setTxIsolationLevel(BitsyIsolationLevel): These methods set the isolation level for this transaction. Future transactions will continue to use the default isolation level

Simulating locks

Vertices or edge that are queried, but not updated by a transaction, do not participate in the version check. For example, if an edge is added between two vertices by a transaction, and a property is concurrently changed in one of the end-point vertices by a different transaction, both transactions will go through.

A transaction can simulate a lock on the vertex or edge, by either setting a property to the currently-held value, or calling the markForUpdate() method defined in the concrete implementations of the Vertex and Edge interfaces, viz. BitsyVertex and BitsyEdge. This ensures that other transactions that update the same edge/vertex after the "lock" operation, but before this transaction commits, will fail with the BitsyRetryException.

An application can also implement locks in Java for pessimistic concurrency control. However, you should make sure that deadlocks are avoided using method such as lock ordering.

Dealing with the BitsyRetryException

The Blueprints API includes a new transaction retry helper that implements what is described in this section (and more). For strategies that retry the transaction, please be sure to set the field named exceptionsToRetryOn in com.tinkerpop.blueprints.util.TransactionRetryStrategy to a Set with the class BitsyRetryException.

Previously documented method

(This is not recommended, since the same functionality is available in the Tinkerpop standard)

The best way to deal with the BitsyRetryException is to retry the transaction again, starting with the queries. One method to retry transactions is to model every transaction that operates on the database as an implementation of an ITransaction interface like this one:

public interface Transaction<T> {
    public T call(BitsyGraph graph) throws Exception;
}

Now, you can use a transaction executor to execute these transactions multiple times as required. Here is an example:

public class TransactionExecutor<T> {
    public TransactionExecutor(BitsyGraph graph, int numTries) {
        this.graph = graph;
        this.numTries = numTries;
    }

    public T execute(Transaction<T> tx) throws Exception {
        for (int i=0; i < numTries; i++) {
            boolean commit = false;
            try {
                tx.call(graph);
                commit = true;
                break;
            } catch (BitsyRetryException e) {
                if (i < numTries - 1) continue; else throw e;
            } finally {
                graph.stopTransaction(commit ? Conclusion.SUCCESS : Conclusion.FAILURE);
            }
        }
    }

The actual work can be implemented by named or anonymous classes:

    // Say txExecutor is a field in this class
    ITransaction sampleAddVertexTx = new ITransaction() {
        public Object call(BitsyGraph graph) {
            return graph.addVertex(null);
        }
    }

    Object newVertexId = txExecutor.execute(sampleAddVertexTx);

Updated