Call us: +1-415-738-4000

Adding the Terracotta Server Array

The Terracotta Server Array is an array of in-memory data servers that plug in seamlessly to provide applications with high-performance, low and predictable latency data access to terabytes of in-memory data and enterprise capabilities like high availability, scalability, fault tolerance and more.

Let's try starting up a Terracotta server and connecting our data sets to it.

Preparation

  • Add a valid Terracotta license key to the top level of your Terracotta installation directory. If you don't have one yet, you can register for a license at terracotta.org/downloads/bigmemorymax
  • Add the following jar in the bigmemory-max-4.x.x download kit to your classpath:
    • apis/toolkit/lib/terracotta-toolkit-runtime-ee-4.x.x.jar
  • Add the terracotta configuration to the configuration file (see below)
  • Make sure data classes serializable
  • Start a Terracotta server instance

Create Configuration File

Create a new configuration file called "ehcache-server-array.xml" in your classpath and add a new cache called "server-array" (you can name it anything you want, as long as you use the correct name in your code).

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
     name="SampleConfig" maxBytesLocalHeap="64M">
  <cache name="hello-world"/>
  <cache name="crud"/>
  <cache name="search">
    <searchable>
        <searchAttribute name="height"/>
        <searchAttribute name="weight"/>
        <searchAttribute name="bodyMassIndex" />
    </searchable>
  </cache>
  <cache name="sort">
    <searchable>
        <searchAttribute name="height"/>
        <searchAttribute name="weight"/>
    </searchable>
  </cache>
  <cache name="group">
    <searchable>
        <searchAttribute name="height"/>
        <searchAttribute name="gender"/>
        <searchAttribute name="bmi" expression="value.getBodyMassIndex()"/>
    </searchable>
  </cache>
  <cache name="server-array">
    <searchable>
        <searchAttribute name="height"/>
        <searchAttribute name="gender"/>
        <searchAttribute name="bmi" expression="value.getBodyMassIndex()"/>
    </searchable>
      <!-- Add the terracotta element to this cache. This causes this data
      set to be managed by the Terracotta server array. For the purposes of
      demonstration, we set the consistency to "strong" to ensure that data
      is always consistent across the entire distributed system. There are
      other consistency settings that may be more suitable for different
      data sets and applications. -->
    <terracotta consistency="strong"/>
  </cache>
  <!-- Add the terracottaConfig element to specify where to find the 
  configuration specific to the server array.  In this case, the configuration
  is retrieved from the server array itself. -->
  <terracottaConfig url="localhost:9510"/>
</ehcache>

Create ServerArrayTest.java

Create and compile a Java class called ServerArrayTest:

import java.io.Serializable;
import java.util.List;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.Direction;
import net.sf.ehcache.search.Query;
import net.sf.ehcache.search.Result;
import net.sf.ehcache.search.Results;
import net.sf.ehcache.search.aggregator.Aggregators;

public class ServerArrayTest {

