六月 13, 2024
摘要:本教程介绍了 PostgreSQL 版本 14 中引入的 LZ4 压缩 TOAST 的功能,并演示了其用法。
目录
背景
TOAST 是 PostgreSQL 用来防止物理数据行大小超过数据块大小(通常为 8KB)的一种机制。PostgreSQL 不支持跨块边界的物理行,因此块大小是行大小的硬性上限。为了允许用户表具有比这更宽的行,TOAST 机制将宽字段值分解为更小的部分,这些部分“线外”存储在与用户表关联的 TOAST 表中。
您创建的每个表都有其关联的(唯一的)TOAST 表,该表最终可能会被使用,也可能不会被使用,具体取决于您插入的行的大小。所有这些都对用户是透明的,并且默认情况下处于启用状态。
当要存储的行“太宽”(默认情况下,该行的阈值为 2KB)时,TOAST 机制首先尝试压缩任何宽字段值。如果这还不足以使行小于 2KB,它会将宽字段值分解和存储到关联的 TOAST 表中的块。每个原始的字段值都被替换为一个小的指针,该指针指向 TOAST 表中查找此“线外”数据的位置。TOAST 将尝试用这种方式将用户表行压缩到 2KB,但只要它能低于 8KB,其实就足够了,这样该行就可以完成存储了。
TOAST 压缩方法
在版本 14 以前,PostgreSQL 中只有一种压缩数据的算法,那就是 pglz。这是一种非常古老的内置算法。现在有一个每列压缩选项,可以设置为 pglz(默认值)或 lz4。并且,引入了新的 postgresql.conf 配置参数 default_toast_compression,用于设置默认值为 pglz 的默认压缩方法。当表列未指定压缩方法时,该配置参数的值会应用于新表中的列。在 PostgreSQL 内核中并没有 lz4 的支持,因此要使用 lz4 压缩,必须使用 –with-lz4 选项来构建 PostgreSQL。
示例
让我们创建两个表:一个使用默认压缩,另一个使用新的 LZ4 压缩:
CREATE TABLE t1 ( a text );
CREATE TABLE t2 ( a text compression lz4 );
两张表都自动附加了一个 toast 表:
SELECT oid, relname FROM pg_class WHERE oid IN
(SELECT reltoastrelid FROM pg_class WHERE relname IN ('t1', 't2'));
oid | relname
-------+----------------
16387 | pg_toast_16384
16392 | pg_toast_16389
(2 rows)
\d+ pg_toast.pg_toast_16384
TOAST table "pg_toast.pg_toast_16384"
Column | Type | Storage
------------+---------+---------
chunk_id | oid | plain
chunk_seq | integer | plain
chunk_data | bytea | plain
Owning table: "public.t1"
Indexes:
"pg_toast_16384_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
Access method: heap
让我们来检查一下,如果我们用一些虚拟数据填充这些表,是否会有区别:
\timing on
INSERT INTO t1(a) SELECT lpad('a',1000000,'a') FROM generate_series(1,1000);
Time: 10943.866 ms (00:10.944)
INSERT INTO t2(a) SELECT lpad('a',1000000,'a') FROM generate_series(1,1000);
Time: 280.503 ms
这是一个巨大的差异,即使重复进行多次测试,也会得到几乎相同的数字。这在速度方面确实是一个很大的改进。但是磁盘上的大小呢?
SELECT pg_size_pretty(pg_relation_size('pg_toast.pg_toast_16384')) AS toast1_size;
toast1_size
-------------
12 MB
(1 row)
SELECT pg_size_pretty(pg_relation_size('pg_toast.pg_toast_16389')) AS toast2_size;
toast2_size
-------------
4000 kB
(1 row)
相当令人印象深刻。除了速度之外,我们还大大降低了磁盘占用空间。由于 LZ4 的压缩效果更好,因此我们在 t2 的 toast 表中看到的行数更少:
SELECT count(*) FROM pg_toast.pg_toast_16384 AS toast1_rows;
count
-------
6000
(1 row)
SELECT count(*) FROM pg_toast.pg_toast_16389 AS toast2_rows;
count
-------
2000
(1 row)
当然,我在这里使用的字符串不是很有代表性,但这个新功能看起来确实很有用处。如果要全局更改此行为,还有一个新参数;
SHOW default_toast_compression;
default_toast_compression
---------------------------
pglz
(1 row)
ALTER SYSTEM SET default_toast_compression = 'lz4';
SELECT pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)