Create Stored Tasks on Infinispan 8

This tutorial will teach you how to use Stored Tasks in Infinispan 8 which can be executed from an Hot Rod Client, much the same way you can execute a Stored Procedure on a Database

Stored Tasks are functions which can be deployed on a Server cache and executed locally. They are especially useful in a client-server Hot rod communication since:

  • They allow using Transaction API within your scripts, so you can trigger transactions from an Hot Rod Client
  • Scripts are executed remotely on the Server, thus enhancing the performance of your applications

Creating your first Stored Task

Start your Infinispan 8 Server at first:

[francesco@localhost bin]$ ./standalone.sh

You will see from the server logs that a special cache, named "___script_cache" has been started:

INFO  [org.jboss.as.clustering.infinispan] (MSC service thread 1-2) DGISPN0001: Started ___script_cache cache from clustered container
INFO  [org.jboss.as.clustering.infinispan] (MSC service thread 1-2) DGISPN0001: Started ___hotRodTopologyCache cache from clustered container
INFO  [org.infinispan.rest.NettyRestServer] (MSC service thread 1-8) ISPN012003: REST server starting, listening on 127.0.0.1:8080
INFO  [org.infinispan.server.endpoint] (MSC service thread 1-8) DGENDPT10002: REST mapped to /rest
INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Infinispan Server 8.1.0.CR1 (WildFly Core 2.0.0.CR8) started in 21108ms - Started 186 of 255 services (139 services are lazy, passive or on-demand)

Now we will code a Stored Task. A Stored Task right now can be coded using Nashhorn's Javascript engine available in JVM. In future versions you could use as well Java for your scripts. So here is a sample script:

// mode=local,language=javascript
var cache = cacheManager.getCache();
cache.get("k");

The above script does nothing fancy: it just recovers the default cache and retrieves the key "k" from it. The mode property on the top instructs the execution engine where we want to run the script: local for running the script on the node that is handling the request and distributed for running the script wrapped by a distributed executor.

Here is a more complex example:

mode=local,language=javascript
key*value
var cache = cacheManager.getCache("default");
cache.clear();
cache.put(marshaller.objectToByteBuffer(key)), marshaller.objectToByteBuffer(value));

In this example we have declared two parameters (key,value) which will be passed to the function. Next, since the current version of the Script Engine transfer parameters as byte streams, we need some Marshalling before inserting them into the cache. Here is a full Java example which can be used to trigger this example:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import java.util.Set;
import java.util.UUID;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;

public class Demo1 {

    private RemoteCacheManager cacheManager;
    private RemoteCache<String, Object> cache;

    public Demo1() {
        String script = "// mode=local,language=javascript\n"
                + "key*value\n"
                + "var cache = cacheManager.getCache(\"default\");\n"
                + "cache.clear();\n"
                + "cache.put(marshaller.objectToByteBuffer(key), marshaller.objectToByteBuffer(value));";

        ConfigurationBuilder builder = new ConfigurationBuilder();
        builder.addServer()
                .host("127.0.0.1")
                .port(Integer.parseInt("11222"));
        cacheManager = new RemoteCacheManager(builder.build());
        cache = cacheManager.getCache("default");

        RemoteCache<String, String> scriptCache = cacheManager.getCache("___script_cache");

        scriptCache.put("script.js", script);

        Map<String, Object> params = new HashMap<>();
        params.put("key", UUID.randomUUID().toString());
        params.put("value", UUID.randomUUID().toString());

        Object result = cache.execute("script.js", params);

        System.out.println("Dumping cache Data");
        System.out.println("==========================");

        Set set = this.cache.keySet();
        Iterator i = set.iterator();
        while (i.hasNext()) {
            String key = (String) i.next();
            System.out.println("[ key: " + key + " - value: " + cache.get(key));
        }

    }


    public static void main(String[] args) {
        new Demo1();
    }

}

So in this simple class we load the js function in the script_cache with the name "script.js". Then we execute the script using cache.execute API which receives as input the Map of parameters. Pretty cool isn't it?

Using the Transaction API in Script Tasks

Another advantage of using Stored Tasks is that you can manage Transaction boundaries from within your script. This allows in practice to make your Hot rod client-server communication transactional. Here follows a simple script which adds one key, then starts a transaction and rolls back the cache operations executed within the Transaction:

String script = "// mode=local,language=javascript\n"
        + "value * value2\n"
        + "var cache = cacheManager.getCache(\"default\");\n"
        + "var tm = cache.getTransactionManager();\n"
        + "cache.clear();\n"
        + "cache.put(marshaller.objectToByteBuffer(\"k0\"), marshaller.objectToByteBuffer(value));\n"
        + "tm.begin();\n"
        + "cache.put(marshaller.objectToByteBuffer(\"k1\"), marshaller.objectToByteBuffer(value2));\n"
        + "tm.rollback();";

Many thanks to Tristan Tarrant for providing details about Stored Script execution on the Infinispan blog: http://blog.infinispan.org/2015/10/stored-script-execution.html

Follow us on Twitter