Rick

Rick
Rick

Saturday, December 12, 2015

Micro-batching and QBit - tuning micro-batches

Intro

I have been working on QBit for two years or so. Not me alone, but others as well. QBit has improved leaps and bounds and we have used it in anger on several projects, and it continues to improve. I like to tell you how it all started, and then share some code talk and perf tests.

Early days

QBit started by working on a project where we had a CPU intensive user preference engine running in memory. The engine could calculate around 30 thousand user requests a second. It looks at user actions which are summarized and stored in memory, and then would generate a list of recommendations with some rules based mix-ins based on users stated preferences versus their actual actions. I did not design the algorithm. I did rewrite most of the original with a focus on basic performance. There was a working prototype but it would not handle the load we were expecting (the way it was written).
The application could get up to 100 million users. And the users would not trickle in as there were peak hours in a week when almost all of the users would be using the system, and then the system would slow down to just a trickle for most of the week. In production, I think the application never had more than 13 million users (not sure about this at most it was 20 million) but we had to assume the worse (or best).
We tried several approaches. We ended up using Vertx and running 9 verticles (this is based on my memory of the project ). We had an IO vertical and then 8 verticles that were running the rules engine. The first issue was we could not get much throughput. Each front end request would look at the user id, hash it, and then pick one of the 8 verticle running the rules preference engine. In a tight loop, the rules preference engine could easily do 30K requests per second (generating a recommendation list), but in this scenario, the whole system with 8 verticles could only do 15K to 30K requests. We used wrk and some custom Lua scripts to pound out the worst case scenario. (A vertical in Vertx is like a module that has its own event loop).
I thought about working on making the preference engine faster. But it seemed to me that the real problem or rather the problem that was burning us the most was the ability to get more requests into preference engine at a time. Also since we were constantly modifying the engine as the requirements changed, I felt optimizing it would be waste when we were having a hard time using what we had because it was hard to bridge the gap of time spent in message passing. Also we tried several designs and the project was on a tight timeframe, we needed some quick wins or we were dead in the water.
I read through the Mechanical Sympathy blog prior to this project and papers as best I could and I already employed ideas and techniques from the ideas in production to great effect. I also had a few mentors in this space. This was not my first time to bat with these ideas. I had some previous success but this project was the primary driver for QBit so I talk about it.
My idea for this project was simple. The workload in my opinion was hard to adapt to a disruptor (at least hard based on my current skill-set at the time, and I am still not sure it made sense for this use-case, but I leave the idea open for future experimentation). My idea was pretty simple, what if I sent a list of requests instead of a single request to the rules engine (which were running in-memory and we were using the Vertx Event bus to issue requests). Under peak load each machine could get thousands of requests per second (not just to generate the list of recommendations but to update the stats that influenced the generation of the recommendations). The application had to be fast, but we needed it to be flexible as well as the requirements were changing all of the time (even days before launch). Later this was also some logic for the rules engine to say hey, I am pretty busy, make the request lists bigger. This seemed to work well. We were allowing the back pressure to control the batch size.
We came up with what I called at the time, poor man's disruptor. It was a confluence of ideas from reading Akka in ActionLMAX papers, working with Vertx and just my own empirical observations. Now after hearing a few talks on Apache Spark, I think the more correct term is micro-batching, but I did not know that at the time. I used micro-batching on another project before this one which was called the disk-batcher which was able to write users events to disk at a rate of 720 MB per second, but this was the first time I used the technique for a CPU intensive application.
The short of it is this, it worked. It was not perfect, but we were able to improve the per server throughput from 30K TPS to 150K TPS to later 200K TPS. Later we would employ the same sort of technique to send event data to be stored into the backend (data backup) and to stream users into memory as the load increased. We got these numbers while employing a full array of servers getting hit by a cloud load testing company.
A similar application was in production that was using 2,000 servers. Another application was in production that was using 150 servers at a competitor. Our final product could have run on six servers or less in production, we ran it on 13 for reliability. It could handle 10x to 100x the load as the other solutions. It was truly a high-speed in-memory microservice. The actual load never reached what it could handle. It seemed like whenever we had a problem, micro-batching was the way to solve it at scale.
By the way, I promised I could run the whole service on three servers in production so I missed, but striving for a higher goal made me focused. If I had twice the time to work on the project, maybe we could have, but there was a time to market aspect as well. It was not just that the code had to run fast. We had to get the project done fast so the real trick was finding the right techniques to meet the goals.
The idea behind micro-batching is simple, at least in my definition of it. Attempt to increase throughput by reducing thread hand-off of messages, by sending many messages at one time. If a certain period of time passes, send what you have, if you are under heavy load, only send what you have if you have reached the batch size or the timeout has been reached. You can also send what you have if the processing side tells you it is not busy or you can create larger batches if the processing side (or your response monitoring) tells you that the processor is bigger. If possible under larger load, detect this state and create larger batches. Micro-batching makes the application slower under light load, but much, much faster under high-load. The trick is how to balance and tweak the batch size and the batch time, timeout (and set up back pressure signals). I don’t think this fully solves this problem, but we created something that works and that we have employed it to do things and handle load that surprises a lot of people.
One of the issues we had was a lot of the micro-batching was hand-coded (by me). I had the same sort of idea repeated in six or seven areas of the application with slight modifications. All hand tuned. Then we started to create a reusable library that I called QBit. It had nothing to do with REST per se. (Although I also wrote a lot of code to handle REST and JSON parsing for the various calls that this service ended up supporting).
I threw must of the original code away, and started QBit from a clean slate. Early QBit code exists in a few projects but QBit is on github.
Using QBit on a recent project after this one, we did some initial load testing and we were handling 10x more load than we actually get in production so we did not have a chance to tune it, which I honestly think we could have increased the performance by another 10x (the app not QBit) but there was no need (which was a bit of a disappointment for me). QBit was good enough out of the box.
The first application was fairly cool (pre-QBit). We used Vertx at scale. We implemented in-memory CPU intensive applications at scale. We employed micro-batching. The ideas from that project are in QBit. QBit could not exist without that experience, and would not be where is was today without working on several other applications with QBit since then.
One issue we had in making the approaches more wide spread was other developers were not familiar with async programming. And there was no easy way to do things like define a REST endpoint (pre-QBit), and coordinate calls to other async services (in-memory and remote). It was still easier to write applications using traditional Spring MVC and if the application did not need the throughput writing it using Vertx and the hodgepodge of libs we wrote (pre QBit) did not make sense. For high-load application, it was worth it. For a smaller load application, it was not.
QBit was the framework I started to make applications like we built easier to produce. I find that small-load services over time, often start getting a lot more load. I could write a whole article on this point. But basically if you write a useful service and others (in the same company) or others start integrating it in new ways with new load demands that you never expected, then it is better to have a lot of head-room. QBit tries to make async, microservice development easier so you can even write your smaller services with it.

