Call us: +1-415-738-4000

BigMemory C++ Client API

Cross-Language API

This section covers connecting to, working with, and searching your BigMemory data. For the complete class library documentation, refer to the API documentation in the /apis/csharp/apidoc-cpp.zip directory of the kit.

Connecting with the CL Connector

Your application will communicate with the CL Connector via the CacheManager interface.

Begin by creating a Configuration object. For a deployment where the BigMemory Client is located on the same machine as the CL Connector, use a configuration with the Nirvana Shared Memory (SHM) transport:

Configuration("/dev/shm", 16);

For a deployment where the BigMemory Client and the CL Connector are located on different machines, use a configuration with the Nirvana socket transport. For example, here we create a configuration that connects to the local host at port 8199, and provisions a pool of 16 connections to share:

Configuration("localhost", 8199, 16);

Notice that configurations with different transport types require different parameters:

  • Nirvana Shared Memory (SHM)Configuration("SHMLocation", clientPoolSize) — generally used when the BigMemory Client is located on the same machine as the CL Connector.
  • Nirvana socketConfiguration("hostName", portNumber, clientPoolSize) — used when the CL Connector and the BigMemory Client are located on different machines.

Once you have your Configuration instance ready, create a CacheManager using the XPlatform static method:

CacheManager cacheManager = XPlatform::createCacheManager(config);

Shutting the connection down

The CacheManager needs to be closed in order for it to release all resources (like connections). Close it by invoking the destructor:

delete cacheManager;

Accessing a Cache

Once you’ve obtained a reference to your CacheManager, it is now connected to your CL Connector and will let you access any Cache known by the CL Connector.

To retrieve a thread-safe instance of a Cache, you will need its name, the type of the key, the value, and a serializer for those types.

Serializers

The protocol recognizes the following types:

  • bool: A boolean value (true or false), one byte
  • byte: A signed byte
  • int: An 8-bit signed integer
  • i16: A 16-bit signed integer
  • i32: A 32-bit signed integer
  • i64: A 64-bit signed integer
  • double: A 64-bit floating point number
  • byte[]: An array of bytes
  • string: Encoding agnostic text or binary string

If you want to send complex types to the CL Connector, you will have to provide a serializer that can serialize your complex key and value types for a Cache.

Say we have the following Class, which is our Value type:

class User{
private: long Id;
    std::string Login;
    std::string Email;

public:  int getID() { return Id; }
                    void setID(int id) { Id = id; }
                    string getEmail() { return Email; }
                    void setEmail(int email) { Email = email; }
                    string getLogin() { return Login; }
                    void setLogin(string name) { Login = name; }
};

We store these keyed to the login, as a string. We now need to provide a serializer like this:

class UserCacheSerializer : public CacheSerializer<std::string, User>
 {

    ValueObject serializeKey(std::string &deserializedKey)
    {
        return ValueObject::stringValue(deserializedKey);
    }

    std::string deserializeKey(ValueObject &serializedKey)
    {
        return serializedKey.getString();
    }

    CacheValue serializeValue(User objectValue)
    {
        std::map<std::string, ValueObject> nvpairs;

        nvpairs["id"] = ValueObject::int8Value(objectValue.Id);
        nvpairs["email"] = ValueObject::stringValue(objectValue.Email);

        CacheValue value = CacheValue();
        value.Value = ValueObject::stringValue(objectValue.Login);
        value.setNvPairs(nvpairs);
        return value;
    }

    User deserializeValue(CacheValue serializedValue)
    {
        User user;
        user.Login = serializedValue.getValue().getString();
        user.Id = serializedValue.getNvPairs()["id"].getInt8();
        user.Email = serializedValue.serializedValue.getNvPairs()["email"].getString();
        return user;
    }
}

Note that both serialize and deserialize methods (whether for key or value) are kept symmetric.

CacheValue is composed of the actual ValueObject (in this example, the string representation of the user’s login). CacheValue can hold entire object graphs serialized with whatever serialization strategies fits your needs. It also holds an arbitrary amount of name/value pairs. The names are std::string, while the values are of type ValueObject. You can use these name-value pairs to store whatever you want. Note that these name-value pairs also become indexable and searchable using the search API.

A byte array is not indexable, however it can be used within name-value pairs. In the above example, both user id and email are stored as byte arrays. Even though byte arrays are not indexable, search can be enabled on the attributes. Since we use the login as the key in our use case, storing that as a binary representation within the “default unindexed value” is good enough.

Note: The RawCache class, available directly from the CacheManager, allows you to access raw data, i.e., ValueObject as keys, and CacheValue as values. RawCache can be used when your data does not need to be serialized, and it allows you to create a cache without a serializer.

Key-based store and retrieve

Now that we have defined a CacheSerializer for our cache, we can retrieve the cache from the CacheManager. The example below assumes that we have defined a cache named “userCache” within the ehcache.xml file used to configure the CL Connector.

