Introduction:

Oracle NoSQL Database data store is a collection of storage nodes, each of which hosts one or more replication nodes. Data is automatically populated across these replication nodes by internal replication mechanisms to ensure high availability and rapid fail-over in the event of a node failure.Oracle NoSQL Database is a sharded, shared-nothing system that distributes data uniformly across multiple shards in a NoSQL database cluster, based on the hashed value of the primary keys. 

Oracle NoSQL Database Architecture

With this as context, in this two part blog series, I would like to define ACID (Atomicity, Consistency, Isolation, and Durability) properties and elaborate on how to configure Consistency and Durability for transactions in the Oracle NoSQL database.  In Oracle NoSQL Database, a transaction is treated as a logical, atomic unit of work that entails a single database operation. Every data modification in the database takes place in a single transaction, managed by the system. Users of the database do not have the ability to group multiple operations into a single transaction because there isn’t the notion of begin/end transaction.   In a database, transactional semantics are often described in terms of ACID properties.

ACID properties

In Oracle NoSQL Database transactions maintain all the following properties and users can control some of them.

  • Atomicity: Transaction either completes or fails in its entirety. There is no in-between state or no partial transactions.
  • Consistency: Transaction leaves the database in a valid state.
  • Isolation: No two transactions mingle or interfere with each other. Users get the same result when the two transactions are executed in sequence or executed in parallel.
  • Durability: Changes in a transaction are saved and the changes survive any type of failure (network, disk, CPU, or a power failure).

Users can define a wide range of consistency levels depending on the application needs with the Oracle NoSQL Database Direct Driver.   In addition, the Oracle NoSQL Database Drivers (commonly called the SDKs) support eventual and absolute consistency.

Users can also configure durability such that updated rows in the database survive any failure with the Oracle NoSQL Database Direct Driver.   Durability is not configurable in the SDKs.

Atomicity and Isolation are not configurable but Oracle NoSQL Database allows users to control consistency and durability policies in order to trade-off for performance for application needs. Some NoSQL databases only support eventual consistency but have no mechanism for absolute consistency.

Let’s explore in more detail.

Configuring Consistency Guarantees:

configuring consistency

To specify a consistency policy, you use one of the static instances of the Consistency class, or one of its nested classes.
Once you have selected a consistency policy, you can put it to use in one of two ways. First, you can use it to define a default consistency policy using the KVStoreConfig.setConsistency() method. Specifying a consistency policy in this way means that all store operations will use that policy, unless they are overridden on an operation by operation basis. The second way to use a consistency policy is to override the default policy using aReadOption class instance you provide to the TableAPI method that you are using to perform the store read operation.

Using Simple Consistency:

You can use static instances of the Consistency base class to specify certain specific consistency guarantees. There are three such instances that you can use:

Consistency.ABSOLUTE:
Requires that the operation be serviced at the leader node. In this way, the rows will always be consistent with the leader. This is the strongest possible consistency guarantee that you can require, but it comes at the cost of servicing all read and write requests at the leader node. If you direct all your traffic to the leader node (which is just one machine for each partition), then you will not be distributing your read operations across your replicas. You also will slow your write operations because your leader will be busy servicing read requests. For this reason, you should use this consistency guarantee sparingly.

Consistency.NONE_REQUIRED
Allows the store operation to proceed regardless of the state of the replica relative to the leader. This is the most relaxed consistency guarantee that you can require and sometimes referred to as eventual consistency. It allows for the maximum possible store performance, but at the high possibility that your application will be operating on stale or out-of-date information.

Example to configure consistency

KVStoreConfig kconfig = new KVStoreConfig(<store_name>,  “hostname_1:port1, hostname_2,port2”);
kconfig.setConsistency(Consistency.ABSOLUTE);
KVStore kvstore = KVStoreFactory.getStore(kconfig);

Using Time-Based Consistency

A time-based consistency policy describes the amount of time that a replica node is allowed to lag behind the leader node. If the replica’s data is more than the specified amount of time out-of-date relative to the leader, then a ConsistencyException is thrown. In that event, you can either abandon the operation, retry it immediately, or pause and then retry it. In order to specify a time-based consistency policy, you use the Consistency.Time class. The constructor for this class requires the following information: permissibleLag, permissibleLagUnits,timeout, timeoutUnit. 

The following example sets a default time-based consistency policy of 2 seconds. The timeout is 4 seconds.

