环境: 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数据压缩的更多相关文章

  1. InfluxDB 1.6文档

    警告!此页面记录了不再积极开发的InfluxDB的早期版本.InfluxDB v1.7是InfluxDB的最新稳定版本. InfluxDB是一个时间序列数据库,旨在处理高写入和查询负载.它是TICK堆 ...

  2. Influxdb的存储引擎

    创建Influxdb数据库时,我们可以看到下面选项,每个选项的含义就是本文要描述的: Influxdb内部数据的存储可以使用不同的存储引擎.当前0.8.7版本支持的是LevelDB, RocksDB, ...

  3. SpringBoot 2.0 + InfluxDB+ Sentinel 实时监控数据存储

    前言 阿里巴巴提供的控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如持久化的后端数据库.可靠的配置中心等.目前 Sentinel 采用内存态的方式存储 ...

  4. influxDB 1.3 中文文档

    influxDB是一个旨在处理高并发写入和查询负载的时序数据库,它是TICK框架的第二部分,influxdb用于任何包含大量时序数据应用的后台存储,包括Devops监控.应用指标数据.物联网传感器数据 ...

  5. 时间序列数据库(TSDB)初识与选择(InfluxDB、OpenTSDB、Druid、Elasticsearch对比)

    背景 这两年互联网行业掀着一股新风,总是听着各种高大上的新名词.大数据.人工智能.物联网.机器学习.商业智能.智能预警啊等等. 以前的系统,做数据可视化,信息管理,流程控制.现在业务已经不仅仅满足于这 ...

  6. 通过Python将监控数据由influxdb写入到MySQL

    一.项目背景 我们知道InfluxDB是最受欢迎的时序数据库(TSDB).InfluxDB具有 持续高并发写入.无更新:数据压缩存储:低查询延时 的特点.从下面这个权威的统计图中,就可以看出Influ ...

  7. InfluxDB总结

    一.简介 InfluxDB(时序数据库)influxdb是一个开源分布式时序.时间和指标数据库,使用 Go 语言编写,无需外部依赖.其设计目标是实现分布式和水平伸缩扩展,是 InfluxData 的核 ...

  8. 简析时序数据库 InfluxDB

    时序数据基础 时序数据特点 时序数据TimeSeries是一连串随时间推移而发生变化的相关事件. 以下图的 CPU 监控数据为例,同个 IP 的相关监控数据组成了一条时序数据,不相关数据则分布在不同的 ...

  9. 认识Influxdb时序数据库及Influxdb基础命令操作

    认识Influxdb时序数据库及Influxdb基础命令操作 一.什么是Influxdb,什么又是时序数据库 Influxdb是一个用于存储时间序列,事件和指标的开源数据库,由Go语言编写而成,无需外 ...

随机推荐

  1. android BitmapDrawable的使用

    <span style="font-size:18px;"> //功能:显示缩略图,大小为40*40 //通过openRawResource获取一个inputStrea ...

  2. 14 道 JavaScript 题?

    http://perfectionkills.com/javascript-quiz/ https://www.zhihu.com/question/34079683 著作权归作者所有.商业转载请联系 ...

  3. golang的日志系统log和glog

    go语言有一个标准库,log,提供了最基本的日志功能,但是没有什么高级的功能,如果需要高级的特性,可以选择glog或log4go. 参考:https://cloud.tencent.com/devel ...

  4. Zookeeper Tutorial 1 -- Overview

    ZooKepper: 一个分布式应用的分布式协调服务(Distributed Coordination Service) 分布式服务难以管理, 他们容易造成死锁和竞争, ZooKepper的动机就是为 ...

  5. verilog语法实例学习(3)

    Verilog 操作运算符 算术运算符 +,-,*,/,**(加/减/乘/除/幂运算),乘法运算的结果的位宽是乘数和被乘数位宽的和. 在进行整数的除法运算时,结果要略去小数部分,只取整数部分:而进行取 ...

  6. Python二维数组构造

    周末用python要写个算法用到来二维数组, 一时间还不知道python怎么构造多维数组出来.看到一段不错的代码, 记录一下. Python使用list嵌套实现多维数组, PHP可以使用array嵌套 ...

  7. 基于ZigBee和STM32的智能家居控制系统的设计与实现(三)

    基于ZigBee和STM32的智能家居控制系统的设计与实现(三) 自从前两篇博客介绍了智能家居系统的基本实现机理后,收到了好多朋友的来信,和我讨论了好多的这方面的知识,在此很高兴,虽然自己做的这个所谓 ...

  8. First Missing Positive leetcode java

    题目: Given an unsorted integer array, find the first missing positive integer. For example, Given [1, ...

  9. 怎样用纯HTML和CSS更改默认的上传文件按钮样式

    如果你曾经试过,你就会知道,用纯CSS样式加HTML实现统一的上传文件按钮可能会很麻烦.看看下面的不同浏览器的截图.很明显的,他们长得很不一样. 我们的目标是创造一个简洁,用纯CSS实现的,在所有浏览 ...

  10. linux下elasticsearch 安装、配置及示例

    简介 开始学es,我习惯边学边记,总结出现的问题和解决方法.本文是在两台linux虚拟机下,安装了三个节点.本次搭建es同时实践了两种模式——单机模式和分布式模式.条件允许的话,可以在多台机器上配置e ...