InfluxDB数据压缩实践

在上述InfluxDB压缩算法的文章中,描述了具体使用的压缩算法,但单就这些算法并不能指导实践,为此本文针对不同的数据类型总结了压缩率,并给出了能提高压缩率的实际方法,写点姿势,希望能在实际生产中指导实践。

1
influx_inspect dumptsm xxx.tsm

上述命令是InfluxDB提供的一个inspect tsm文件的工具,运行上述命令会产生如下输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Summary:
File: /tmp/tsm1-test412750211/tsm1test039799558
Time Range: 2017-04-12T02:57:16.000352Z - 2017-05-05T06:30:35.900035Z
Duration: 555h33m19.899683s Series: 1 File Size: 217812294

Statistics
Blocks:
Total: 1 Size: 217812245 Min: 217812241 Max: 217812241 Avg: 217812245
Index:
Total: 1 Size: 36
Points:
Total: 20000000
Encoding:
Timestamp: none: 0 (0%) s8b: 1 (100%)
Float: none: 0 (0%) gor: 1 (100%)
Compression:
Per block: 10.89 bytes/point
Total: 10.89 bytes/point

其中statistics包含重要信息,首先总共的点数是2000w,其次timestamp的压缩方法是simple8b(100%),float使用的gorilla(100%)的压缩方法,最后,平均每个点占用的空间是10.89bytes。

下面我们从不同的数据类型入手,探究多种写点的姿势对压缩率的影响。

bool类型的数据

策略一,时间戳乱序写入

1
2
3
4
5
6
7
8
9
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+rand.Int63n(10000000000), true))
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect结果

1
2
3
Encoding:
Timestamp: none: 1 (100%)
Bool: none: 0 (0%) bp: 1 (100%)

磁盘文件大小:155MB

策略二, 时间戳等差数列写入

1
2
3
4
5
6
7
8
9
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i), true))
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect结果

1
2
3
Encoding:
Timestamp: none: 0 (0%) s8b: 0 (0%) rle: 1 (100%)
Bool: none: 0 (0%) bp: 1 (100%)

磁盘文件大小:2.4MB

策略三,时间戳升序写入

1
2
3
4
5
6
7
8
9
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i)*1000000000000+rand.Int63n(1000000000), true))
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect结果

1
2
3
Encoding:
Timestamp: none: 0 (0%) s8b: 1 (100%)
Bool: none: 0 (0%) bp: 1 (100%)

磁盘上文件大小:105MB

结论

2000w个bool类型的数据实际上是2000w个bool类型的数据+2000w个int64的时间戳,bool类型数据压缩很固定,只有2.4MB,而且对压缩率基本没有影响;

而int64时间戳的才是真正的影响压缩率的大头,从上述策略中得出,时间戳等比数列的压缩率最高,其次是升序时间戳,最后是完全无序随机。

InfluxDB的主要使用场景是IoT时序数据,这些数据绝大部分都遵从时间升序的规律,甚至对于一些定时采样的数据,时间戳甚至是等比数列的,也就可以使这些数据有相当大的压缩率。

int类型的数据

上述分析,已经得出时间戳对数据的影响了,2000w个int类型的数据实际上是4000w个int64的数据

时间戳等比数列 +随机int数据

1
2
3
4
5
6
7
8
9
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i)*1000000000000, rand.Int63n(1000000000)))
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect 结果

1
2
3
Encoding:
Timestamp: none: 0 (0%) s8b: 0 (0%) rle: 1 (100%)
Int: none: 0 (0%) s8b: 1 (100%)

磁盘文件大小:99MB

时间戳等比数列 +升序int数据

1
2
3
4
5
6
7
8
9
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i)*1000000000000, int64(i)*1000000+rand.Int63n(1000000)))
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect 结果

1
2
3
Encoding:
Timestamp: none: 0 (0%) s8b: 0 (0%) rle: 1 (100%)
Int: none: 0 (0%) s8b: 1 (100%)

磁盘文件大小:77MB

时间戳等比数列 + 整体升序,局部随机数据

1
2
3
4
5
6
7
8
9
10
11
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
pos := 0
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i)*1000000000000, int64(pos/100000)*1000000+rand.Int63n(1000000)))
pos++
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect结果

1
2
3
Encoding:
Timestamp: none: 0 (0%) s8b: 0 (0%) rle: 1 (100%)
Int: none: 0 (0%) s8b: 1 (100%)

磁盘文件大小:63MB

结论

在上述策略中,我们排除了时间戳对数据压缩的影响,所有的时间戳都是递增的,在这种情况下,压缩的效率就完全取决于int数据了。

从三个策略看,int的压缩全部是s8b的压缩,而且数据是否有序这个因素影响并不是很显著。

float类型的数据

为了排除时间戳对压缩率的影响,我们同样采取int类型数据的三种策略

时间戳等比数列 +随机float数据

1
2
3
4
5
6
7
8
9
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i)*1000000000000, rand.Float64()*100000))
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect 结果:

1
2
3
Encoding:
Timestamp: none: 0 (0%) s8b: 0 (0%) rle: 1 (100%)
Float: none: 0 (0%) gor: 1 (100%)

磁盘文件大小: 157MB

时间戳等比数列 +升序float数据

1
2
3
4
5
6
7
8
9
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i)*1000000000000, float64(i*1000000)))
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect结果:和上述结果一样
磁盘文件大小:119MB

时间戳等比数列 + 整体升序,局部随机数据

1
2
3
4
5
6
7
8
9
10
11
values := make([]tsm1.Value, 0, 20000000)
now := time.Now().Add(-time.Minute * 60 * 24 * 365).UnixNano()
pos := 0
for i := 0; i < 20000000; i++ {
values = append(values, tsm1.NewValue(now+int64(i)*1000000000000, float64(int64(pos/100000)*1000000)+rand.Float64()*1000000))
pos++
}
if err := w.Write([]byte("cpu"), values); err != nil {
fmt.Printf("unexpected error writing: %v", err)
return
}

inspect结果:和上面一样
磁盘文件大小:157MB

结论

float的压缩和int的压缩类似,所有的float压缩也全是用的gorrila的压缩方法,且和是否有序没有显著关系。

总结

首先,InfluxDB中需要压缩的数据由两部分构成,实际的数据(int,float,boo,string)和timestamp(int),且一个实际的数值对应一个实际的时间戳,时间戳的个数占比是高达50%的,所以时间戳的压缩比的高低直接关系着整体数据的压缩比的高低;timestamp的压缩方法是s8b的压缩方法,所以在时间戳有序的情况下,能有效提高压缩比。

其次,bool数据的压缩是恒定的,一个bool值占一个bit,无论什么情况下都是这样的;float使用的gorrila的压缩方法,也没有对数据顺序的要求。

最后可以看出InfluxDB对数据的压缩率很大情况下取决于数据的时间戳是否有序,越有序(升序)那么压缩率越高。

欢迎留言讨论~