Detour Microservices, reactive programming and 12 factor applications

What does QBit have to do with Microservices. When we came up with the design for the preference engine, people said it was a Microservice. This was a while ago.
I ignore all such terms as Microservice until someone tells me what it means. It saves time. Then I go and study it. There are so many ideas out there to study, and if you chased them all down, there would be no time to write code. I learn by doing. Reading and then doing some more. To me the ideas behind Microservices really clicked.
Apparently, I had been on many projects that employed Microservice ideas before I knew what the term meant. I thought I was doing Restful SOA, simple SOA or whatever. Once I found out what Microservices meant, I was sure that this is what I wanted to encourage with QBit.

12 Factor Microservices

More recently people have got me interested in reactive streaming, reactive programming and 12-factor microservice development. Again, many of these things we have been doing in one shape, form or another but giving the ideas name gives them power as they are easier to communicate. For a while now, after working on some of these high-speed, cloud deployed, services, I have been wanting to add (and have added in ad hoc ideas) many of the concepts from these ideas into QBIt and the application that we have written with QBit and Vertx. I see that Vertx 3 added a lot of the same things that I added to QBit for microservice monitoring. The idea for 12-factor microservice development time has come. Vertx 3 did such a job, that I decided to make it so you could embed QBit inside of Vertx (again as this was how it was originally) as well as use Vertx as a network lib.

Back to micro-batching

QBit support micro-batching. It is built into QBit’s core. One of the first set of experiments I did with QBit was try to find a decently fast implementation of micro-batching (although at the time I did not know the term was even called micro-batching and I was calling it a batching queue or the poor man’s disruptor).

Code walk through

Let's do some perf testing.
The very core of QBit is the queue. Use these flags when starting up the garbage collector.

Setup garbage collector

