11.3. Modifying the Interval Between Invocations

Documentation

VoltDB Home » Documentation » Guide to Performance and Customization

11.3. Modifying the Interval Between Invocations

To customize the interval between invocations of the task's stored procedure, you create a custom task that implements the IntervalGenerator interface. The custom task consists of the Java class you create, and the SQL statements to load and declare the task. At run time, for each invocation of the task, VoltDB uses the custom class to determine how long to wait before invoking the stored procedure specified in the task definition.

Let's look at an example. Say we want a task, similar to the previous example, that periodically deletes unused sessions. The task can use the same stored procedure as before, this time with a fixed batch size passed in as an argument when declaring the task:

CREATE PROCEDURE PurgeOldSessions DIRECTED AS
    DELETE FROM session 
       WHERE last_access < DATEADD(MINUTE,-5,NOW()) 
       ORDER BY sessionID  ASC LIMIT ?;

Now we want to customize how frequently the procedure runs — increasing the frequency if it always deletes the full batch, or decreasing the frequency if fewer records are deleted each time. To do this we create a custom task that checks how many records were actually deleted in each run. If the count of deleted records is less than 80% of the batch size, increase the interval between runs. On the other hand, if the number deleted records equals the batch size, increase the frequency by reducing the interval. Finally, we can use parameters to the custom task to specify the minimum, maximum, and starting frequency, as well as the batch size.

11.3.1. Designing a Java Class That Implements IntervalGenerator

The first step is to write a Java class that implements the IntervalGenerator interface. This must be a static class whose constructor takes no arguments. In our example, we will also declare class variables for a helper object that is used in subsequent methods and minimum, maximum, and initial values for interval, plus the batch size.

The class must, at a minimum, declare or override three methods:

1

initialize()

The initialize() method is called first and receives a helper object inserted automatically by the subsystem that manages tasks, plus any parameters defined by the task definition in SQL. In our example there are four parameters from the task definition, which the method stores in the variables declared earlier. So the initialize() method ends up having a total of five arguments.

2

GetFirstInterval()

The getFirstInterval() method is called when the task starts; that is, when the task is first defined or when the database starts or resumes. The method must return an Interval object, which specifies the length of time to wait until the first execution of the task as well as a callback to invoke after the task runs. In our example, we initialize the Interval object with the current interval value, the MILLISECONDS time unit, and the callback defined in the next step.

3

A callback method

Finally, your custom task must have a callback method (the method you specify when creating the Interval object), which is invoked when the task completes. The callback method must return another Interval, similar to getFirstInterval().

In our example, the callback compares the batch size to the number of records deleted by the last run and makes appropriate adjustments to the next interval. It increases the frequency by reducing the interval if the full batch was used, it decreases the frequency by extending the interval if less than 80% was used, and it keeps the interval within the specified minimum and maximum.

Note that the callback method can be any name you choose. It does not have to be callback() and you can specify different callback methods each time if your application logic requires it.

Example 11.2, “Custom Task Implementing IntervalGenerator” shows the completed example task class, with the key elements highlighted.

Example 11.2. Custom Task Implementing IntervalGenerator

package mytasks;

import java.util.concurrent.TimeUnit;
import org.voltdb.VoltTable;
import org.voltdb.client.ClientResponse;
import org.voltdb.task.*;

public class PurgeIntervals implements IntervalGenerator {

    private TaskHelper helper;
    private long min, max, delta, batchsize;

    public void initialize(TaskHelper helper,                 1
                long min, long max, 
                long delta, long batch) {
        this.min = min;
        this.max = max;
        this.batchsize = batch;
    }

    public Interval getFirstInterval() {                      2
        return new Interval(this.delta, TimeUnit.MILLISECONDS,
                            this::callback);
    }

        /* 
        * Callback to handle the result of the task 
        * and return next Action.
        */
    private Interval callback(ActionResult result) {          3

        /* Find out how many records were deleted */
        ClientResponse response = result.getResponse();
        VoltTable[] results = response.getResults();
        long count = results[0].fetchRow(0).getLong(0);
 
         /* If less than 80%, increase by 10% */
        if (count < this.batchsize * .8) 
            this.delta += this.max * .1;

        /* If equal to batch size, decrease by 10% */
        if (count == this.batchsize)  
            this.delta -= this.max * .1;

            /* Stay within min & max */
        if (this.delta > this.max) this.delta = this.max;
        if (this.delta < this.min) this.delta = this.min;
        
        return new Interval(this.delta, TimeUnit.MILLISECONDS,
                            this::callback);
    }
}

11.3.2. Compiling and Loading the Class into VoltDB

Once you complete your Java source code, you compile, debug, and package it into a JAR file the same way you compile and package stored procedures. In fact, you can package tasks, procedures, and other classes (such as user-defined functions) into a single or separate JARs depending on your application and operational needs. The following example compiles the Java classes in the src/ folder and packages them into the JAR file sessiontasks.jar:

$ javac -classpath "/opt/voltdb/voltdb/*" \
        -d ./obj  src/*.java
$ jar  cvf  sessiontasks.jar -C obj . 

You then load the classes from the JAR file into VoltDB using the sqlcmd LOAD CLASSES directive:

LOAD CLASSES sessiontasks.jar;

11.3.3. Declaring the Task

Finally, once the custom class is loaded into the database, you can declare the task and start it running. You declare the task using the CREATE TASK statement, replacing the ON SCHEDULE static interval with FROM CLASS specifying the classpath of your new class. In our example, the custom task also requires four arguments: a minimum, maximum and starting interval, plus the batch size. (The same batch size passed to the stored procedure PurgeOldSessions.) The following statement creates the custom task with a minimum interval of 100ms (a tenth of a second), a maximum of 10 seconds, an initial interval of 1 second, and a batch size of 500 records.

CREATE TASK timecleanup 
  ON SCHEDULE FROM CLASS mytasks.PurgeIntervals
     WITH (100,10000,1000,500)
  PROCEDURE PurgeOldSessions WITH (500)
     RUN ON PARTITIONS;

Because the stored procedure PurgeOldSessions is a directed procedure (that is, it runs separately on every partition on the cluster), the task must be declared to RUN ON PARTITIONS.

The task starts as soon as it is declared, unless you include the DISABLE clause. Alternately, you can use the ALTER TASK statement to change the state of the task. For example, the following statement disables our newly created task:

ALTER TASK timecleanup DISABLE;