Developer

Developer

This section provides a data model overview followed by examples in Python and Java, respectively.

Data Model Overview

The Citrusleaf data model eschews the traditional schema-based relational database model in favor of a much more lightweight approach.  Let's see how it works.

Any piece of data in a namespace is uniquely identified by its ''set'' and its ''key''.  A key is a primary reference to a piece of data: common keys include usernames and session identifiers.  A set is a grouping of common keys.  A key is unique within a set, but the same key could be reused in different sets.
The data referenced by the combination of a set and a key is called a ''record,'' and is organized as a collection of ''bins,'' which are just named values.  In some sense, a bin is analogous to a database column, and all the bins in a record comprise a row.  For example, in the record described above (corresponding to the username of `cbabbage` in the set `Users`) contains six bins.

The contents of bins, or ''values,'' are typed.  The record above illustrates this: some bins contain strings (`username`, `passwordHash`, `password`), some contain integers (`failedLogins`), others contain timestamps (`birthdate`), and still others contain binary data (`picture`).  The available types correspond directly to the most common data types used in software development, and are mapped by the Citrusleaf library directly into the native representation of the calling language where possible.
Citrusleaf's data model is completely schema-free.  If you decide that you need a new set or bin to support a feature, just start using it. If you request a bin not available in this record, the entire transaction does not fail – that bin is simply found to be empty.

Citrusleaf guarantees record-based atomicity of operations: each operation on a record is applied atomically and completely.  For example, a read from multiple bins in a record is guaranteed a consistent view of the record.  After a write is completely applied and the client is notified of success, all subsequent read requests are guaranteed to find the newly written data: there is no possibility of reading stale data.  When a read and a write operation for a record are pending simultaneously, they will be internally serialized before completion, but their precise ordering is not guaranteed. 

An unusual feature of Citrusleaf is the “Compare and swap” functionality. This feature allows reading of a record, then writing with the write succeeding only if no other write has been applied. If an intervening write has been applied, the new write will fail with a compare and swap error, allowing the application to determine the best recovery method (usually to retry the transaction, or to fail). As this feature does not involve an explicit lock, performance is higher and error recovery cases are simpler.

Citrusleaf support automatic eviction of data. A record can be set to time out after a certain period of time, after which it will be purged from the system. This timeout value is set on a record-by-record basis, or can be configured across an entire namespace.  A namespace can also be configured as a cache, specifying a certain quantity of data, and evicting older data when approaching the quota.

Back to top

Example in Python

The Citrusleaf-supplied Python library is compatible with Python 2.5 and 2.6, and has been tested on the CPython platform. Please enquire regarding Python 3.x support and regarding running under other Python versions.

import Citrusleaf
cluster = citrusleaf.Cluster()
cluster.addHost("mycluster.mydomain.com",3000)

The first step is creating a cluster object. This object is injected with an address of any cluster member. That cluster member must be accessible during object creation only. The creation of the object is heavy-weight, and each cluster should have only one cluster object created per process.

Let’s now put some data in the cluster:

bins = [("firstName","brian”),("lastName","bulkowski”),(“age”,41)]
bins.append( ("mobileNumber","302-555-1212") )
bins.append((“lastAccess”,datetime.now())
citrusleaf.put(cluster, "namespace", "users", myGuid, bins)

Putting values is done through any iterable object (set, list, etc). The collection must contain tuples where the first item is a string, and the second item is any supported value type. The data type will be recorded in Citrusleaf.

result_code, kv = citrusleaf.get_all(cluster, "namespace", "users", myGuid)
for bin,value in kv.iteritems():
    print "bin name is:",bin," value is ",value

The get_all method is similar to a SELECT * in SQL. All bins are retrieved as a `dict()`. Notice that the types of values are maintained – when retrieving data, integers stay integers and strings stay strings, even across languages. Compound types are serialized and stored as “python blobs”, which are inaccessible to other languages but extraordinarily useful for single-language applications.

After doing a number of operations, you may consider closing all connections in the pool before allowing Python to clean up the object.

cluster.close()

More details on the Python Client API can be found here. There are also details on the Ruby Client API here.

Back to top

Example of Compare and Swap Usage, in Java

Compare and swap functionality allows complicated operations to be coded in your application’s high level language.
Let’s see how it’s done!

First, let’s import the CitrusleafClient library and connect to the cluster:

import net.citrusleaf.CitrusleafClient;
import net.citrusleaf.CitrusleafResult;
CitrusleafClient cc = new net.citrusleaf.CitrusleafClient(“mycluster.com”, 3000);

Next, let’s set a record with a few different bins, but including a Vector containing the current stock:

CitrusleafResult cr;
Vector<CitrusleafBin> v = new Vector<CitrusleafBin>(3);
v[0] = new CitrusleafBin(“model”, “delSol”);
v[1] = new CitrusleafBin(“make”, “Honda”);
Vector<String> vins = new Vector<String>();
vins.add(“56054AFG341243”);
vins.add(“77041KDD354312”);
v[2] = new CitrusleafBin(“stock”, vins);
cr = cc.set(“namespace”,”cars”,”Honda002”, v);

Let’s update the stock, since we’ve got another car in. Get the complex type, keeping mind of the generation.

cr = cc.get_all(“namespace”, “cars”, “Honda002”);
Vector<String> vins = null;
try {
    vins = (Vector<String>) cr.results.Get(“stock”);
}
catch (ClassCastException ex) {
    log.Debug(“wrong type in bin stock”);
   return;
}

Then update your complex type in any way you see fit.

vins.add(“0092345JFGKD234”);

And write it back, but make sure to include the generation.

cr = cc.set(“namespace”,”cars”,”Honda002”, “stock”, vins, cr.generation);
if (cr.resultCode == ResultCode.OK) {
    System.out.println(“success!”);
} else if (cr.resultCode  == ResultCode.GENERATION_ERROR) {
   System.out.println(“simultaneous add, try again!”);
}

Back to top