-Xms4g -Xmx4g -XX:+UseG1GC

Trade class

    public class Trade {
        final String name;
        final long amount;

        public Trade(String name, long amount) {
            this.name = name;
            this.amount = amount;
        }

        public String getName() {
            return name;
        }

        public long getAmount() {
            return amount;
        }
    }
We have a simple trade class. We will then send this through the queue at a rate of 100 million a second. Here is how we construct the queue.

Create a queue

        final QueueBuilder queueBuilder = QueueBuilder
                .queueBuilder()
                    .setName("trades")
                    .setBatchSize(batchSize)
                    .setSize(size)
                    .setPollWait(pollWait);


        final Queue<Trade> queue = queueBuilder.build();
The size is the size of the underlying java util queue (if appropriate). The batch size is how many messages that we are sending each time. Unless flush is called. The poll wait is how long you want to wait (poll wait) after you get a null from a poll.
To listen to trades coming into the queue, we will use a simple mechanism.

Increment an atomic integer

        final AtomicLong tradeCounter = new AtomicLong();

        queue.startListener(item -> {

            tradeCounter.incrementAndGet();
        });
We could make this more efficient.
The micro batching is on the client side of the equation.

SendQueue

        final SendQueue<Trade> tradeSendQueue = queue.sendQueue();
        for (int c = 0; c < tradeCount; c++) {
            tradeSendQueue.send(new Trade("ibm", 100L));
        }
        tradeSendQueue.flushSends();
We created a helper method to run messages through the queue (over and over).

Method to run our perf test