// create a serializer
CacheSerializer<string, User> *serializer = new userCacheSerializer();

// get a cache 
Cache<string, User> users = cacheManager->getCache("userCache", *serializer);

// make a user
User user1;
user1.Id = 1;
user1.Email = "someone@somewhere";
user1.Login = "secretpasswd";

// put the user
users->put(user1.Login, user1, type);

The following is an example for the Recipe code sample (example03). Recipe is the class defining the domain object, and RecipeProtoBufSerializer() is the serializer class.

Cache<string, Recipe> *cache = cacheManager->getCache("TestCache", new RecipeProtoBufSerializer());
cache->put(rec.Name, rec, type);

The type attribute calls the consistency, configured as either strong or eventual, with which you expect the CL Connector to execute the operation. For more information, refer to the Consistency section below.

To retrieve by key, invoke the get method:

 User someUser = userCache->get(someLogin, type);

An example of retrieving, using the Recipe demo:

Recipe rec = (Recipe)cache->get(args[1], type);

Compare and Swap (CAS) Operations

The following CAS operations are provided in C++:

  • putIfAbsent(key_type_ref key, value_type_ref cacheValue) — Puts the specified key/value pair into the cache only if the key has no currently assigned value. Unlike put, which can replace an existing key/value pair, putIfAbsent creates the key/value pair only if it is not present in the cache.

  • remove(key_type_ref key, value_type_ref value) — Conditionally removes the specified key, if it is mapped to the specified value.

  • replace(key_type_ref key, value_type_ref value) — Maps the specified key to the specified value, if the key is currently mapped to some value.

  • replace(key_type_ref key, value_type_ref oldValue, value_type_ref newValue) — Conditionally replaces the specified key's old value with the new value, if the currently existing value matches the old value.

For more information about atomic operations and cache consistency, refer to the Consistency section below.

This section provides an overview of how to do cross-language searches of PNF data stored within BigMemory. More detailed information may be found on the BigMemory Search Setup page.

In order to search BigMemory data, you first need to add the <searchable/> tag to the cache configuration in your ehcache.xml file.

<ehcache>
(...)
  <cache name="userCache">
  (...)
    <searchable/>
  </cache>
</ehcache>

This configuration will scan all keys and values in the cache and, if they are of supported search types, add them as search attributes. Values of the Name/Value pairs in the PNF’s ValueObject sent to the CL Connector, other than of type byte[], can all be indexed and made searchable. (Continuing with the example of the previous section, this would mean id and email.)

You can also add search attributes using the provided attributeExtractor class, for example:

<ehcache>
(...)
  <cache name="userCache">
  (...)
    <searchable>
      <searchAttribute name="email" class="net.sf.ehcache.xplatform.search.Indexer"/>
    </searchable>
  </cache>
</ehcache>

To perform a query, use the Cache.query(String) method, providing a BigMemory SQL query string, for example:

SearchResults<string, User> result = userCache->query("select * from userCache where email ilike '%@terracottatech.com'");

For more information about how to construct BigMemory SQL queries, refer to the BigMemory SQL Queries page.

Note: Java (POJO) data stored within BigMemory can also be searched using the native Ehcache Search API, which is documented on the BigMemory Max Search API page.

Consistency

Consistency is set in the ehcache.xml configuration file, and it has to do with how the cache you connect to is clustered on the CL Connector. A cache clustered using strong consistency will only be able to have strong operations performed on it, meaning that changes made by any node in the cluster are visible instantly. Atomic operations (for example, putIfAbsent) are always performed in the strong mode. A cache clustered using eventual consistency, on the other hand, will be able to perform both strong and eventual operations, and changes made by any node are visible to other nodes eventually. To understand consistency at the Terracotta clustering level, refer to the BigMemory documentation.

In the Cross-Language API, the ConsistencyType attribute sets the consistency, strong or eventual, with which you expect the CL Connector to execute an operation. For example:

Consistency::Type type = Consistency::STRONG;

All operations on a cache that can be performed using either strong or eventual consistency take type as an additional parameter. Other than a few operations such as atomic ops, RemoveAll, and queries, most regular cache operations accept this last parameter.

When set to STRONG, data in the cache remains consistent across the cluster at all times. This mode guarantees that a read gets an updated value only after all write operations to that value are completed, and it guarantees that each put operation is in a separate transaction. When set to EVENTUAL, reads without locks are allowed, and read/write performance is substantially boosted, but at the cost of potentially having an inconsistent cache for brief periods of time.

If you try to execute an eventually consistent operation on a strong cache, you will get an exception. Executing a strongly consistent operations on an eventual cache is allowed but will result in locks being used on the CL Connector, and your method will return only when all of the steps are finished (acquiring the appropriate lock, performing the operation, and releasing the lock).