A Redis-based Java Time Series Collection

Redis is an open-source, in-memory data structure store used to implement NoSQL key-value databases. In this article, we'll discuss how you can use Redis and the Java programming language to build a powerful, enterprise-class time series database.

What is time series data?

Time series data is information marked with timestamps, such that the data points can be organized in a temporal sequence. For efficient processing and analysis, time series data is usually stored in a time series database, which has been specifically built and optimized for working with time series data. Common use cases for time series databases include weather observations, stock prices, and Internet of Things (IoT) sensors—any situation in which you want to understand how data changes over time.

Time series data in Redis

The base version of Redis has multiple options for working with time series data:

  • The sorted set data structure. The time series data is stored as a set of key-value pairs, in which the timestamp is the key and the data is the corresponding value.
  • Redis Streams, a new data type for modeling log files in which data is appended to the end of the stream.

Time series Redis Java collection with Redisson

While the options for time series data in Redis above are perfectly workable, they can be difficult to implement in practice. What's more, the Redis learning curve can be challenging for developers using programming languages such as Java, which comes with a rich library of useful objects and collections.

The good news is that many of these objects and collections are reproduced in third-party Redis Java clients such as Redisson. What's more, Redisson even comes with new interfaces and implementations that aren't present in the base version of Java—interfaces such as RTimeSeries for time series data.

The RTimeSeries interface is a data structure that stores time series data. Below is an example of how to use the RTimeSeries interface in Redisson:

RTimeSeries<String> ts = redisson.getTimeSeries("myTimeSeries");

ts.add(201908110501, "10%");
ts.add(201908110502, "30%");
ts.add(201908110504, "10%");
ts.add(201908110508, "75%");

// entry time-to-live is 10 hours
ts.add(201908110510, "85%", 10, TimeUnit.HOURS);
ts.add(201908110510, "95%", 10, TimeUnit.HOURS);

String value = ts.get(201908110508);
ts.remove(201908110508);

Collection<String> values = ts.pollFirst(2);
Collection<String> range = ts.range(201908110501, 201908110508);

As you can see, instantiating a new RTimeSeries database requires the user to specify the type of data that the database will store (here, a String). The database follows a simple key-value model, in which the key is the timestamp and the value is the associated data.

In the example above, the timestamp encodes a date and time (e.g. 2019/08/11, 05:01), with precision up to the minute. However, the timestamp is simply a wrapper for the long primitive type, which has a maximum value of 9,223,372,036,854,775,807 (2^63 - 1). This number is larger than the number of nanoseconds in a year, so RTimeSeries provides plenty of space for extremely precise time series data.

You'll also notice that the add() method has optional arguments that specify the maximum time to live (TTL) for the entries in the time series database. This attribute can be specified in any valid Java TimeUnit, from TimeUnit.NANOSECONDS to TimeUnit.DAYS.

Some of the most useful RTimeSeries methods are:

  • add(): This method adds a time series data entry (consisting of a timestamp and the associated object) to the database, as well as an optional expiration date.
  • first(): This method returns the first entry in the time series database.
  • last(): This method returns the last entry in the time series database.
  • pollFirst(): This method removes and returns the first entry in the time series database.
  • pollLast(): This method removes and returns the last entry in the time series database.
  • range(): This method returns the entries in the time series database within the given timestamp range.
  • remove(): Given a timestamp, this method removes the associated entry in the time series database.
  • size(): This method returns the number of entries in the time series database.

Note that RTimeSeries also comes with asynchronous, reactive, and RxJava2 interfaces, so that you can choose the programming model that best fits your needs. For example, here is how the code above would change if you were using the asynchronous interface:

RFuture<Void> future = ts.add(201908110501, "10%");
RFuture<Void> future = ts.addAsync(201908110502, "30%");
RFuture<Void> future = ts.addAsync(201908110504, "10%");
RFuture<Void> future = ts.addAsync(201908110508, "75%");

// entry time-to-live is 10 hours
RFuture<Void> future = ts.addAsync(201908110510, "85%", 10, TimeUnit.HOURS);
RFuture<Void> future = ts.addAsync(201908110510, "95%", 10, TimeUnit.HOURS);

RFuture<String> future = ts.getAsync(201908110508);
RFuture<Boolean> future = ts.removeAsync(201908110508);

future.whenComplete((res, exception) -> {

    // handle both result and exception

});
Similar articles