private static void run(int runs, int tradeCount, 
                                   int batchSize, int checkEvery, 
                                   int numThreads, int pollWait, 
                                   int size) {

        final QueueBuilder queueBuilder = QueueBuilder
                .queueBuilder()
                    .setName("trades")
                    .setBatchSize(batchSize)
                    .setSize(size)
                    .setPollWait(pollWait);
You can specify the numThreads (how many threads), runs (how many runs), and the parameters we talked about before.
With this setup:

Perf test

    public static void main(final String... args) throws Exception {

        final int runs = 76;
        final int tradeCount = 220_000;
        final int batchSize = 1_000;
        final int checkEvery = 0;
        final int numThreads = 6;
        final int pollWait = 1_000;
        final int size = 1_000_000;

        for (int index = 0; index < 100; index++) {
            run(runs, tradeCount, batchSize, checkEvery, numThreads, pollWait, size);
        }
    }
With this, we were able to get this:
DONE traded 100,320,000 in 1001 ms 
batchSize = 1,000, checkEvery = 0, threads= 6

DONE traded 100,320,000 in 999 ms 
batchSize = 1,000, checkEvery = 0, threads= 6 

DONE traded 100,320,000 in 987 ms 
batchSize = 1,000, checkEvery = 0, threads= 6 
It takes ten or so runs for the GC etc. to tune itself.
We consistently get over 100M messages per second. It took a while to tweak the runs, tradeCount, etc. to get 100M messages per second. If we drop to 50,000 we can be a lot more flexible with number of threads, trade count per thread, etc.
Here is the complete code listing to get 100M TPS.

Complete code listing for 100 M TPS test

package io.advantageous.qbit.example.perf;

import io.advantageous.boon.core.Sys;
import io.advantageous.qbit.queue.Queue;
import io.advantageous.qbit.queue.QueueBuilder;
import io.advantageous.qbit.queue.SendQueue;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

public class QueuePerfMain {

    public static class Trade {
        final String name;
        final long amount;

        public Trade(String name, long amount) {
            this.name = name;
            this.amount = amount;
        }

        public String getName() {
            return name;
        }

        public long getAmount() {
            return amount;
        }
    }

    public static void main(final String... args) throws Exception {

        final int runs = 76;
        final int tradeCount = 220_000;
        final int batchSize = 1_000;
        final int checkEvery = 0;
        final int numThreads = 6;
        final int pollWait = 1_000;
        final int size = 1_000_000;


        for (int index = 0; index < 10; index++) {
            run(2, 1000, 10, 3, 2, 100, size);
        }

        for (int index = 0; index < 100; index++) {
            run(runs, tradeCount, batchSize, checkEvery, numThreads, pollWait, size);
        }
    }


    private static void run(int runs, int tradeCount, int batchSize, int checkEvery, int numThreads, int pollWait, int size) {

        final QueueBuilder queueBuilder = QueueBuilder
                .queueBuilder()
                    .setName("trades")
                    .setBatchSize(batchSize)
                    .setSize(size)
                    .setPollWait(pollWait);


        final int totalTrades = tradeCount * runs * numThreads;

        if (checkEvery > 0) {
            queueBuilder.setLinkTransferQueue();
            queueBuilder.setCheckEvery(checkEvery);
            queueBuilder.setBatchSize(batchSize);
        }

        final Queue<Trade> queue = queueBuilder.build();
        final AtomicLong tradeCounter = new AtomicLong();

        queue.startListener(item -> {
            item.getAmount();
            item.getName();
            tradeCounter.incrementAndGet();
        });


        final long startRun = System.currentTimeMillis();

        for (int r = 0; r < runs; r++) {
            runThreads(tradeCount, numThreads, queue, tradeCounter);
        }
        System.out.printf("DONE traded %,d in %d ms \nbatchSize = %,d, checkEvery = %,d, threads= %,d \n\n",
                totalTrades,
                System.currentTimeMillis() - startRun,
                batchSize,
                checkEvery,
                numThreads);
        queue.stop();

    }

    private static void runThreads(int tradeCount, int numThreads, Queue<Trade> queue, AtomicLong tradeCounter) {
        final List<Thread> threads = new ArrayList<>();
        for (int t = 0; t < numThreads; t++) {

            final Thread thread = new Thread(() -> {
                sendMessages(queue, tradeCount);
            });
            thread.start();
            threads.add(thread);
        }
        for (int index = 0; index < 100000; index++) {
            Sys.sleep(10);
            if (tradeCounter.get() >= (tradeCount * numThreads)) {
                break;
            }
        }
    }

    private static void sendMessages(final Queue<Trade> queue, final int tradeCount) {
        final SendQueue<Trade> tradeSendQueue = queue.sendQueue();
        for (int c = 0; c < tradeCount; c++) {
            tradeSendQueue.send(new Trade("ibm", 100L));
        }
        tradeSendQueue.flushSends();
    }


}

There are ways to optimize the test. I have similar tests running up to 200M TPS but the code is a lot harder to follow. This is fairly decent speed and the code is easy to follow and explain.
To compare micro-batching to not using micro-batching, I reduce the messages to 48 million instead of 100 million. I tried running no batching through 100 million and it seem to hang for a long time. I am sure it would have finished eventually. But I am not that patient.
It takes no batching about 3000 milliseconds to process 48 million messages. Fairly consistently with a really wide standard deviation. But a batch size of 1000, yields 550 milliseconds to process with a very tight standard deviation. This becomes more pronounced as the service becomes more CPU intensive.
More to come.

Monday, November 30, 2015

CallbackBuilder and generics for Reactive Java Microservices

The CallbackBuilder is used to create callbacks. Callbacks have error handlers, timeout handlers and return handlers.

Setting up error handlers, timeout handlers and callback handlers with a callback builder.

                callbackBuilder
                .setCallback(ResultSet.class, resultSet -> 
                                             statusCallback.accept(resultSet!=null))
                .setOnTimeout(() -> statusCallback.accept(false))
                .setOnError(error -> statusCallback.onError(error))
                .build(ResultSet.class);

        this.addEventStorageRecordAsync(callbackBuilder.build(), storageRec);
The CallbackBuilder has many helper methods to help you with dealing with common Java types like OptionalMapListCollectionSetString, primitive types and wrappers.
This allows you to quickly build callbacks without navigating the complexity of Generics. Let's cover a small example.
First let's define a basic service that uses lists, maps and optional.

Basic service to drive the example

package io.advantageous.qbit.example.callback;


import io.advantageous.boon.core.Lists;
import io.advantageous.boon.core.Maps;
import io.advantageous.qbit.reactive.Callback;

import java.util.List;
import java.util.Map;
import java.util.Optional;

public class EmployeeServiceImpl implements EmployeeService {

    @Override
    public void getEmployeesAsMap(final Callback<Map<String, Employee>> empMapCallback) {

        empMapCallback.returnThis(Maps.map("rick", new Employee("Rick")));
    }

    @Override
    public void getEmployeesAsList(final Callback<List<Employee>> empListCallback) {

        empListCallback.returnThis(Lists.list(new Employee("Rick")));
    }


    @Override
    public void findEmployeeByName(final Callback<Optional<Employee>> employeeCallback,
                                   final String name) {

        if (name.equals("Rick")) {
            employeeCallback.returnThis(Optional.of(new Employee("Rick")));
        } else {
            employeeCallback.returnThis(Optional.empty());
        }
    }

}
The interface for the above looks like this:

Basic interface to drive the example

package io.advantageous.qbit.example.callback;

import io.advantageous.qbit.reactive.Callback;

import java.util.List;
import java.util.Map;
import java.util.Optional;

public interface EmployeeService {
    void getEmployeesAsMap(Callback<Map<String, Employee>> empMapCallback);

    void getEmployeesAsList(Callback<List<Employee>> empListCallback);

    void findEmployeeByName(Callback<Optional<Employee>> employeeCallback,
                            String name);
}
If you are familiar with QBit, all of the above should already make sense. If not, I suggest going through the home page of the WIKI and coming back here after you skim it.
To show how to use the CallbackBuilder we will define a basic Rest service calledCompanyRestService.

CompanyRestService to demonstrate CallbackBuilder

/**
 * To access this service
 * curl http://localhost:8080/emap
 {"rick":{"name":"Rick"}}
 */
@RequestMapping("/")
public class CompanyRestService {

    private final Logger logger = LoggerFactory.getLogger(CompanyRestService.class);
    private final EmployeeService employeeService;

    public CompanyRestService(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

...

    @QueueCallback({QueueCallbackType.EMPTY, QueueCallbackType.LIMIT})
    public void process(){
        ServiceProxyUtils.flushServiceProxy(employeeService);
    }


QBit uses micro-batching which helps optimize message passing between service queues (service actors). The QueueCallback annotation allows us to capture when our request queue is empty or when it has hit its limit. The limit is usually the batch size but could be other things like hitting an important message. Whenever we hit our limit or when are request queue is empty, we go ahead and flush things to the downstream service by callingServiceProxyUtils.flushServiceProxy. This should be mostly review.
As you can see, the EmployeeService has a lot of methods that take Generic types likeOptionalList and Map. When we want to call a downstream service that is going to return a map, list or optional, we have helper methods to make the construction of the callback easier.

CompanyRestService Calling getEmployeesAsMap using CallbackBuilder.wrap

@RequestMapping("/")
public class CompanyRestService {

...
    @RequestMapping("/emap")
    public void employeeMap(final Callback<Map<String, Employee>> empMapCallback) {

        final CallbackBuilder callbackBuilder = CallbackBuilder.newCallbackBuilder();
        callbackBuilder.wrap(empMapCallback); //Forward to error handling, timeout, and callback defined in empMapCallback
        employeeService.getEmployeesAsMap(callbackBuilder.build());

    }
In this case, we use the wrap method. This will forward errors, timeouts and the callback return to the empMapCallback.
To run this, we need to start up the application.

Starting up the REST application.

    public static void main(final String... args) throws Exception {

        /** Create a ManagedServiceBuilder which simplifies QBit wiring. */
        final ManagedServiceBuilder managedServiceBuilder = ManagedServiceBuilder.managedServiceBuilder().setRootURI("/");
        managedServiceBuilder.enableLoggingMappedDiagnosticContext();

        /** Create a service queue for the employee service. */
        final ServiceQueue employeeServiceQueue = managedServiceBuilder.createServiceBuilderForServiceObject(
                new EmployeeServiceImpl()).buildAndStartAll();

        /** Add a CompanyRestService passing it a client proxy to the employee service. */
        managedServiceBuilder.addEndpointService(
                new CompanyRestService(employeeServiceQueue.createProxy(EmployeeService.class)));

        /** Start the server. */
        managedServiceBuilder.startApplication();

    }
If we wanted to copy and mutate the map before we serialized it, we could use the withMapCallback to capture the async return, i.e., Callback from the employeeService.

CompanyRestService using withMapCallback and delegate

@RequestMapping("/")
public class CompanyRestService {
...

    @RequestMapping("/emap2")
    public void employeeMap2(final Callback<Map<String, Employee>> empMapCallback) {

        final CallbackBuilder callbackBuilder = CallbackBuilder.newCallbackBuilder();
        callbackBuilder.delegate(empMapCallback); //Forward to error handling and timeout defined in empMapCallback

        callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
            logger.info("GET MAP {}", employeeMap);
            empMapCallback.returnThis(employeeMap);
        });
        employeeService.getEmployeesAsMap(callbackBuilder.build());

    }
In this case we forward just the error handling and timeout handling to the callback that we are creating, and then we create a custom return handler using withMapCallback.

CompanyRestService using withMapCallback and delegateWithLogging

@RequestMapping("/")
public class CompanyRestService {
...

    @RequestMapping("/emap3")
    public void employeeMap3(final Callback<Map<String, Employee>> empMapCallback) {

        final CallbackBuilder callbackBuilder = CallbackBuilder.newCallbackBuilder();
        // Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
        // timeout and error handling that associates the error and timeout handling with this call.
        callbackBuilder.delegateWithLogging(empMapCallback, logger, "employeeMap3");
        callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
            logger.info("GET MAP {}", employeeMap);
            empMapCallback.returnThis(employeeMap);
        });
        employeeService.getEmployeesAsMap(callbackBuilder.build());
    }
If you want to handle error logging and timeout logging in the context of this services log, you can simply use the delegateWithLogging method. This will setup some basic logging for error handing and timeouts.
We of course also have methods that work with List can Collections and Sets and....

Working with list by using withListCallback

@RequestMapping("/")
public class CompanyRestService {
...

    @RequestMapping("/elist")
    public void employeeList(final Callback<List<Employee>> empListCallback) {

        final CallbackBuilder callbackBuilder = CallbackBuilder.newCallbackBuilder();
        // Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
        // timeout and error handling that associates the error and timeout handling with this call.
        callbackBuilder.delegateWithLogging(empListCallback, logger, "employeeList");
        callbackBuilder.withListCallback(Employee.class, employeeList -> {
            logger.info("GET List {}", employeeList);
            empListCallback.returnThis(employeeList);
        });
        employeeService.getEmployeesAsList(callbackBuilder.build());
    }
The above works as you would expect. Let's mix things up a bit. We will callfindEmployeeByName which may or many not return an employee.

Working with optional by using withOptionalCallback

@RequestMapping("/")
public class CompanyRestService {
...

    @RequestMapping("/find")
    public void findEmployee(final Callback<Employee> employeeCallback,
                             @RequestParam("name") final String name) {

        final CallbackBuilder callbackBuilder = CallbackBuilder.newCallbackBuilder();
        // Forward to error handling and timeout defined in empMapCallback, 
        // but install some additional logging for
        // timeout and error handling that associates the error and timeout handling with this call.
        callbackBuilder.delegateWithLogging(employeeCallback, logger, "employeeMap3");
        callbackBuilder.withOptionalCallback(Employee.class, employeeOptional -> {


            if (employeeOptional.isPresent()) {
                employeeCallback.returnThis(employeeOptional.get());
            } else {
                employeeCallback.onError(new Exception("Employee not found"));
            }
        });
        employeeService.findEmployeeByName(callbackBuilder.build(), name);
    }
To work with Optional's we use withOptionalCallback. Here we return an error if the employee is not found and we return the Employee object if he is found.
/**
 * You need this is you want to do error handling (Exception) from a callback.
 * Callback Builder
 * created by rhightower on 3/23/15.
 */
@SuppressWarnings("UnusedReturnValue")
public class CallbackBuilder {


    /**
     * Builder method to set callback handler that takes a list
     * @param componentClass  componentClass
     * @param callback callback
     * @param <T> T
     * @return this
     */
    public <T> CallbackBuilder withListCallback(final Class<T> componentClass,
                                                       final Callback<List<T>> callback) {
        this.callback = callback;
        return this;
    }


    /**
     * Builder method to set callback handler that takes a set
     * @param componentClass  componentClass
     * @param callback callback
     * @param <T> T
     * @return this
     */
    public <T> CallbackBuilder withSetCallback(final Class<T> componentClass,
                                                     final Callback<Set<T>> callback) {
        this.callback = callback;
        return this;
    }


    /**
     * Builder method to set callback handler that takes a collection
     * @param componentClass  componentClass
     * @param callback callback
     * @param <T> T
     * @return this
     */
    public <T> CallbackBuilder withCollectionCallback(final Class<T> componentClass,
                                                            final Callback<Collection<T>> callback) {
        this.callback = callback;
        return this;
    }


    /**
     * Builder method to set callback handler that takes a map
     * @param keyClass  keyClass
     * @param valueClass  valueClass
     * @param callback callback
     * @param <K> key type
     * @param <V> value type
     * @return this
     */
    public <K, V> CallbackBuilder withMapCallback(final Class<K> keyClass,
                                                        final Class<V> valueClass,
                                                        final Callback<Map<K, V>> callback) {
        this.callback = callback;
        return this;
    }


    /**
     * Builder method to set callback handler that takes a boolean
     * @param callback callback
     * @return this
     */
    public CallbackBuilder withBooleanCallback(final Callback<Boolean> callback) {
        this.callback = callback;
        return this;
    }

    /**
     * Builder method to set callback handler that takes a integer
     * @param callback callback
     * @return this
     */
    public CallbackBuilder withIntCallback(final Callback<Integer> callback) {
        this.callback = callback;
        return this;
    }


    /**
     * Builder method to set callback handler that takes a long
     * @param callback callback
     * @return this
     */
    public CallbackBuilder withLongCallback(final Callback<Long> callback) {
        this.callback = callback;
        return this;
    }


    /**
     * Builder method to set callback handler that takes a string
     * @param callback callback
     * @return this
     */
    public CallbackBuilder withStringCallback(final Callback<String> callback) {
        this.callback = callback;
        return this;
    }



    /**
     * Builder method to set callback handler that takes an optional string
     * @param callback callback
     * @return this
     */
    public CallbackBuilder withOptionalStringCallback(final Callback<Optional<String>> callback) {
        this.callback = callback;
        return this;
    }



    /**
     * Builder method to set callback handler that takes an optional string
     * @param callback callback
     * @return this
     */
    public <T> CallbackBuilder withOptionalCallback(final Class<T> cls, final Callback<Optional<T>> callback) {
        this.callback = callback;
        return this;
    }

Read more about callback builders and how to handle errors, timeouts and downstream calls.

Reactor

Let's say that EmployeeService was really talking to some downstream remote services or perhaps to Cassandra and/or Redis. Let's also say that you want to add some timeout for this downstream system. Let's say 10 seconds.
Then our example will use the QBit Reactor and the easiest way to do that would be to subclass the BaseService.

Using QBit Reactor from the BaseService

package io.advantageous.qbit.example.callback;

import io.advantageous.qbit.admin.ManagedServiceBuilder;

import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
import io.advantageous.qbit.reactive.Callback;
import io.advantageous.qbit.reactive.CallbackBuilder;
import io.advantageous.qbit.reactive.Reactor;
import io.advantageous.qbit.reactive.ReactorBuilder;
import io.advantageous.qbit.service.BaseService;
import io.advantageous.qbit.service.ServiceQueue;
import io.advantageous.qbit.service.stats.StatsCollector;
import io.advantageous.qbit.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@RequestMapping("/")
public class CompanyRestServiceUsingReactor extends BaseService {


    private final Logger logger = LoggerFactory.getLogger(CompanyRestService.class);
    private final EmployeeService employeeService;

    public CompanyRestServiceUsingReactor(Reactor reactor,
                                          Timer timer,
                                          StatsCollector statsCollector,
                                          EmployeeService employeeService) {
        super(reactor, timer, statsCollector);
        this.employeeService = employeeService;
        reactor.addServiceToFlush(employeeService);
    }



    @RequestMapping("/emap")
    public void employeeMap(final Callback<Map<String, Employee>> empMapCallback) {

        final CallbackBuilder callbackBuilder =  super.reactor.callbackBuilder();
        callbackBuilder.wrap(empMapCallback); //Forward to error handling, timeout, and callback defined in empMapCallback
        employeeService.getEmployeesAsMap(callbackBuilder.build());

    }


    @RequestMapping("/emap2")
    public void employeeMap2(final Callback<Map<String, Employee>> empMapCallback) {

        final CallbackBuilder callbackBuilder = super.reactor.callbackBuilder();
        callbackBuilder.delegate(empMapCallback); //Forward to error handling and timeout defined in empMapCallback

        callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
            logger.info("GET MAP {}", employeeMap);
            empMapCallback.returnThis(employeeMap);
        });
        employeeService.getEmployeesAsMap(callbackBuilder.build());

    }


    @RequestMapping("/emap3")
    public void employeeMap3(final Callback<Map<String, Employee>> empMapCallback) {

        final CallbackBuilder callbackBuilder = super.reactor.callbackBuilder();
        // Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
        // timeout and error handling that associates the error and timeout handling with this call.
        callbackBuilder.delegateWithLogging(empMapCallback, logger, "employeeMap3");
        callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
            logger.info("GET MAP {}", employeeMap);
            empMapCallback.returnThis(employeeMap);
        });
        employeeService.getEmployeesAsMap(callbackBuilder.build());
    }


    @RequestMapping("/elist")
    public void employeeList(final Callback<List<Employee>> empListCallback) {

        final CallbackBuilder callbackBuilder = super.reactor.callbackBuilder();
        // Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
        // timeout and error handling that associates the error and timeout handling with this call.
        callbackBuilder.delegateWithLogging(empListCallback, logger, "employeeList");
        callbackBuilder.withListCallback(Employee.class, employeeList -> {
            logger.info("GET List {}", employeeList);
            empListCallback.returnThis(employeeList);
        });
        employeeService.getEmployeesAsList(callbackBuilder.build());
    }


    @RequestMapping("/find")
    public void findEmployee(final Callback<Employee> employeeCallback,
                             @RequestParam("name") final String name) {

        final long startTime = super.time;

        final CallbackBuilder callbackBuilder = super.reactor.callbackBuilder();
        // Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
        // timeout and error handling that associates the error and timeout handling with this call.
        callbackBuilder.delegateWithLogging(employeeCallback, logger, "employeeMap3");
        callbackBuilder.withOptionalCallback(Employee.class, employeeOptional -> {


            super.recordTiming("findEmployee", time - startTime);
            if (employeeOptional.isPresent()) {

                employeeCallback.returnThis(employeeOptional.get());
            } else {
                employeeCallback.onError(new Exception("Employee not found"));
            }
        });
        employeeService.findEmployeeByName(callbackBuilder.build(), name);
    }


    public static void main(final String... args) throws Exception {

        /** Create a ManagedServiceBuilder which simplifies QBit wiring. */
        final ManagedServiceBuilder managedServiceBuilder = ManagedServiceBuilder.managedServiceBuilder().setRootURI("/");
        managedServiceBuilder.enableLoggingMappedDiagnosticContext();

        /** Create a service queue for the employee service. */
        final ServiceQueue employeeServiceQueue = managedServiceBuilder.createServiceBuilderForServiceObject(
                new EmployeeServiceImpl()).buildAndStartAll();

        /** Add a CompanyRestService passing it a client proxy to the employee service. */
        managedServiceBuilder.addEndpointService(
                new CompanyRestServiceUsingReactor(
                        ReactorBuilder.reactorBuilder().setDefaultTimeOut(10).setTimeUnit(TimeUnit.SECONDS).build(),
                        Timer.timer(),
                        managedServiceBuilder.getStatServiceBuilder().buildStatsCollector(),
                        employeeServiceQueue.createProxy(EmployeeService.class)));

        /** Start the server. */
        managedServiceBuilder.startApplication();

    }
}
Notice that the callbackBuilder is now constructed from the reactor (final CallbackBuilder callbackBuilder = super.reactor.callbackBuilder();).
To learn more about the Reactor, please read Reactively handling async calls with QBit Reactive Microservices.

Stats

When you use the BaseService, you also have access to the stats system.

Stats from BaseService

    @RequestMapping("/find")
    public void findEmployee(final Callback<Employee> employeeCallback,
                             @RequestParam("name") final String name) {

        final long startTime = super.time;

        final CallbackBuilder callbackBuilder = super.reactor.callbackBuilder();
        callbackBuilder.delegateWithLogging(employeeCallback, logger, "employeeMap3");
        callbackBuilder.withOptionalCallback(Employee.class, employeeOptional -> {


            /** Record timing. */
            super.recordTiming("findEmployee", time - startTime);

            if (employeeOptional.isPresent()) {
                /* Increment count of employees found. */
                super.incrementCount("employeeFound");
                employeeCallback.returnThis(employeeOptional.get());
            } else {
                /* Increment count of employees not found. */
                super.incrementCount("employeeNotFound");
                employeeCallback.onError(new Exception("Employee not found"));
            }
        });
        employeeService.findEmployeeByName(callbackBuilder.build(), name);
    }
Kafka and Cassandra support, training for AWS EC2 Cassandra 3.0 Training