  public static void main(final String[] args) {

    // Create a cache manager using the factory method...
    final CacheManager cacheManager = CacheManager.newInstance(Group.class
        .getResource("/ehcache-server-array.xml"));

    // Retrieve the "sort" data set...
    final Cache dataSet = cacheManager.getCache("server-array");

    // Check to see if there's any data in it yet...
    Element person1 = dataSet.get(1L);
    if (person1 == null) {
      System.out.println("We didn't find any data in the server array."
          + " This must be the first execution.");
      // The data isn't in the server array yet (this must be the first time we
      // ran the program), so let's create some objects...
      final Person janeAusten = new Person(1, "Jane", "Austen", "Female",
          5 * 12 + 7, 130);
      final Person charlesDickens = new Person(2, "Charles", "Dickens", "Male",
          5 * 12 + 9, 160);
      final Person janeDoe = new Person(3, "Jane", "Doe", "Female", 5 * 12 + 5,
          145);
      final Person alexSmith = new Person(4, "Alex", "Smith", "Other",
          5 * 12 + 5, 160);

      // Put the objects into the data set...
      dataSet.put(new Element(janeAusten.getId(), janeAusten));
      dataSet.put(new Element(charlesDickens.getId(), charlesDickens));
      dataSet.put(new Element(janeDoe.getId(), janeDoe));
      dataSet.put(new Element(alexSmith.getId(), alexSmith));
    } else {

      System.out.println("We found data in the server array from a previous"
          + " execution. No need to create new data.");

    }

    // Fetch the search attributes that we'll use in our query and when fetching
    // our results...
    Attribute<Integer> height = dataSet.getSearchAttribute("height");
    Attribute<String> gender = dataSet.getSearchAttribute("gender");
    Attribute<Float> bmi = dataSet.getSearchAttribute("bmi");

    // Create a query object. (This query is designed to select the entire data
    // set.)
    final Query query = dataSet.createQuery().addCriteria(height.gt(5 * 12));

    // Group by the gender attribute so we can perform aggregations on each
    // gender...
    query.addGroupBy(gender);
    query.includeAttribute(gender);

    // Include an aggregation of the average height and bmi of each gender in
    // the results...
    query.includeAggregator(Aggregators.average(height));
    query.includeAggregator(Aggregators.average(bmi));

    // Include the count of the members of each gender
    query.includeAggregator(Aggregators.count());

    // Make the results come back sorted by gender in alphabetical order
    query.addOrderBy(gender, Direction.ASCENDING);

    // Execute the query...
    final Results results = query.execute();

    // Print the results...
    for (Result result : results.all()) {
      String theGender = result.getAttribute(gender);
      List<Object> aggregatorResults = result.getAggregatorResults();
      Float avgHeight = (Float) aggregatorResults.get(0);
      Float avgBMI = (Float) aggregatorResults.get(1);
      Integer count = (Integer) aggregatorResults.get(2);
      System.out.println("Gender: " + theGender + "; count: " + count
          + "; average height: " + avgHeight + "; average BMI: " + avgBMI);
    }
  }

  public static class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String firstName;
    private final String lastName;
    private final long id;
    private final int height;
    private final int weight;
    private String gender;

    public Person(final long id, final String firstName, final String lastName,
        final String gender, final int height, final int weight) {
      this.id = id;
      this.firstName = firstName;
      this.lastName = lastName;
      this.gender = gender;
      this.height = height;
      this.weight = weight;
    }

    public String getFirstName() {
      return firstName;
    }

    public String getLastName() {
      return lastName;
    }

    public String getGender() {
      return gender;
    }

    public int getHeight() {
      return height;
    }

    public int getWeight() {
      return weight;
    }

    public float getBodyMassIndex() {
      return ((float) weight / ((float) height * (float) height)) * 703;
    }

    public long getId() {
      return id;
    }

    public String toString() {
      return "[id=" + id + ", firstName=" + firstName + ", lastName="
          + lastName + ", height=" + height + " in" + ", weight=" + weight
          + " lbs" + ", bmi=" + getBodyMassIndex() + "]";
    }
  }
}

Start a Terracotta Server Instance

In a terminal, go to the "server" directory in your BigMemory Max distribution. Then run the start-tc-server command:

%> cd /path/to/bigmemory-max-4.x.x/server
%> ./bin/start-tc-server.sh

Execute

The first time you run the ServerArrayTest program in a terminal, there is no data in the server array, so you should see output like this:

We didn't find any data in the server array. This must be the first execution.
Gender: Female; count: 2; average height: 66.0; average BMI: 22.242641
Gender: Male; count: 1; average height: 69.0; average BMI: 23.625288
Gender: Other; count: 1; average height: 65.0; average BMI: 26.622484

If you run the ServerArrayTest program again (without restarting the Terracotta server instance that is already running), the data will be automatically retrieved from the server array, so you should see output like this:

We found data in the server array from a previous execution. No need to create new data.
Gender: Female; count: 2; average height: 66.0; average BMI: 22.242641
Gender: Male; count: 1; average height: 69.0; average BMI: 23.625288
Gender: Other; count: 1; average height: 65.0; average BMI: 26.622484

In our default configuration, the server keeps all data in memory only (though, you can add easily add data persistence with a fast restartable store configuration). To clear all data, restart the server before you next run the ServerArrayTest program.

Next Steps

This is just a short example of using BigMemory Max with the full power of the server array to help get you started. Keep in mind that the settings we used here were chosen to make it as easy as possible to get started. We encourage you to read the rest of the documentation and experiment with the different configuration options, features and deployment topologies available to best meet your application's performance and scale needs.