KVStoreConfig kconfig = new KVStoreConfig(<store_name>, “hostname_1:port1,hostname_2,port2”);
Consistency.Time cpolicy = new Consistency.Time(2, TimeUnit.SECONDS, 4, TimeUnit.SECONDS);
kconfig.setConsistency(cpolicy);
KVStore kvstore = KVStoreFactory.getStore(kconfig);

Using Version-Based Consistency

Version-based consistency is used on a per-operation basis. It ensures that a read performed on a replica is at least as current as some previous write performed on the leader. Use of this consistency policy might require that version information be transferred between processes in your application. To create a version-based consistency policy, use the Consistency.Version class. The constructor for this class requires the following information: version,timeout,timeoutUnit

The following code performs a store write, collects the version information, and then uses it to construct a version-based consistency policy.

//Obtain the necessary handles
TableAPI tableH = kvstore.getTableAPI();
Table myTable = tableH.getTable(“myTable”);
Row row = myTable.createRow();
row.put(“itemType”,”Hats”)
   .put(“itemCategory”,”baseball”)
   .put(“itemClass”,”longbill”)
   .put(“itemColor”,”red”)
   .put(“itemSize”,”small”)
   .put(“price”,13.07)
   .put(“inventoryCount”, 107);

// Now write the table to the store, capturing the
// Version information as we do.
Version matchVersion = tableH.put(row, null, null);
Version matchVersion = kvstore.put(myKey, myValue);

 

// Create the consistency policy, using the
// Version object we captured, above.
Consistency.Version versionConsistency = new Consistency.Version(matchVersion,200, TimeUnit.NANOSECONDS);
// Create a ReadOptions using our new consistency policy.
ReadOptions ro = new ReadOptions(versionConsistency, 0, null);
// Now perform the read.
try {
    Row row = tableH.get(key, ro);
    // Do work with the row here
} catch (ConsistencyException ce) {
    // The consistency guarantee was not met
}

 

Configuring Durability Guarantees:

Configuring Durability

A durability guarantee is a policy that describes how strongly persistent your data is in the event of some kind of catastrophic failure within the store. A high durability guarantee means that there is a very high probability that the write operation will be retained in the event of a catastrophic failure. A low durability guarantee means that the write is very unlikely to be retained in the event of a catastrophic failure. The higher your durability guarantee, the slower your write-throughput will be in the store. This is because a high durability guarantee requires a great deal of disk and network activity.

Usually, you want some kind of a durability guarantee, although if you have highly transient data that changes from run-time to run-time, you might want the lowest possible durability guarantee for that data.
Durability guarantees include two types of information: acknowledgment guarantees and synchronization guarantees. Whenever a leader node performs a write operation, it must send that operation to its various replica nodes. The replica nodes then apply the write operation(s) to their local databases so that the replicas are consistent relative to the leader node.

Upon successfully applying write operations to their local databases, replicas in primary zones send an acknowledgment message back to the leader node. This message simply says that the write operation was received and successfully applied to the replica’s local database.
When setting an acknowledgment-based durability policy, you can require acknowledgment from All replicas or No replicas or A simple majority of replicas in primary zones.

Whenever a node performs a write operation, the node must know whether it should wait for the data to be written to stable storage before successfully returning from the operation.
You can control how much of this process the leader node will wait to complete before it returns from the write operation with normal status. There are three different levels of synchronization durability that you can require: NO_SYNC, WRITE_NO_SYNC, SYNC.

Setting Durability Guarantees:

To set a durability guarantee, use the Durability class. When you do this, you must provide three pieces of information: the acknowledgment policy, a synchronization policy at the leader node, a synchronization policy at the replica nodes.

The following example sets the default durability policy for the store.

Durability defaultDurability =
    new Durability(Durability.SyncPolicy.SYNC, // Leader sync
                   Durability.SyncPolicy.NO_SYNC, // Replica sync
                  Durability.ReplicaAckPolicy.SIMPLE_MAJORITY);
kconfig.setDurability(defaultDurability);
KVStore kvstore = KVStoreFactory.getStore(kconfig);

You can override the default Durability setting for a particular operation in the store.

// Construct a durability policy
Durability durability =
      new Durability(Durability.SyncPolicy.NO_SYNC, // Leader
                     Durability.SyncPolicy.NO_SYNC, // Replica
                     Durability.ReplicaAckPolicy.NONE);
// Construct a WriteOptions object using durability policy.
WriteOptions wo = new WriteOptions(durability, 0, null);
// Now write the table to the store using the durability policy defined, above.
tableH.put(row, null, wo);

Check out my other blog How to configure transactions in Oracle NoSQL Database – Part 2 about performing operations on multiple rows of the Oracle NoSQL Database in a single transaction.