...
概要设计
Page和Chunk合并
根据每次刷盘数据量、Experiments of TsFile index area实验:TsFile 索引区#(II)ExperimentaboutcombineChunkandPage中(II) Experiment about combine Chunk and Page的分析及源码分析,有如下结论:
...
Code Block |
---|
boolean serializeStatistic = (chunkMetadataList.size() > 1); |
(3)Timeseries metadata中保存的信息如下:
Code Block |
---|
byte timeSeriesMetadataType; String measurementID; varInt chunkMetaDataListDataSize; |
(3)chunk chunk header中保存的header信息如下:
Code Block |
---|
byte marker; varInt data_size; String sensor_namemeasurementID; // redundant, can remove varInt data_size; byte datatype; byte compressionType; byte encoding type |
...
Code Block |
---|
varInt uncompressed_size; varInt compressed_size; |
观察可知两者的header并不冲突,合并并不能节省空间
综上所述,目前的tsfile对于单chunk单page已经做了元数据去冗余的优化,本设计没必要合并Page和Chunk
...
观察可知三者的header并不冲突,合并并不能节省空间
综上所述,目前的tsfile对于单chunk单page已经做了元数据去冗余的优化,本设计没必要合并Page和Chunk,ChunkHeader中保存了一份的measurement ID信息,而TimeseriesMetadata中保存了measurement ID,存在冗余,可以删除,需要修改TsFileSequenceReader中如下接口:
Code Block |
---|
// old interface
private ChunkHeader readChunkHeader(long position, int chunkHeaderSize);
// new interface
private ChunkHeader readChunkHeader(long position, int chunkHeaderSize, String measurementID) |
去除统计信息
Statistic
增加一个byte标识,标记其是否含有统计信息块,进而决定序列化和反序列化的方式
TsfileResource
增加一个byte标识,标记其是否含有统计信息块
查询适配
修改StorageGroupProcessor中的getFileResourceListForQuery方法,对于无统计信息的Tsfile生成一个特殊的TsfileResource,在loadChunk时自动生成统计信息块,进而复用原有查询代码
优缺点分析:
优点
(1)节省存储空间,每序列10个点的情况下,移除统计信息大约能节省28%的空间;每序列1个点的情况下,移除统计信息大约能节省45%的空间
(2)提高写入性能:写入性能约提高2-3%
缺点
(1)查询无法使用统计信息进行过滤和加速聚合
(2)删除chunk header里面的measurementID后,Tsfile无法顺序恢复chunk,需要重做所有WAL
实验
实验场景
模拟海量时间序列一次刷盘1%的数据量,即100万时间序列,其中有1万个设备,每个设备下有100个工况,每条时间序列写10个点,生成tsfile
每条时间序列的类型为long,编码算法为RLE,压缩算法为SNAPPY每条时间序列的类型为long,值随机生成,编码算法为RLE,压缩算法为SNAPPY或GZIP
试试GZIP
去除统计信息前后文件大小比较
每序列10个点(SNAPPY)
文件部分 / 文件所含内容 | 原始文件 | 仅保留起止时间统计信息去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
数据区(Timeseries metadata之前的部分) | 126.24 MB | 126.24 MB | 126.24 MB | 126.24 MB |
元数据区(Timeseries metadata之后的部分) | 79.17 MB | 38.17 MB | 22.17 MB | 12.25 MB |
总大小 | 205.41 MB | 164.41 MB | 148.41 MB | 138.49 MB |
总大小比例 | 100 % | 80.04 % | 72.25 % | 67.42 % |
每序列1个点(SNAPPY)
文件部分 / 文件所含内容 | 原始文件 | 仅保留起止时间统计信息去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
数据区(Timeseries metadata之前的部分) | 48.99 MB | 48.99 MB | 48.99 MB | 48.99 MB |
元数据区(Timeseries metadata之后的部分) | 79.16 MB | 38.16 MB | 22.16 MB | 12.24 MB |
总大小 | 128.15 MB | 87.15 MB | 71.15 MB | 61.23 MB |
总大小比例 | 100 % | 68.01 % | 55.52 % | 47.80 % |
每序列10个点(GZIP)
文件部分 / 文件所含内容 | 原始文件 | 仅保留起止时间统计信息去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
数据区(Timeseries metadata之前的部分) | 140.11 MB | 140.11 MB | 140.11 MB | 140.11 MB |
元数据区(Timeseries metadata之后的部分) | 79.17 MB | 38.17 MB | 22.17 MB | 12.25 MB |
总大小 | 219.28 MB | 178.28 MB | 162.28 MB | 152.36 MB |
总大小比例 | 100 % | 81.30 % | 74.01 % | 7669.74 48 % |
每序列1个点(GZIP)
文件部分 / 文件所含内容 | 原始文件 | 仅保留起止时间统计信息去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
数据区(Timeseries metadata之前的部分) | 62.03 MB | 62.03 MB | 62.03 MB | 62.03 MB |
元数据区(Timeseries metadata之后的部分) | 79.16 MB | 38.16 MB | 22.16 MB | 12.25 MB |
总大小 | 141.20 MB | 100.20 MB | 84.20 MB | 74.28 MB |
总大小比例 | 100 % | 70.96 % | 59.63 % | 52.61 % |
去除统计信息前后写入时间比较
...
(SSD)
CPU:i5-5257U CPU @ 2.70GHz
每序列10个点 (SNAPPY)
写入速度 / 文件所含内容 | 原始文件 | 去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
写入时间 | 39.858 s | 39.575 s | 39.674 s | 38.948 s |
写入时间比 | 100 % | 99.29 % | 99.54 % | 97.72 % |
每序列1个点 (SNAPPY)
写入速度 / 文件所含内容 | 原始文件 | 去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
写入时间 | 37.771 s | 37.401 s | 37.023 s | 36.993 s |
写入时间比 | 100 % | 99.02 % | 98.02 % | 97.94 % |
每序列10个点 (GZIP)
...
写入速度 / 文件所含内容 | 原始文件 | 仅保留起止时间统计信息去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID | |
---|---|---|---|---|---|
写入时间 | 8154.053 s | 7954.042 s | 53.782 s74 | 53. | 134465 s |
写入时间比 | 100 % | 99.98 % | 9799.52 % | 98.91 .46 % |
每序列1个点 (GZIP)
写入速度 / 文件所含内容 | 原始文件 | 仅保留起止时间统计信息去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID | ||||
---|---|---|---|---|---|---|---|---|
写入时间 | 7949.911 s | 7249.701 s | 65.384 s | 写入时间比 | 100 % | 90.98 % | 81.82 %49.384 s | 49.224 s |
写入时间比 | 100 % | 99.58 % | 98.94 % | 98.47 % |
去除统计信息前后写入时间比较 (HDD)
CPU:i7-10700 CPU @ 2.90GHz
每序列10个点 (SNAPPY)
写入速度 / 文件所含内容 | 原始文件 | 去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
写入时间 | 8.887 s | 8.837 s | 8.729 s | 8.627 s |
写入时间比 | 100 % | 99.44 % | 98.22 % | 97.07 % |
每序列1个点 (SNAPPY)
写入速度 / 文件所含内容 | 原始文件 | 去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
写入时间 | 8.182 s | 8.023 s | 8.092 s | 7.998 s |
写入时间比 | 100 % | 98.06 % | 98.90 % | 97.75 % |
每序列10个点 (GZIP)
写入速度 / 文件所含内容 | 原始文件 | 去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
写入时间 | 18.591 s | 18.293 s | 17.992 s | 18.003 s |
写入时间比 | 100 % | 98.40 % | 96.78 % | 96.83 % |
每序列1个点 (GZIP)
写入速度 / 文件所含内容 | 原始文件 | 去掉值统计信息 | 去除全部统计信息 | 去除全部统计信息及measurementID |
---|---|---|---|---|
写入时间 | 16.392 s | 16.113 s | 16.104 s | 15.982 s |
写入时间比 | 100 % | 98.30 % | 98.24 % | 97.50 % |
实验结论
(1)存储空间:每序列10个点的情况下,移除统计信息大约能节省33%的空间;每序列1个点的情况下,移除统计信息大约能节省50%的空间
(2)写入速度:去除统计信息可以节省2-3%的写入时间,写入时间与CPU关系很大,按照磁盘写入速度,SSD写磁盘占用的时间不到整个写入时间的4%,HDD写磁盘占用的时间不到整个写入时间的8%
实验源码
写入程序
Code Block |
---|
import java.io.File; import java.util.Random; import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType; import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding; import org.apache.iotdb.tsfile.fileSystem.FSFactoryProducer; import org.apache.iotdb.tsfile.read.common.Path; 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.LongDataPoint; import org.apache.iotdb.tsfile.write.schema.UnaryMeasurementSchema; /** * An example of writing data with TSRecord to TsFile It uses the interface: public void * addMeasurement(MeasurementSchema measurementSchema) throws WriteProcessException */ public class TsFileWriteTest { public static int deviceNum; public static int sensorNum; public static int fileNum; public static int pointNum = 10; public static void main(String[] args) { try { deviceNum = 1; // Integer.parseInt(cl.getOptionValue("d")); sensorNum = 1; // Integer.parseInt(cl.getOptionValue("m")); fileNum = 1; // Integer.parseInt(cl.getOptionValue("f")); } catch (Exception e) { e.printStackTrace(); } for (int fileIndex = 0; fileIndex < fileNum; fileIndex++) { try { String path = "/Users/SilverNarcissus/iotdb/tsfile_test" + "/withoutStat/" + deviceNum + "." + sensorNum + "/test" + fileIndex + ".tsfile"; File f = FSFactoryProducer.getFSFactory().getFile(path); if (f.exists()) { f.delete(); } try { TsFileWriter tsFileWriter = new TsFileWriter(f); for (int i = 1; i <= deviceNum; i++) { for (int j = 1; j <= sensorNum; j++) { Path path1 = new Path(Constant.DEVICE_PREFIX + i); tsFileWriter.registerTimeseries( path1, new UnaryMeasurementSchema( Constant.SENSOR_ + j, TSDataType.INT64, TSEncoding.RLE)); } } // construct TSRecord int count = 0; for (int j = 1; j <= deviceNum; j++) { for (int i = 1; i <= pointNum; i++) { TSRecord tsRecord = new TSRecord(i, Constant.DEVICE_PREFIX + j); for (int t = 1; t <= sensorNum; t++) { DataPoint dPoint1 = new LongDataPoint(Constant.SENSOR_ + t, new Random().nextLong()); tsRecord.addTuple(dPoint1); count++; } // write TSRecord tsFileWriter.write(tsRecord); if (count % 100000 == 0) { System.out.println(count); tsFileWriter.flushAllChunkGroups(); } } } tsFileWriter.close(); } catch (Throwable e) { e.printStackTrace(); System.out.println(e.getMessage()); } } catch (Throwable e) { e.printStackTrace(); System.out.println(e.getMessage()); } } } } |
...