Postgres 中什么会被 TOAST 处理

Postgres 中什么会被 TOAST 处理

💡 原文英文,约2500词,阅读约需10分钟。
📝

内容提要

Postgres的TOAST机制在行超过2032字节时压缩或外移大值。Postgres 19将默认压缩算法从pglz改为LZ4。LZ4窗口64KB、速度快,适合多数场景;pglz窗口4KB、搜索更充分,在JSON等重复数据上压缩率更高,读取前缀也更快。已压缩数据或需随机读取的bytea值宜用EXTERNAL跳过压缩。

🔎

延伸解读

TOAST 触发条件与行大小计算

TOAST 仅在整行超过 2032 字节时启动,而非单个值。行由 24 字节头部和列值组成,固定宽度类型占用固定字节,可变长度类型额外带 1 或 4 字节长度头。例如 2000 个 'x' 的 text 值加上 int 列恰好 2032 字节,不触发;2001 个字符则超限,被压缩到 26 字节。理解这一点有助于预估哪些数据会受 TOAST 影响。

TOAST 的两种处理方式与存储策略

行超限后,TOAST 按值从大到小处理:先尝试压缩并保留在行内,若仍超限则移出到 TOAST 表,拆成最多 1996 字节的块,原行保留 18 字节指针。列的存储策略决定行为:EXTENDED 先压缩再移出,EXTERNAL 只移出,MAIN 尽量保留在行内。默认 text、jsonb、bytea 用 EXTENDED,numeric 用 MAIN。

LZ4 与 pglz 的压缩特性对比

LZ4 窗口 64KB、速度快,但只检查单个候选匹配;pglz 窗口 4KB、搜索更充分,能发现更长匹配。在重复间隔较近的数据上,pglz 压缩率可能更高,例如 JSON 文档中 pglz 表 19MB 而 LZ4 表 24MB。但 LZ4 在多数场景下速度优势明显,且对窗口外的重复更敏感。Postgres 19 将默认算法从 pglz 改为 LZ4。

何时选择 EXTERNAL 跳过压缩

已压缩数据(如 JPEG)或需要随机读取中间部分的 bytea 值,适合用 EXTERNAL 跳过压缩。压缩后,每个块仍为 1996 字节,但解压后对应多少原始字节未知,因此随机读取需解压所有块。EXTERNAL 下每块恰好 1996 字节,可直接定位目标块。实测读取 50 万字节处的 200 字节,EXTERNAL 仅需 0.6ms,而 LZ4 需 43ms,pglz 需 116ms。

❓

Q&A

Postgres 中 TOAST 机制在什么情况下会被触发?

当一行数据的总大小超过 2032 字节时,TOAST 机制会被触发。这个阈值大约是 8KB 页面的四分之一,目的是确保每个页面至少能容纳四行数据。

TOAST 处理大值时具体会做哪些操作?

TOAST 有两种处理方式:一是压缩值,将压缩后的较小版本保留在行内;二是将值移出到单独的 TOAST 表中。TOAST 会从最大的值开始处理,直到行大小降到 2032 字节以下。

Postgres 19 中 TOAST 的默认压缩算法有什么变化?

Postgres 19 将 TOAST 的默认压缩算法从 pglz 改为 LZ4(在支持 LZ4 的构建中)。LZ4 窗口为 64KB,速度更快,适合大多数场景;而 pglz 窗口为 4KB,搜索更充分,在 JSON 等重复数据上压缩率更高。

pglz 和 LZ4 压缩算法在性能上有什么差异?

LZ4 速度更快,因为其窗口更大(64KB)且使用查找表快速匹配;pglz 窗口较小(4KB),但会搜索多个候选以找到最长匹配,压缩率可能更高。在读取前缀时,pglz 由于有压缩上限,只需读取第一个块,而 LZ4 需要读取整个值。

在什么情况下应该使用 EXTERNAL 存储策略跳过压缩?

对于已经压缩的数据(如 JPEG 图像)或需要随机读取的大型 bytea 值,建议使用 EXTERNAL 策略。这样 TOAST 只将值移出到 TOAST 表而不压缩,避免不必要的 CPU 开销,并允许直接定位到特定块进行读取。

TOAST 和大型对象(large objects)有什么区别?

TOAST 是自动的,适用于任何变长列,值最大 1GB,随行删除;大型对象需要手动创建,可存储高达 4TB 的数据,且独立于行存在,需用 lo_unlink 删除。对于超过 1GB 的文件,必须使用大型对象。

🏷️

标签

➡️

继续阅读