Skip to content

Getting started with Sirius

tbarker9comcast edited this page Apr 21, 2014 · 15 revisions

======

Configuring and instantiating Sirius requires the following steps:

  • implement a RequestHandler, which will build your app-specific in-memory data structures
  • build a configuration object for Sirius
  • create an instance of Sirius using SiriusFactory.createInstance
  • await Sirius initialization

These steps are described in more detail below. The included examples use modified code snippets from our reference applications which implement simple datastores using the Sirius library.

####Implementing a RequestHandler

To use the Sirius library, RequestHandler must be implemented. The RequestHandler has three methods:

  • handleGet(String key) - retrieves data from the backend
  • handlePut(String key, byte[] body) - updates or creates an item
  • handleDelete(String key) - deletes an item

A complex Sirius application will probably have the backend logic separated into other classes, but to keep this example simple the RequestHandler will use a ConcurrentHashMap as the backend implementation. The groovy reference application demonstrates this type of implementation

public class DefaultRequestHandler implements RequestHandler {
    private static final String OK = "ok"

    ConcurrentHashMap backend = [:]

    @Override
    public SiriusResult handleGet(String key) {
        String value = backend.get(key)

        if (value == null) {
            println "[$key] does not have a value"
            return SiriusResult.none()
        }

        println "value for [$key] is [$value]"
        return SiriusResult.some(value)
    }

    @Override
    public SiriusResult handlePut(String key, byte[] body) {
        def value = new String(body)
        backend.put(key, value)

        println "added value [$value] to backend with key [$key]"
        return SiriusResult.some(OK)
    }

    @Override
    public SiriusResult handleDelete(String key) {
        def value = backend.remove(key)

        if (value) {
            println "deleted value [$value] for [$key]"
        } else {
            println "key [$key] does not exist"
        }

        return SiriusResult.some(OK)
    }

}

The RequestHandler implementation is not used directly, but instead it is inserted into Sirius's workflow for updating and retrieving data from the in-memory datastore. Although Sirius does not require a thread safe backend, in order to safely deal with concurrent backend operations (and avoid ConcurrentModificationException ), it is likely the backend will require some kind of synchronized behaviour.

As you can see, Sirius uses a very simple interface for managing a memory based backend. So as long as a backend is maintainable with this interface, an implementor can make the datastore as simple or complex as they need.

####Configuring Sirius

Before booting up Sirius, a SiriusConfiguration needs to be constructed. The simplest SiriusConfiguration would look something like

SiriusConfiguration siriusConfig = new SiriusConfiguration();
siriusConfig.setProp(SiriusConfiguration.HOST(), "localhost");
siriusConfig.setProp(SiriusConfiguration.PORT(), 2552);
siriusConfig.setProp(SiriusConfiguration.CLUSTER_CONFIG(), "/path/to/cluster.config");
siriusConfig.setProp(SiriusConfiguration.LOG_LOCATION(), "/path/to/UberStore");
  • host - host of this Sirius / akka node (see cluster config below)
  • port - port of this Sirius / akka node (see cluster config below)
  • cluster config - path to cluster config containing sirius / akka endpoints of all other nodes
  • log location - path of directory to store the transaction log

A typical cluster config will look something like

# specify actors in cluster, one per line, as akka addresses

akka.tcp://sirius-system@localhost:2552/user/sirius
akka.tcp://sirius-system@localhost:2553/user/sirius
akka.tcp://sirius-system@localhost:2554/user/sirius

The cluster config file must have the same host and port as the configuration. For instance, you can't use the ip in one place and the host in another.

See Configuring Sirius for more configuration details

####Running Sirius

Once RequestHandler and SiriusConfiguration have been implemented and instantiated, use SiriusFactory.createInstance(RequestHandler requestHandler, SiriusConfig siriusConfig) to create a Sirius instance. Before using Sirius, one should wait until it is online. To do this, periodically check the isOnline() method. For example

System.out.println("Waiting for sirius to boot.");
Long waitTime = System.currentTimeMillis() + timeout;
Long sleepTime = 100L;
while (!siriusImpl.isOnline() && System.currentTimeMillis() < waitTime) {
    try {
        Thread.sleep(sleepTime);
    } catch (InterruptedException e) {
        // we're the only ones here, nobody's going to interrupt us.
        // and if it does happen, well, just keep waiting for sirius to start anyway.
    }
}

if (!siriusImpl.isOnline() ) {
    throw new IllegalStateException("Sirius failed to boot in " + timeout + "ms");
}

When issue #17 is done, this should be a lot easier.

####Using Sirius

Sirius has the following methods

  • Future enqueueGet(String key)
  • Future enqueuePut(String key, byte[])
  • Future enqueueDelete(String key)
  • boolean isOnline()

The data operations are very similar to data operations in RequestHandler. When calling enqueuePut or enqueueDelete the following occurs

  • nodes coordinate to reach consensus on ordering
  • data is sent to other nodes to ensure consistency
  • data is persisted to the UberStore
  • appropriate RequestHandler method is called

enqueueGet(String key) is rarely used. Instead gets are generally performed directly against the underlying datastore. The extra serialization steps that Sirius adds when calling enqueueGet(String key) to get the same answer are rarely justified.

To see runnable examples, please checkout our reference applications