Influxdb数据压缩
环境: CentOS6.5_x64
InfluxDB版本:1.1.0
数据压缩可以参考:
https://docs.influxdata.com/influxdb/v1.1/concepts/storage_engine/#compression
influxdb根据不同的数据类型会采用不同的压缩算法。
- int
首先使用ZigZag算法进行编码,如果编码后的值小于 (1 << 60 ) - 1,使用simple8b算法;
如果大于该值,不压缩;
- timestamp
时间戳为独立的数据类型,并且具有一定的规律可循,在InfluxDB中, 针对时间戳先执行排序操作后使用差分编码算法进行编码,然后再根据编码结果采用不同的算法。
解释如下:
1、根据输入的原始数组arrValues计算出差值数组deltaValues;
2、如果差值数组的所有值相同,使用RLE编码算法;
3、如果差值数组的所有值不同,并且差值数组的最大值大于(1 << 60)- 1,使用Raw编码算法;
4、如果差值数组的所有值不同,并且差值数组的最大值不大于(1 << 60)- 1,使用Packed编码;
- float
使用 Facebook Gorilla paper提供的浮点数压缩算法
- bool
只有1位数据,采用简单的位数据打包策略
- string
采用snappy算法
压缩算法介绍
ZigZag算法
ZigZag这个算法使用的基础就是认为在大多数情况下,我们使用的数字都是不大的数字。 其原理是将标志位后移至末尾,并去掉编码中多余的0,从而达到压缩效果。
算法描述
编码过程
其编码过程如下:
1)获取int64类型输入X;
2)对X执行左移1位的操作,得到X1;
3)对X执行右移63位的操作,得到X2;
4)对X1和X2执行异或运算,得到ZigZag编码结果;
从编码过程可以看出,该算法的原理是将标志位后移至末尾,如果是负数则保留符号位移过来的1,非负数直接为0(异或操作),去掉编码中多余的前导0,则可以使用更少的字节来存储数据,从而达到压缩效果。
比如int64类型的数字1,其标志位为0,用二进制表示时前面会有63个0,最后一位才是1,执行位移操作后,X1为2,X2为0,执行异或操作后的值为2,前面有62个0, 去掉前面多余的0,仅用最后8位数表示,则编码后的数据为: 00000010 。
标志位后移主要是为了处理负数,比如int64类型的数字 -1 ,其标志位为1,用二进制表示时两端各有一个1,中间有62个0,执行位移操作后,X1为0xfffffffffffffffe,X2为0xffffffffffffffff,执行异或操作后的值为1,前面有62个0,去掉前面多余的0,仅用最后8位数表示,则编码后的数据为: 00000001 。
如果用原来的64位int传输显然很浪费带宽,可以使用8位的int传输,则带宽为原来的 1/8 ,针对小数据压缩效果很明显。
小整数对应的ZigZag码字短,大整数对应的ZigZag码字长。在特定的场景下,比如,要传输的整数为大整数居多,ZigZag编码的压缩效率就不理想了。
解码过程
该算法的解码过程如下:
1)获取ZigZag编码结果V;
2)对V执行右移1位的操作,得到结果V1;
3)将V与1相与,得到中间值,将中间值左移63位,然后右移63位,得到结果V2;
4)对V1和V2执行异或操作,得到结果X;
算法实现
ZigZag编码实现(go语言代码):
// ZigZagEncode converts a int64 to a uint64 by zig zagging negative and positive values
// across even and odd numbers. Eg. [0,-1,1,-2] becomes [0, 1, 2, 3]
func ZigZagEncode(x int64) uint64 {
return uint64(uint64(x<<) ^ uint64((int64(x) >> )))
} // ZigZagDecode converts a previously zigzag encoded uint64 back to a int64
func ZigZagDecode(v uint64) int64 {
return int64((v >> ) ^ uint64((int64(v&)<<)>>))
}
其它
示例代码:
package main import (
"fmt"
) func ZigZagEncode(x int64) uint64 {
return uint64(uint64(x<<) ^ uint64((int64(x) >> )))
} func ZigZagDecode(v uint64) int64 {
return int64((v >> ) ^ uint64((int64(v&)<<)>>))
} func main() {
var arr []int64 arr = append(arr,-)
arr = append(arr,)
arr = append(arr,) fmt.Printf("original \t encode \t decode \t\n")
for _,a := range arr {
a1 := ZigZagEncode(a)
a2 := ZigZagDecode(a1)
fmt.Printf("%d \t\t %d \t\t %d\n",a,a1,a2)
}
}
运行效果如下:
[root@localhost test]# go run zigzagTest1.go
original encode decode
- - [root@localhost test]#
simple8b算法
Simple8b算法是64位算法,实现将多个整型数据(在 0 和 1<<60 - 1 之间)压缩到一个64位的存储结构中。
其中前4位为选择器,后面60位用于存储数据,数据使用下表进行编码:
┌──────────────┬─────────────────────────────────────────────────────────────┐
│ Selector │ │
├──────────────┼─────────────────────────────────────────────────────────────┤
│ Bits │ │
├──────────────┼─────────────────────────────────────────────────────────────┤
│ N │ │
├──────────────┼─────────────────────────────────────────────────────────────┤
│ Wasted Bits│ │
└──────────────┴─────────────────────────────────────────────────────────────┘
压缩过程描述
压缩流程如下:
1)selector 从 0 到 15 ,依次检查是否满足压缩条件;
2)如果可以被压缩,则使用对应规则执行压缩过程;
3)记录已压缩数据数组的下标,并产生新的未压缩数据数组;
4)执行步骤1)直至未压缩数组为空;
下面举例说明下该算法的大致流程及压缩效果。
1、数组中存储的数字相同
比如有如下数组(30个3):
[ ]
该数组中的最大数据为3,可以使用2位二进制表示,则查表可得,Selector等于3,每2个bit存储一个数据,可以存储30个数据。
前4位数据为: 0011
后面存储了30个3,则后面60位数据为:111111111111111111111111111111111111111111111111111111111111
两部分数据合并在一起表示:0011111111111111111111111111111111111111111111111111111111111111
使用16进制进行表示: 0x3fffffffffffffff
因此,30个3使用该算法压缩后可表示为: 0x3fffffffffffffff
如果上面的30个3都使用int64进行存储,该算法的压缩后占用空间为原来的 3.3%( (1 * 8) / (30.0 * 8)= 0.033);
如果上面的30个3都使用int32进行存储,该算法的压缩后占用空间为原来的 6.7%( (1 * 8) / (30.0 * 4)= 0.067);
如果上面的30个3都使用int8(即一个Byte)进行存储,该算法的压缩后占用空间为原来的 26.7%( 8 / 30.0 = 0.267);
2、数组中存储的数字不同
上面的数据是比较理想的情况,如果有如下数组:
[ ]
可以将数据分成3组分别进行压缩。
1)前15个数据中([0 1 2 3 4 5 6 7 8 9 10 11 12 13 14])的最大值为14(0x0E), 可以使用4位bit进行存储,编码规则选择5,则这15个数据可存储为: 0x50123456789abcde , 如果逆序存放,则表示为: 0x5edcba9876543210
事实上,Simple8b算法中使用逆序存放数据(go语言):
// pack15 packs 15 values from in using 3 bits each
func pack15(src []uint64) uint64 {
return << |
src[] |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<< |
src[]<<
}
2)紧挨着的12个数字([15 16 17 18 19 20 21 22 23 24 25 26])的最大值为26(0x1A), 可以使用5位bit进行存储,编码器选择6,则这12个数据可存储为: 0x6d6717b56939460f
可用以下代码进行验证(Python代码):
def pack12(src) :
ret = 6<<60
for i in range(12):
ret = ret | (src[i] <<(i*5))
return ret arr = range(15,27)
print arr,len(arr) ret = pack12(arr)
print ret,'0x%08x' % ret
3)后面3个数字([ 27 28 29 ])的最大值为29,但只有3个数字,编码规则选择13, 则这3个数据可存储为: 0xd0001d0001c0001b
可用以下代码进行验证(Python代码):
def pack3(src) :
ret = 13<<60
for i in range(3):
ret = ret | (src[i] <<(i*20))
return ret arr = range(27,30)
print arr,len(arr) ret = pack3(arr)
print ret,'0x%08x' % ret
如果上面的30个数据都使用int64进行存储,该算法的压缩后占用空间为原来的 10%( (3 * 8) / (30.0 * 8)= 0.1);
如果上面的30个数据都使用int32进行存储,该算法的压缩后占用空间为原来的 20%( 3 * 8 / (30.0 * 4) = 0.2);
如果上面的30个数据都使用int8(即一个Byte)进行存储,该算法的压缩后占用空间为原来的 80%( 3 * 8 / 30.0 = 0.8);
由上面两个例子可以看出,该算法针对使用int64和int32存储数据的场景压缩效果是比较明显的,如果存储数据的范围波动比较大,需要使用64位或32位的int进行存储,但大部分数据的绝对值比较小(比如可以使用一个字节存储),则使用该算法的压缩效果比较明显。
解压缩过程描述
解压缩流程如下:
1)首先获取压缩数据V的前4个bit作为Selector的值;
2)如果Selector的值大于或等于16,直接出错返回;
3)如果Selector的值小于16,执行解码操作:根据不同的Selector值选取不同的解码规则进行解码操作。
下面举例说明下该算法的大致流程。
1、数组中存储的数字相同
比如V为 : 0x3fffffffffffffff
则Selector为3(Selector = V >> 60),查表可知每2个bit存储一个数据,则解码过程如下(python示例代码):
def unpack30(V,refDst):
for i in range(30):
dst[i] = (V >> (i*2)) & 3
dst=[0]*30
V = 0x3fffffffffffffff
unpack30(V,dst)
print dst
2、数组中存储的数字不同
比如V为 : 0x5edcba9876543210
则Selector为5(Selector = V >> 60),查表可知每4个bit存储一个数据,则解码过程如下(python示例代码):
def unpack15(V,refDst):
for i in range(15):
dst[i] = (V >> (i*4)) & 15
dst = [0]*15
V = 0x5edcba9876543210
unpack15(V,dst)
print dst
其它
示例代码如下(go语言):
package main import (
"fmt" "github.com/jwilder/encoding/simple8b"
) func testEncode(in []uint64) {
enc := simple8b.NewEncoder() for _,e := range in {
enc.Write(e)
} fmt.Println("data in : ",in) encoded, err := enc.Bytes()
if err != nil {
fmt.Println("error occur!")
}
fmt.Println("encoded(arr) : ",encoded)
fmt.Printf("len(encoded) : %d bytes\r\n",len(encoded))
fmt.Printf("encoded(hex) : ")
for _,ele := range encoded {
fmt.Printf("%x ",ele)
}
fmt.Println("") fmt.Printf("decode : ")
dec := simple8b.NewDecoder(encoded)
i :=
for dec.Next() {
if i >= len(in) {
fmt.Printf("Decoded too many values: got %v, exp %v", i, len(in))
} decTmp := dec.Read()
if decTmp != in[i] {
fmt.Printf("Decoded[%d] != %v, got %v", i, in[i], dec.Read())
}else{
fmt.Printf("%d ",decTmp)
}
i +=
}
fmt.Println("")
fmt.Println("--------------------------")
} func main(){
N :=
in := make([]uint64, N)
for i:=;i < N;i++ {
in[i]=
}
testEncode(in)
for i := ; i < N ; i++ {
in[i] = uint64(i)
}
testEncode(in) }
运行效果如下:
[root@localhost test]# ./simp8bTest1
data in : [ ]
encoded(arr) : [ ]
len(encoded) : bytes
encoded(hex) : 3f ff ff ff ff ff ff ff
decode :
--------------------------
data in : [ ]
encoded(arr) : [ ]
len(encoded) : bytes
encoded(hex) : 5e dc ba 6d b5 f d0 1d c0 1b
decode :
--------------------------
[root@localhost test]#
时间戳类型相关编码算法
RLE编码算法描述
使用该算法的前提是差值数组的所有数值都相同。使用该算法进行编码时,其存储结构如下:
解释如下:
EncodeType : 记录编码类型,占4个bit
Divisor :记录除数的log10值,占4个bit
Timestamp : 记录第一个时间戳的值
DeltaValue : 记录第一个差值
N : 重复次数
该算法的核心思想是记录数据的重复次数,其存储结构的第一个字节的高4位用于记录该存储结构使用了RLE编码,后4位记录除数的log10值。 由于差值数组是相对原始数组的第一个数据计算的,所以原始数组的第一个值(第一个时间戳)必须记录,即上述结构中的Timestamp字段。 差值数组的所有值都相同,所以可以在存储结构中可以记录第一个差值和重复次数,即上述结构中的DeltaValue字段和N字段。
Raw编码算法描述
使用该算法的前提是差值数组的最大值大于(1 << 60)- 1。使用该算法进行编码时,其存储结构如下:
解释如下:
EncodeType :编码类型,和其它结构兼容,第一个字节的前4个bit用于记录编码类型;
RawData : 原始数组的数据;
该算法数据没有压缩,反而增加了一个字节。 为了和其它结构兼容,第一个字节的前4个bit用于记录当前存储的数据使用的是Raw编码类型。
Packed编码算法描述
使用该算法的前提是在差值数组的所有数值均不同,并且差值数组中数据的最大值不大于(1 << 60)- 1 。使用该算法进行编码时,其存储结构如下:
解释如下:
EncodeType :记录编码类型,占4个bit;
Divisor :记录除数的log10值,站4个bit;
Timestamp :记录第一个时间戳的值;
Simple8bData :差值数组使用Simple8b算法编码后的结果;
该算法首先使用差值编码对原始数据进行编码,将编码后的值除于最大共同除数Divisor(10的倍数或1), 使差分数组的值尽量缩小。然后将差值数组使用Simple8b算法进行编码,进一步提高压缩效果。
浮点数XOR算法描述
第一个值不压缩, 后面的值是跟第一个值XOR的结果来的,如果结果相同,仅存储一个0, 如果结果不同,存储XOR后的结果。
算法描述
该算法是结合遵循IEEE754标准的浮点数存储格式的数据特征设计的特定算法。
数据编码过程如下:
1、第一个值不压缩(记录为v0);
2、计算后续值v与第一个值v0的异或值vDelta;
3、如果vDelta为0(即:v与v0的值相同),接下来的一个bit存储一个0(占用一个bit);
4、如果vDelta不为0(即:v与v0的值不相同),接下来的一个bit存储一个1(占用一个bit),然后根据vDelta的值分以下两种情况进行处理:
如果重置前导值或尾数存储空间更优化,则按如下流程处理:
1)接下来的一个bit写入1;
2)接下来的 5 个 bit 写入vDelta值(二进制表示)中前导0的个数leading;
3)接下来的 6 个 bit 写入vDelta值(二进制表示)中有效位大小sigbits;
4)将vDelta值(二进制表示)右移去掉后面多余的0(长度前面有效数字已经标记过)得到vDelta2,写入vDelata2的值(仅有效长度);
如果重置前导值或尾数存储空间没有达到更优效果,则之前使用之前的参数,按如下流程处理:
1)接下来的一个bit写入0;
2)将vDelta值(二进制表示)右移去掉后面多余的0(长度前面有效数字已经标记过)得到vDelta2,写入vDelata2的值(仅有效长度);
存储示例1
比如有以下数组(30个12):
[12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12]
存储结果(12.0的二进制表示方式后面跟29个bit的0,数据补齐后用16进制表示) : 0x402800000000000000000000
共12个字节,则压缩后的数据为原来的: (12 * 1.0) / (30 * 8.0) = 0.05 = 5%
该算法的解码过程与编码过程刚好相反,这里暂不描述。
参考资料:
http://www.vldb.org/pvldb/vol8/p1816-teller.pdf
snappy算法
以下是Google几年前发布的一组测试数据(《HBase: The Definitive Guide》):
Algorithm % remaining Encoding Decoding
GZIP 13.4% MB/s MB/s
LZO 20.5% MB/s MB/s
Zippy/Snappy 22.2% MB/s MB/s
其中:
1)GZIP的压缩率最高,但是它是CPU密集型的,对CPU的消耗比其他算法要多,压缩和解压速度也慢;
2)LZO的压缩率居中,比GZIP要低一些,但是压缩和解压速度明显要比GZIP快很多,其中解压速度快的更多;
3)Zippy/Snappy的压缩率最低,而压缩和解压速度要稍微比LZO要快一些。
好,就这些了,希望对你有帮助。
本文github地址:
https://github.com/mike-zhang/mikeBlogEssays/blob/master/2017/20170423_Influxdb数据压缩描述.rst
欢迎补充
Influxdb数据压缩的更多相关文章
- InfluxDB 1.6文档
警告!此页面记录了不再积极开发的InfluxDB的早期版本.InfluxDB v1.7是InfluxDB的最新稳定版本. InfluxDB是一个时间序列数据库,旨在处理高写入和查询负载.它是TICK堆 ...
- Influxdb的存储引擎
创建Influxdb数据库时,我们可以看到下面选项,每个选项的含义就是本文要描述的: Influxdb内部数据的存储可以使用不同的存储引擎.当前0.8.7版本支持的是LevelDB, RocksDB, ...
- SpringBoot 2.0 + InfluxDB+ Sentinel 实时监控数据存储
前言 阿里巴巴提供的控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如持久化的后端数据库.可靠的配置中心等.目前 Sentinel 采用内存态的方式存储 ...
- influxDB 1.3 中文文档
influxDB是一个旨在处理高并发写入和查询负载的时序数据库,它是TICK框架的第二部分,influxdb用于任何包含大量时序数据应用的后台存储,包括Devops监控.应用指标数据.物联网传感器数据 ...
- 时间序列数据库(TSDB)初识与选择(InfluxDB、OpenTSDB、Druid、Elasticsearch对比)
背景 这两年互联网行业掀着一股新风,总是听着各种高大上的新名词.大数据.人工智能.物联网.机器学习.商业智能.智能预警啊等等. 以前的系统,做数据可视化,信息管理,流程控制.现在业务已经不仅仅满足于这 ...
- 通过Python将监控数据由influxdb写入到MySQL
一.项目背景 我们知道InfluxDB是最受欢迎的时序数据库(TSDB).InfluxDB具有 持续高并发写入.无更新:数据压缩存储:低查询延时 的特点.从下面这个权威的统计图中,就可以看出Influ ...
- InfluxDB总结
一.简介 InfluxDB(时序数据库)influxdb是一个开源分布式时序.时间和指标数据库,使用 Go 语言编写,无需外部依赖.其设计目标是实现分布式和水平伸缩扩展,是 InfluxData 的核 ...
- 简析时序数据库 InfluxDB
时序数据基础 时序数据特点 时序数据TimeSeries是一连串随时间推移而发生变化的相关事件. 以下图的 CPU 监控数据为例,同个 IP 的相关监控数据组成了一条时序数据,不相关数据则分布在不同的 ...
- 认识Influxdb时序数据库及Influxdb基础命令操作
认识Influxdb时序数据库及Influxdb基础命令操作 一.什么是Influxdb,什么又是时序数据库 Influxdb是一个用于存储时间序列,事件和指标的开源数据库,由Go语言编写而成,无需外 ...
随机推荐
- 阿里云linux图形界面(centos6)
阿里云linux图形界面的安装方法:安装gnome图形化桌面#yum groupinstall -y "X Window System"#yum groupinstall -y & ...
- 唐顿庄园第一至五季/全集Downton Abbey迅雷下载
本季Downton Abbey 1(2010)看点:ITV古装剧剧<唐顿庄园>由曾因<高斯福德庄园>而荣获奥斯卡的金牌编剧Julian Fellowes一手打造,明星云集的演员 ...
- 推荐一款移动端的web UI控件 -- mobiscroll
用mobiscroll 可实现ios系统自带的选择器控件效果,支持几乎所有的移动平台(iOS, Android, BlackBerry, Windows Phone 8, Amazon Kindle) ...
- Android之把eoe客户端的关联ViewPager的滑动条勾出来使用
使用代码: /** * A PageIndicator is responsible to show an visual indicator on the total views * number a ...
- Android性能检测工具——traceview
之前的几篇文章中介绍了android中常用的一些工具,今天介绍的工具也是比较实用和方便的,它可以用量化的指标告诉我们哪个方法执行的时间最长,被调用的次数最多,有没有重复调用.下面我们就来看看它是怎么为 ...
- 一分钟了解:String & StringBuilder & StringBuffer
这三个都是字符串对象,本篇就来分析下它们的使用途径,力求简单明了. 一.String String 长度是不可变的,如果你要改变string对象的字符或者是拼接字符的话,系统就会新建一个string, ...
- SeekBar的用法和自定义滑块的样式
SeekBar继承自ProgressBar,所以基本一样,我们自定义一般也就是顶一个滑块的图片而已. 布局文件 <RelativeLayout xmlns:android="http: ...
- POJO与PO、VO的区别
http://www.cnblogs.com/wangjunwei/p/3859360.html POCO的概念是从java的POJO借用而来,而两者的含义是一致的,不同的仅仅是使用的语言不一样.所以 ...
- [leetcode]Interleaving String @ Python
原题地址:https://oj.leetcode.com/problems/interleaving-string/ 题意: Given s1, s2, s3, find whether s3 is ...
- [leetcode]Combinations @ Python
原题地址:https://oj.leetcode.com/problems/combinations/ 题意:组合求解问题. 解题思路:这种求组合的问题,需要使用dfs来解决. 代码: class S ...