对于列压缩选项,PostgreSQL 14提供了新的压缩方法LZ4。与TOAST中现有的PGLZ压缩方法相比,LZ4压缩更快。本文介绍如何使用整个选项,并和其他压缩算法进行性能比较。
背景
PG中,页是存储数据的单位,默认是8KB。一般情况下,一行数据不允许跨页存储。然而,有一些变长的数据类型,存储的数据可能超出一页大学。为了克服整个限制,大字段域会被压缩或者分割成多个物理行。这个技术就是TOAST:
默认情况下,如果表中有变长列,行数据的大小超过TOAST_TUPLE_THRESHOLD(默认2KB)就会触发TOAST。首先,会先压缩数据;压缩后如果仍然太大,会溢出存储。需要注意,如果列的存储策略指定EXTERNAL/PLAIN,压缩会被禁止。
PG14之前版本,TOAST仅支持一个压缩算法PGLZ(PG内置算法)。但是其他压缩算法可能比PGLZ更快或者有更高的压缩率。PG14中有了新压缩选项LZ4压缩,这是一个以速度著称的无损压缩算法。因此我们可以期望它有助于提高TOAST压缩和解压缩的速度。
如何使用LZ4?
为了使用LZ4压缩特性,在编译时需要指定--with-lz4,并且在操作系统中按照LZ4库。通过GUC参数default_toast_compression可以指定PG实例的TOAST默认压缩算法。可以在postgresql.conf中配置,也可以通过SET命令仅改变当前连接:
postgres=# SET default_toast_compression=lz4;
SET
在CREATE TABLE创建表时指定列压缩算法:
我们使用d+命令可以看到所有列的压缩算法。如果列不支持或者没有指定压缩算法,那么会在Compression列显示空格。上面的例子中,id列不支持压缩算法,col1列使用PGLZ,col2使用LZ4,col3没有指定压缩算法,那么它会使用默认的压缩算法。
可以通过ALTER TABLE修改列压缩算法,但需要注意,修改后的算法仅影响执行整个命令后的insert数据。
postgres=# INSERT INTO tbl VALUES (1, repeat('abc',1000), repeat('abc',1000),repeat('abc',1000));
INSERT 0 1
postgres=# ALTER TABLE tbl ALTER COLUMN col1 SET COMPRESSION lz4;
ALTER TABLE
postgres=# INSERT INTO tbl VALUES (2, repeat('abc',1000), repeat('abc',1000),repeat('abc',1000));
INSERT 0 1
postgres=# SELECT id,
postgres-# pg_column_compression(id) AS compression_colid,
postgres-# pg_column_compression(col1) AS compression_col1,
postgres-# pg_column_compression(col2) AS compression_col2,
postgres-# pg_column_compression(col3) AS compression_col3
postgres-# FROM tbl;
id | compression_colid | compression_col1 | compression_col2 | compression_col3
---+-------------------+------------------+-----
1 | | pglz | lz4 | lz4
2 | | lz4 | lz4 | lz4
(2 rows)
可以看到在修改压缩算法前插入的行,col1仍使用PGLZ压缩算法,即使将压缩算法从PGLZ修改到了LZ4。(那么,修改后进行解压时使用哪个算法呢?)
需要注意,如果从其他表扫数据插入本表,例如CREATE TABLE ...AS...或者INSERT INTO...SELECT...,插入的数据使用的压缩算法仍然使用原始数据的压缩方法。pg_dump和pg_dumpall也添加了选项--no-toast-compuression,使用整个选项后,不会dump出TOAST压缩选项。
性能比较
测试了LZ4和PGLZ压缩率和压缩速度。并添加了未压缩数据的测试结果(指定存储策略为EXTERNAL),对于未压缩数据,没有压缩和解压的耗时,但读和写数据的时间会增加。
测试使用的数据:PG documents(一行数据一个HTML文件);SilesiaCorpus提供的数据,包括HTML、Text、源代码、可执行二进制文件、图片
测试机器使用Intel? Xeon? Silver 4210 CPU @2.20GHz with 10 cores/20 threads/2 sockets。
使用pgbench测试SQL语句执行时间,pg_table_size检查表大学(每次执行前都执行VACUUM FULL排除死记录的影响)。
压缩率
PGLZ和LZ4的压缩率都依赖于重复数据,重复的元组越多,压缩率越高。但是如果PG评估这样的压缩率不好时,就不会执行压缩,即使数据大小达到了阈值。因为压缩并没有高效节省磁盘空间,还会带来解压锁的额外时间和资源消耗。
当前PG14中,PGLZ需要至少25%的压缩率,LZ则仅比未压缩数据时小即可。我比较了LZ4、PGLZ的表与未压缩表大小。可以看到,大部分场景下,PGLZ的压缩率稍微好点,压缩率评价为2.23,LZ4的压缩率为2.07。这意味着PGLZ可以节省7%的磁盘空间。
Figure 1 - Comparing table sizes (in KB)
压缩/解压缩速度
Insert和查询时TOAST数据会被压缩和解压缩。因此,我执行一些SQL语句查看不同压缩算法带来的影响。
首先比较了INSERT语句,列使用LZ、PGLZ和未使用压缩时的性能。可以看到与未压缩数据比,LZ4耗费稍微多一点时间,PGLZ耗费时间更多。LZ4的压缩时间比PGLZ平均节省20%。这是一项非常显著的改进。
Figure 2 - Comparing INSERT performance
下面比较SELECT。与PGLZ相比,LZ4可以节省20%的时间,与未压缩数据相比,没有太大差别。解压缩的消耗已经降到了很低了。
Figure 3 - Comparing SELECT performance
再比较16个客户端的INSERT语句并发。与PGLZ相比使用LZ4的单大文件(HTML,英文文本,源代码,二进制执行文件,图片)的压缩性能快60%-70%。插入多个小文件(PG文档),性能提升不大。和未压缩的数据相比,有巨大提升,猜测使用压缩减少了写入磁盘的数据量。
Figure 4 - Comparing INSERT performance with 16 clients
16个客户端的SELECT,多数场景下,LZ4性能优于PGLZ:
Figure 5 - Comparing SELECT performance with 16 clients
同样也比较了使用字符串函数的SELECT、UPDATE处理文本的速度。整个场景下LZ4优于PGLZ。LZ4压缩算法的数据与未压缩数据相比,函数处理的速度几乎一样,LZ4算法几乎不会影响字符串操作速度。
Figure 6 - Comparing performance using string functions
与PGLZ相比,LZ4压缩和解压缩TOAST数据更加高效,并提供很好的性能。和未压缩数据相比,查询速度几乎一样,和PGLZ相比,插入快80%。当然某些场景下压缩率不太好,但如过你想要提升执行速度,强烈推荐使用LZ4算法。
同样需要注意,需要考虑表中的数据是否合适压缩。如果压缩率不好,它仍然会尝试压缩数,然后放弃。这将导致额外的内存资源浪费,并极大影响插入数据的速度。
未来
LZ4对TOAST的压缩和解压缩性能带来了很大提升。除了LZ4,还有很多其他压缩算法比如Zstandard。支持Zstandard用户可以得到比PGLZ更好的压缩率。LZ4 HC具有比LZ4解压98.5%的压缩速度,但是可以大幅提升压缩率。希望未来PG版本可以使用更多的压缩算法。
除了TOAST外,其他场景也需要压缩。据我所知,目前开发版本已经支持WAL的LZ4压缩,这是一项令人兴奋的特性。