如果您必须猜测数据库设计者的最高优先级,那么压缩数据的能力可能会在您的列表中占据很高的位置。您可以压缩的数据越多,它就越适合您的磁盘—这似乎非常重要。但是您可能已经注意到,大多数流行的数据库引擎提供很少或没有现成的数据压缩功能。
压缩数据的有用性和效率在很大程度上取决于环境。首先,数据存储空间通常不是大多数应用程序的限制因素。数据库的速度通常是。
但压缩不必以速度为代价;正如您所料,它实际上也可以提高速度。当数据四处移动时,它会通过各种瓶颈,这些瓶颈比压缩和解压缩所需的时间要长得多。这包括I/O或有限带宽。因此,如果在移动数据之前使数据更紧凑,可以节省大量时间。如果你在云上,它可以节省时间和金钱。还需要考虑虚拟内存,它比存储更稀缺。因此,压缩不仅仅适用于存储空间——假设使用得当。
数据库通常不投资于压缩功能还有其他原因。任何压缩算法的性能都完全取决于数据本身的属性。因此,在许多情况下,用户能够更好地决定何时以及如何压缩数据。
为了使压缩作为数据库功能具有意义,它必须从一开始就成为设计的优先事项。数据库需要知道如何在不同的场景中,在给定不同类型和数据量的情况下,从其算法中获得最佳性能。同时,理想情况下,压缩应该对用户毫不费力且透明,而不是让他们自己去权衡压缩。
当然,许多自由和开源的压缩技术已经存在,并且被广泛使用,比如JPEG或GZIP,所以数据库不一定要尝试重新发明轮子。另一方面,当用户已经使用的数据库也在同一个包中提供强大的压缩时,这对用户来说要容易得多,而不必混合和匹配将来可能不兼容的不同工具。
在本文中,我们将讨论RavenDB如何对这些类型的数据进行压缩:
-
文件集
-
文件修订
-
TCP和HTTP流量
-
大文本字段
-
映射和减少索引
-
时间序列数据
速度悖论
数据压缩可以是应用程序所做的一切的力倍增器,或者比无用还要糟糕。最基本的原因是[de]压缩是以CPU为代价的,并且需要最少的时间。
压缩算法由压缩速度、解压缩速度和压缩比(计算为未压缩大小除以压缩大小:1或更大的值)来判断。在高速低比和低速高比的算法之间存在一个频谱。每个算法都被设计为在该频谱中占据一些小生境。
无论是在频谱的末端还是在任何给定算法的操作过程中,都有一种趋势,即遇到收益递减的障碍。速度可能会下降得非常快,您越是追求最佳数据压缩比,反之亦然。
“无损”和“有损”算法之间还有一个重要区别。有损压缩可以提高速度或比率,但代价是永久改变数据。一个熟悉的例子是JPEG图像格式。RavenDB只使用无损压缩,其他大多数提供压缩的数据库也是如此。
如上所述,额外的存储空间并不总是值得速度方面的成本。由于给定GB物理磁盘空间的成本不断降低,以及云存储的可用性不断提高,存储不断变得更便宜、更丰富。(使用RavenDB云是利用这一点的好方法。)
对于今天的许多应用程序,最高优先级是用户与服务器通信时的往返速度。每次你按下网站上的一个按钮并等待它更新时,你希望他们不要浪费你的时间进行不必要的压缩。
当然,如果您的数据需要通过耗时的瓶颈,压缩通过减少需要通过的数据量来节省时间。这包括向磁盘写入数据和从磁盘读取数据这样简单的事情。与其他进程相比,每秒I/O(也称为IOPS)通常非常慢。同样,在云上,I/O时间通常比CPU时间花费更多的钱。
对于I/O而言,网络带宽也是如此。这就是为什么RavenDB在默认情况下压缩所有TCP和HTTP流量-在RavenDB实例之间以及服务器和客户端之间。
相反,如果您不希望数据通过任何重大瓶颈,那么压缩可能会成为数据管道中最慢的部分。
更多数据输入,更好的性能输出
算法的潜在效率最终取决于您提供给它的信息的属性。所有算法都依赖于利用数据中的重复模式。这也称为数据冗余,与信息熵密切相关。
任何重复多次的模式都可以用如下方式总结:只存储重复模式的一个副本,然后记录在未压缩数据中找到模式的所有位置。如果给定的模式足够频繁地重复,这可能导致任意大的压缩比。如果两个信息块的大小相等,那么它们的可压缩性决不是相等的:磁盘上的一兆字节可能包含随机噪声,或者可能只包含零。
这条经验法则通常适用:一大块信息往往比同一数据的任何小块都具有更多的冗余。例如,如果你看英语中的大多数句子(比如这一句),可能没有一个单词出现多次。但是当你增加你的样本-甚至只有两到三个句子-你会看到冗余的急剧增加。
需要强调的是,这不是铁律——压缩性能并不总是和您提供给它的数据量相关。有时,随着输入量的增加,您会发现自己花费越来越多的CPU来换取压缩比越来越小的改进。在这些情况下,输入太少和太多之间可能存在一个金色地带。例如,彼此相似的数据对象表可能比不同表的组合更可压缩。
但一般来说,小块数据通常冗余度较低,这使得压缩变得困难。RavenDB的文档压缩功能有一种独特的小数据块处理方法,这是它最大的优势之一。在介绍Zstd算法之后,我们将在下面进一步讨论这一点。
文档数据库中的压缩
像RavenDB这样的文档数据库是“NoSQL数据库”-它们不同于更常见的关系数据库,因为没有严格的数据模式。文档类似于行,文档的字段类似于列。文档可以分组到集合中,集合大致类似于表,但同一集合中的不同文档不必具有相同的结构,甚至不必具有任何相同的字段。
关系模式有利于压缩,因为行的结构在整个表中是冗余的。但这并不意味着文档数据库的效率必须降低。您可以选择将RavenDB视为关系数据库,方法是创建一个集合,其中所有文档都具有相同的结构。在这种情况下,冗余与模式中的冗余一样可压缩。您还可以选择利用NoSQL的自由度,减少压缩冗余,或者在两者之间做任何事情。
此外,文档数据库通常将数据存储为JSON。这是一种常见的数据格式,用于许多其他应用程序,如TCP请求和响应。这很方便,因为格式中使用的括号、引号、逗号和其他文本元素是高度冗余的。
“块”压缩问题
回到上一个主题:如果我们想增加输入数据量,我们面临一个问题。许多算法的一个限制是它们以不可分割的块处理数据。一块信息被一次压缩,并且还必须一次全部解压缩。
结果是,如果您将10 KB的数据压缩为一个单元,但现在您只想解压缩1 KB的数据,那么您就不走运了。您必须支付解压缩所有10 kB CPU和其他资源的成本。这种类型的压缩没有通用术语,所以我在这里称之为“块”压缩。另一种方法是“隔离”压缩。
原因在于,对于许多算法,压缩数据的特定部分和未压缩数据的具体部分之间不一定存在一一对应关系。压缩数据的一部分可能与未压缩数据的多个不同部分具有部分关系。同样,压缩数据的不同部分可能相互依赖,以便在解压缩期间正确解释。
块压缩的使用
块压缩是有限的,但很有用。在许多数据库中,块压缩是它们所能提供的全部功能。一个例子是流行的数据库MySQL。MySQL有本机压缩,但只在称为“页面”的块中。这些页面可以是1、2、4、8或16 KB。这意味着您不能利用任何只在大于16 KB的范围内出现的冗余。
此功能背后的假设是,在有限的情况下,您确实希望一次解压缩超过16KB。此外,如前所述,尝试一次压缩更多数据的回报可能会递减。因此,MySQL的数据压缩是有缺陷的,但对于许多应用程序来说可能是足够的。
RavenDB在几个特定的情况下很好地使用了块压缩:字段压缩、map-reduce索引压缩和时间序列压缩。
当您在一个字段中存储大量文本时,RavenDB会自动压缩它。这样做的好处是,为了读取或修改文档中的任何其他字段,不必解压缩字段。甚至压缩字段的名称也可以与文本本身分开访问。这与RavenDB的开箱即用全文搜索和索引功能无缝配合。
Map-reduce索引是一种特殊类型的索引,不仅可以查询数据,还可以执行快速复杂的聚合查询。Map reduce数据是高度冗余的,因此RavenDB会以非常高的压缩比自动压缩所有Map-reduce索引。
类似地,RavenDB的时间序列功能使用Gorilla压缩和算法LZ4压缩数据。时间序列数据由一系列数值(条目)组成,每个数值与时间戳相关联。它们可以高度压缩,因为您可以选择仅存储连续条目之间的差异,而不是单独记录每个条目。事实上,RavenDB通常只能够记录4位的条目,这是在实际压缩之前。
但在必要时,RavenDB使用Zstd来规避块压缩的问题。
Zstd压缩算法
Zstandard,或Zstd,是Facebook于2016年开发的开源算法。与LZ算法家族的所有成员一样,它是无损的。它的速度和压缩比都非常高。由于有22种可能的压缩“级别”,它在速比频谱的中间占据了很宽的范围。它的性能与频谱相同部分的许多其他算法类似,如果不是更好的话。
Zstd具有特别高的解压速度。通常,解压缩速度大致与压缩速度成比例增加,与压缩比成比例减少。在Zstd中,解压缩速度非常高,并且在不同的压缩级别上相对恒定。这使得Zstd在读取速度是最高优先级的所有场景中特别有用。
但到目前为止,Zstd压缩最有趣的方面是它的字典训练功能。所有LZ算法都使用字典,但它们用于块压缩-每个块一个字典。Zstd通过在样本数据上训练字典,然后将该字典释放出来,以便灵活地用于压缩它提供的任何数据,从而彻底改变了压缩。它甚至允许您解压缩压缩块的较小部分。
字典压缩
要了解字典是如何工作的,我们需要了解在没有字典的情况下压缩是如何工作。
例如,让我们压缩单词“Mississippi”。使用典型的非字典压缩算法,结果可能如下:
“Mis” (-1,1) (-3,4) “p” (-1,1) (-3,1)
单词的唯一部分用引号括起来,多余部分用括号编码。我们如何减压?解压缩后的单词以引号中的第一个文本“Mis”开头。接下来,每组括号将添加现有文本的某个部分的副本。第一个数字表示冗余序列的起始位置,第二个数字表示其长度。

