Programming - TsFile API
TsFile is a file format of Time Series we used in IoTDB. In this section, we would like to introduce the usage of this file format.
TsFile libaray Installation
There are two ways to use TsFile in your own project.
Using as jars:
Compile the source codes and build to jars
git clone https://github.com/apache/incubator-iotdb.git
cd tsfile/
mvn clean package -Dmaven.test.skip=true
Then, all the jars can be get in folder named
target/
. Importtarget/tsfile-0.9.3-jar-with-dependencies.jar
to your project.
Using as a maven dependency:
Compile source codes and deploy to your local repository in three steps:
Get the source codes
git clone https://github.com/apache/incubator-iotdb.git
Compile the source codes and deploy
cd tsfile/
mvn clean install -Dmaven.test.skip=true
add dependencies into your project:
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>tsfile</artifactId>
<version>0.9.3</version>
</dependency>
Or, you can download the dependencies from official Maven repository:
- First, find your maven `settings.xml` on path: `${username}\.m2\settings.xml` , add this `<profile>` to `<profiles>`:
```
<profile>
<id>allow-snapshots</id>
<activation><activeByDefault>true</activeByDefault></activation>
<repositories>
<repository>
<id>apache.snapshots</id>
<name>Apache Development Snapshot Repository</name>
<url>https://repository.apache.org/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
```
- Then add dependencies into your project:
```
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>tsfile</artifactId>
<version>0.9.3</version>
</dependency>
```
TSFile Usage
This section demonstrates the detailed usages of TsFile.
Time-series Data
A time-series is considered as a sequence of quadruples. A quadruple is defined as (device, measurement, time, value).
measurement: A physical or formal measurement that a time-series is taking, e.g., the temperature of a city, the sales number of some goods or the speed of a train at different times. As a traditional sensor (like a thermometer) also takes a single measurement and produce a time-series, we will use measurement and sensor interchangeably below.
device: A device refers to an entity that is taking several measurements (producing multiple time-series), e.g., a running train monitors its speed, oil meter, miles it has run, current passengers each is conveyed to a time-series.
Table 1 illustrates a set of time-series data. The set showed in the following table contains one device named “device_1” with three measurements named “sensor_1”, “sensor_2” and “sensor_3”.
device_1 | |||||
---|---|---|---|---|---|
sensor_1 | sensor_2 | sensor_3 | |||
time | value | time | value | time | value |
1 | 1.2 | 1 | 20 | 2 | 50 |
3 | 1.4 | 2 | 20 | 4 | 51 |
5 | 1.1 | 3 | 21 | 6 | 52 |
7 | 1.8 | 4 | 20 | 8 | 53 |
A set of time-series data
One Line of Data: In many industrial applications, a device normally contains more than one sensor and these sensors may have values at a same timestamp, which is called one line of data.
Formally, one line of data consists of a device_id
, a timestamp which indicates the milliseconds since January 1, 1970, 00:00:00, and several data pairs composed of measurement_id
and corresponding value
. All data pairs in one line belong to this device_id
and have the same timestamp. If one of the measurements
does not have a value
in the timestamp
, use a space instead(Actually, TsFile does not store null values). Its format is shown as follow:
device_id, timestamp, <measurement_id, value>...
An example is illustrated as follow. In this example, the data type of two measurements are INT32
, FLOAT
respectively.
device_1, 1490860659000, m1, 10, m2, 12.12
Writing TsFile
Generate a TsFile File.
A TsFile can be generated by following three steps and the complete code will be given in the section “Example for writing TsFile”.
First, construct a
TsFileWriter
instance.Here are the available constructors:
- Without pre-defined schema
public TsFileWriter(File file) throws IOException
- With pre-defined schema
public TsFileWriter(File file, Schema schema) throws IOException
This one is for using the HDFS file system.
TsFileOutput
can be an instance of classHDFSOutput
.public TsFileWriter(TsFileOutput output, Schema schema) throws IOException
If you want to set some TSFile configuration on your own, you could use param
config
. For example:TSFileConfig conf = new TSFileConfig();
conf.setTSFileStorageFs("HDFS");
TsFileWriter tsFileWriter = new TsFileWriter(file, schema, conf);
In this example, data files will be stored in HDFS, instead of local file system. If you’d like to store data files in local file system, you can use
conf.setTSFileStorageFs("LOCAL")
, which is also the default config.You can also config the ip and port of your HDFS by
config.setHdfsIp(...)
andconfig.setHdfsPort(...)
. The default ip islocalhost
and default port is9000
.Parameters:
file : The TsFile to write
schema : The file schemas, will be introduced in next part.
config : The config of TsFile.
Second, add measurements
Or you can make an instance of class
Schema
first and pass this to the constructor of classTsFileWriter
The class
Schema
contains a map whose key is the name of one measurement schema, and the value is the schema itself.Here are the interfaces:
// Create an empty Schema or from an existing map
public Schema()
public Schema(Map<String, MeasurementSchema> measurements)
// Use this two interfaces to add measurements
public void registerMeasurement(MeasurementSchema descriptor)
public void registerMeasurements(Map<String, MeasurementSchema> measurements)
// Some useful getter and checker
public TSDataType getMeasurementDataType(String measurementId)
public MeasurementSchema getMeasurementSchema(String measurementId)
public Map<String, MeasurementSchema> getAllMeasurementSchema()
public boolean hasMeasurement(String measurementId)
You can always use the following interface in
TsFileWriter
class to add additional measurements: public void addMeasurement(MeasurementSchema measurementSchema) throws WriteProcessException
The class
MeasurementSchema
contains the information of one measurement, there are several constructors:public MeasurementSchema(String measurementId, TSDataType type, TSEncoding encoding)
public MeasurementSchema(String measurementId, TSDataType type, TSEncoding encoding, CompressionType compressionType)
public MeasurementSchema(String measurementId, TSDataType type, TSEncoding encoding, CompressionType compressionType,
Map<String, String> props)
Parameters:
measurementID: The name of this measurement, typically the name of the sensor.
type: The data type, now support six types:
BOOLEAN
,INT32
,INT64
,FLOAT
,DOUBLE
,TEXT
;encoding: The data encoding. See Chapter 2-3.
compression: The data compression. Now supports
UNCOMPRESSED
andSNAPPY
.props: Properties for special data types.Such as
max_point_number
forFLOAT
andDOUBLE
,max_string_length
forTEXT
. Use as string pairs into a map such as (“max_point_number”, “3”).
> **Notice:** Although one measurement name can be used in multiple deltaObjects, the properties cannot be changed. I.e. it's not allowed to add one measurement name for multiple times with different type or encoding. Here is a bad example:
```
// The measurement "sensor_1" is float type
addMeasurement(new MeasurementSchema("sensor_1", TSDataType.FLOAT, TSEncoding.RLE));
// This call will throw a WriteProcessException exception
addMeasurement(new MeasurementSchema("sensor_1", TSDataType.INT32, TSEncoding.RLE));
```
Third, insert and write data continually.
Use this interface to create a new
TSRecord
(a timestamp and device pair).public TSRecord(long timestamp, String deviceId)
Then create a
DataPoint
(a measurement and value pair), and use the addTuple method to add the DataPoint to the correct TsRecord.Use this method to write
public void write(TSRecord record) throws IOException, WriteProcessException
Finally, call
close
to finish this writing process.public void close() throws IOException
Example for writing a TsFile
You should install TsFile to your local maven repository.
mvn clean install -pl tsfile -am -DskipTests
You could write a TsFile by constructing TSRecord if you have the non-aligned (e.g. not all sensors contain values) time series data.
A more thorough example can be found at /example/tsfile/src/main/java/org/apache/iotdb/tsfile/TsFileWriteWithTSRecord.java
package org.apache.iotdb.tsfile;
import java.io.File;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
import org.apache.iotdb.tsfile.write.TsFileWriter;
import org.apache.iotdb.tsfile.write.record.TSRecord;
import org.apache.iotdb.tsfile.write.record.datapoint.DataPoint;
import org.apache.iotdb.tsfile.write.record.datapoint.FloatDataPoint;
import org.apache.iotdb.tsfile.write.record.datapoint.IntDataPoint;
import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
/**
* An example of writing data to TsFile
* It uses the interface:
* public void addMeasurement(MeasurementSchema MeasurementSchema) throws WriteProcessException
*/
public class TsFileWriteWithTSRecord {
public static void main(String args[]) {
try {
String path = "test.tsfile";
File f = new File(path);
if (f.exists()) {
f.delete();
}
TsFileWriter tsFileWriter = new TsFileWriter(f);
// add measurements into file schema
tsFileWriter
.addMeasurement(new MeasurementSchema("sensor_1", TSDataType.INT64, TSEncoding.RLE));
tsFileWriter
.addMeasurement(new MeasurementSchema("sensor_2", TSDataType.INT64, TSEncoding.RLE));
tsFileWriter
.addMeasurement(new MeasurementSchema("sensor_3", TSDataType.INT64, TSEncoding.RLE));
// construct TSRecord
TSRecord tsRecord = new TSRecord(1, "device_1");
DataPoint dPoint1 = new LongDataPoint("sensor_1", 1);
DataPoint dPoint2 = new LongDataPoint("sensor_2", 2);
DataPoint dPoint3 = new LongDataPoint("sensor_3", 3);
tsRecord.addTuple(dPoint1);
tsRecord.addTuple(dPoint2);
tsRecord.addTuple(dPoint3);
// write TSRecord
tsFileWriter.write(tsRecord);
// close TsFile
tsFileWriter.close();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
You could write a TsFile by constructing RowBatch if you have the aligned time series data.
A more thorough example can be found at /example/tsfile/src/main/java/org/apache/iotdb/tsfile/TsFileWriteWithRowBatch.java
package org.apache.iotdb.tsfile;
import java.io.File;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
import org.apache.iotdb.tsfile.write.TsFileWriter;
import org.apache.iotdb.tsfile.write.schema.Schema;
import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
import org.apache.iotdb.tsfile.write.record.RowBatch;
/**
* An example of writing data with RowBatch to TsFile
*/
public class TsFileWriteWithRowBatch {
public static void main(String[] args) {
try {
String path = "test.tsfile";
File f = new File(path);
if (f.exists()) {
f.delete();
}
Schema schema = new Schema();
// the number of rows to include in the row batch
int rowNum = 1000000;
// the number of values to include in the row batch
int sensorNum = 10;
// add measurements into file schema (all with INT64 data type)
for (int i = 0; i < sensorNum; i++) {
schema.registerMeasurement(
new MeasurementSchema("sensor_" + (i + 1), TSDataType.INT64, TSEncoding.TS_2DIFF));
}
// add measurements into TSFileWriter
TsFileWriter tsFileWriter = new TsFileWriter(f, schema);
// construct the row batch
RowBatch rowBatch = schema.createRowBatch("device_1");
long[] timestamps = rowBatch.timestamps;
Object[] values = rowBatch.values;
long timestamp = 1;
long value = 1000000L;
for (int r = 0; r < rowNum; r++, value++) {
int row = rowBatch.batchSize++;
timestamps[row] = timestamp++;
for (int i = 0; i < sensorNum; i++) {
long[] sensor = (long[]) values[i];
sensor[row] = value;
}
// write RowBatch to TsFile
if (rowBatch.batchSize == rowBatch.getMaxBatchSize()) {
tsFileWriter.write(rowBatch);
rowBatch.reset();
}
}
// write RowBatch to TsFile
if (rowBatch.batchSize != 0) {
tsFileWriter.write(rowBatch);
rowBatch.reset();
}
// close TsFile
tsFileWriter.close();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
Interface for Reading TsFile
Before the Start
The set of time-series data in section “Time-series Data” is used here for a concrete introduction in this section. The set showed in the following table contains one deltaObject named “device_1” with three measurements named “sensor_1”, “sensor_2” and “sensor_3”. And the measurements has been simplified to do a simple illustration, which contains only 4 time-value pairs each.
device_1 | |||||
---|---|---|---|---|---|
sensor_1 | sensor_2 | sensor_3 | |||
time | value | time | value | time | value |
1 | 1.2 | 1 | 20 | 2 | 50 |
3 | 1.4 | 2 | 20 | 4 | 51 |
5 | 1.1 | 3 | 21 | 6 | 52 |
7 | 1.8 | 4 | 20 | 8 | 53 |
A set of time-series data
Definition of Path
A path is a dot-separated string which uniquely identifies a time-series in TsFile, e.g., “root.area_1.device_1.sensor_1”. The last section “sensor_1” is called “measurementId” while the remaining parts “root.area_1.device_1” is called deviceId. As mentioned above, the same measurement in different devices has the same data type and encoding, and devices are also unique.
In read interfaces, The parameter paths
indicates the measurements to be selected.
Path instance can be easily constructed through the class Path
. For example:
Path p = new Path("device_1.sensor_1");
We will pass an ArrayList of paths for final query call to support multiple paths.
List<Path> paths = new ArrayList<Path>();
paths.add(new Path("device_1.sensor_1"));
paths.add(new Path("device_1.sensor_3"));
Notice: When constructing a Path, the format of the parameter should be a dot-separated string, the last part will be recognized as measurementId while the remaining parts will be recognized as deviceId.
Definition of Filter
Usage Scenario
Filter is used in TsFile reading process to select data satisfying one or more given condition(s).
IExpression
The IExpression
is a filter expression interface and it will be passed to our final query call. We create one or more filter expressions and may use binary filter operators to link them to our final expression.
Create a Filter Expression
There are two types of filters.
TimeFilter: A filter for
time
in time-series data.IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter);
Use the following relationships to get a
TimeFilter
object (value is a long int variable).Relationship Description TimeFilter.eq(value) Choose the time equal to the value TimeFilter.lt(value) Choose the time less than the value TimeFilter.gt(value) Choose the time greater than the value TimeFilter.ltEq(value) Choose the time less than or equal to the value TimeFilter.gtEq(value) Choose the time greater than or equal to the value TimeFilter.notEq(value) Choose the time not equal to the value TimeFilter.not(TimeFilter) Choose the time not satisfy another TimeFilter ValueFilter: A filter for
value
in time-series data.IExpression valueFilterExpr = new SingleSeriesExpression(Path, ValueFilter);
The usage of
ValueFilter
is the same as usingTimeFilter
, just to make sure that the type of the value equal to the measurement’s(defined in the path).
Binary Filter Operators
Binary filter operators can be used to link two single expressions.
- BinaryExpression.and(Expression, Expression): Choose the value satisfy for both expressions.
- BinaryExpression.or(Expression, Expression): Choose the value satisfy for at least one expression.
Filter Expression Examples
TimeFilterExpression Examples
IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.eq(15)); // series time = 15
IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.ltEq(15)); // series time <= 15
IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.lt(15)); // series time < 15
IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.gtEq(15)); // series time >= 15
IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.notEq(15)); // series time != 15
IExpression timeFilterExpr = BinaryExpression.and(new GlobalTimeExpression(TimeFilter.gtEq(15L)),
new GlobalTimeExpression(TimeFilter.lt(25L))); // 15 <= series time < 25
IExpression timeFilterExpr = BinaryExpression.or(new GlobalTimeExpression(TimeFilter.gtEq(15L)),
new GlobalTimeExpression(TimeFilter.lt(25L))); // series time >= 15 or series time < 25
Read Interface
First, we open the TsFile and get a ReadOnlyTsFile
instance from a file path string path
.
TsFileSequenceReader reader = new TsFileSequenceReader(path);
ReadOnlyTsFile readTsFile = new ReadOnlyTsFile(reader);
Next, we prepare the path array and query expression, then get final QueryExpression
object by this interface:
QueryExpression queryExpression = QueryExpression.create(paths, statement);
The ReadOnlyTsFile class has two query
method to perform a query.
Method 1
public QueryDataSet query(QueryExpression queryExpression) throws IOException
Method 2
public QueryDataSet query(QueryExpression queryExpression, long partitionStartOffset, long partitionEndOffset) throws IOException
This method is designed for advanced applications such as the TsFile-Spark Connector.
params : For method 2, two additional parameters are added to support partial query:
partitionStartOffset
: start offset for a TsFilepartitionEndOffset
: end offset for a TsFile
What is Partial Query ?
In some distributed file systems(e.g. HDFS), a file is split into severval parts which are called “Blocks” and stored in different nodes. Executing a query paralleled in each nodes involved makes better efficiency. Thus Partial Query is needed. Paritial Query only selects the results stored in the part split by
QueryConstant.PARTITION_START_OFFSET
andQueryConstant.PARTITION_END_OFFSET
for a TsFile.
QueryDataset Interface
The query performed above will return a QueryDataset
object.
Here’s the useful interfaces for user.
bool hasNext();
Return true if this dataset still has elements.
List<Path> getPaths()
Get the paths in this data set.
List<TSDataType> getDataTypes();
Get the data types. The class TSDataType is an enum class, the value will be one of the following:
BOOLEAN,
INT32,
INT64,
FLOAT,
DOUBLE,
TEXT;
RowRecord next() throws IOException;
Get the next record.
The class
RowRecord
consists of along
timestamp and aList<Field>
for data in different sensors, we can use two getter methods to get them.long getTimestamp();
List<Field> getFields();
To get data from one Field, use these methods:
TSDataType getDataType();
Object getObjectValue();
Example for reading an existing TsFile
You should install TsFile to your local maven repository.
A more thorough example with query statement can be found at /tsfile/example/src/main/java/org/apache/iotdb/tsfile/TsFileRead.java
package org.apache.iotdb.tsfile;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.iotdb.tsfile.read.ReadOnlyTsFile;
import org.apache.iotdb.tsfile.read.TsFileSequenceReader;
import org.apache.iotdb.tsfile.read.common.Path;
import org.apache.iotdb.tsfile.read.expression.IExpression;
import org.apache.iotdb.tsfile.read.expression.QueryExpression;
import org.apache.iotdb.tsfile.read.expression.impl.BinaryExpression;
import org.apache.iotdb.tsfile.read.expression.impl.GlobalTimeExpression;
import org.apache.iotdb.tsfile.read.expression.impl.SingleSeriesExpression;
import org.apache.iotdb.tsfile.read.filter.TimeFilter;
import org.apache.iotdb.tsfile.read.filter.ValueFilter;
import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
/**
* The class is to show how to read TsFile file named "test.tsfile".
* The TsFile file "test.tsfile" is generated from class TsFileWrite.
* Run TsFileWrite to generate the test.tsfile first
*/
public class TsFileRead {
private static void queryAndPrint(ArrayList<Path> paths, ReadOnlyTsFile readTsFile, IExpression statement)
throws IOException {
QueryExpression queryExpression = QueryExpression.create(paths, statement);
QueryDataSet queryDataSet = readTsFile.query(queryExpression);
while (queryDataSet.hasNext()) {
System.out.println(queryDataSet.next());
}
System.out.println("------------");
}
public static void main(String[] args) throws IOException {
// file path
String path = "test.tsfile";
// create reader and get the readTsFile interface
TsFileSequenceReader reader = new TsFileSequenceReader(path);
ReadOnlyTsFile readTsFile = new ReadOnlyTsFile(reader);
// use these paths(all sensors) for all the queries
ArrayList<Path> paths = new ArrayList<>();
paths.add(new Path("device_1.sensor_1"));
paths.add(new Path("device_1.sensor_2"));
paths.add(new Path("device_1.sensor_3"));
// no query statement
queryAndPrint(paths, readTsFile, null);
//close the reader when you left
reader.close();
}
}
Change TsFile Config
TSFileConfig config = TSFileDescriptor.getInstance().getConfig();
config.setXXX();
Bloom filter
Bloom filter checks whether a given time series is in the tsfile before loading metadata. This can improve the performance of loading metadata and skip the tsfile that doesn’t contain specified time series. If you want to learn more about its mechanism, you can refer to: wiki page of bloom filter.
configuration
you can control the false positive rate of bloom filter by changing the bloomFilterErrorRate in TSFileConfig
# The acceptable error rate of bloom filter, should be in [0.01, 0.1], default is 0.05
bloomFilterErrorRate=0.05