第一组括号告诉您将一个字符返回到“s”,并仅重复该一个字符。现在我们有了“小姐”。接下来,我们回到三个字符,回到“我”,然后再向前四个字符。这些是“i”、“s”、“s”……我们如何得到第四个字符?在这种格式中,我们可以将序列继续到刚才添加的新“i”。这给了我们“密西西比”。接下来,添加“p”,依此类推。
这里的压缩比不是很大,因为重复序列非常短,但现在不要担心。关键是,在这种传统的压缩方法中,被解压缩的原始数据被用作仍然被压缩的数据中冗余序列的存储库,导致了我们刚才分析的这种复杂的递归模式。
这就是为什么压缩块也会被解压缩为块:如果不先解压缩“Missis”,就无法访问数据“ippi”。
字典是如何改变事物的?字典由所有序列的存储库组成,无论它们是否冗余。但它本身不是压缩数据的一部分。因此,例如,如果我们为密西西比州创建一个字典,我们可以记录序列“ssi”或“iss”。这比我们之前做的要简单一些:我们必须使用一个引用来创建序列“iss”,然后第二个引用才能指向它。有了字典,密西西比河现在可以压缩成这样的东西:

引用不必指向引用之前的数据段,而是可以自由指向字典的任何部分。字典可能非常高效,但正是Zstd的字典充分发挥了字典压缩的潜力。Zstd将字典与压缩数据完全分离。这样,一个字典就可以用来压缩1千字节或整个TB。
它还最终将我们从块压缩中解放出来,因为引用不依赖于压缩块的其他部分;它们指向外部的东西。
RavenDB对Zstd的有效使用
最后,我们可以谈谈RavenDB的文档压缩功能。RavenDB不是唯一使用Zstd的数据库。它甚至不是唯一一个使用Zstd的面向文档的数据库,MongoDB也这么做。然而,在4.2版中引入Zstd两年多之后,MongoDB仍然没有利用Zstd的字典训练功能。
RavenDB一次对一组文档应用压缩。RavenDB在前几个文档上训练Zstd以创建字典,然后使用该字典压缩进入集合的所有其他数据。
更好的是,数据库监控新文档的压缩比。如果该比率低于某个阈值,RavenDB将根据集合中最近修改的文档自动训练新字典。如果这本新字典比上一本好,它会替换它。这意味着进入集合的新数据将继续被压缩,不会中断,压缩比仍高于阈值。
整个过程对用户是透明的。您所需要做的就是选择要压缩的集合,其余的由RavenDB完成。Zstd压缩也可以应用于数据库中的所有文档修订。修订是对数据库中每个文档所做更改的记录。它们提供了一种跟踪数据过去的方法,允许您恢复旧数据或从意外的服务器故障中恢复。
希望本文能让您对压缩有一些了解,无论是在一般情况下还是在数据库引擎的上下文中。现在您了解了RavenDB是如何为各种场景提供高效压缩的,所有这些都是以最小的麻烦实现的。它是为数不多的经过如此精心设计并考虑到压缩的主要数据库之一。您还可以看到数据库对算法的选择是如何至关重要的:无论是对其自身的能力,还是对用户如何利用它。
有许多可用的免费压缩技术。但无论何时向堆栈中添加内容,都很容易导致意外的兼容性问题。如果您明智地选择了数据库,您就不必自己计算压缩,也不必为云资源买单。RavenDB在生活中的使命是让你安心。您可以专注于您的产品,知道数据在后端得到了很好的处理。
原文标题:Smarter Data Compression With RavenDB
原文作者:Mor Hilai
原文链接:https://dzone.com/articles/smarter-data-compression-with-ravendb




