内容提要
2026年9月,TypeSafe开放Jev模型早期访问,输入每百万token 0.042美元,输出免费。文章实测PostgreSQL批量回填:默认fillfactor下更新百万行会使表翻倍、索引膨胀。按id%10交错批次配合VACUUM可达96.1% HOT,索引几乎不涨,WAL减半;范围批次仅11.5%。检查点会大幅增加交错写WAL。部分索引、jsonb概率列会破坏HOT,建议标签存独立表。
延伸解读
交错回填的代价:检查点下的WAL放大
交错批次(如id % 10)能大幅提升HOT比例并减少WAL,但这一优势在频繁检查点下可能逆转。文章实测显示,每批后执行CHECKPOINT时,交错运行的WAL从455 MB激增至3022 MB,而范围批次仅从850 MB升至1025 MB。原因是交错批次会修改每个堆页面,检查点后每个页面首次修改都需写入完整页镜像。因此,在长时间回填中,若检查点间隔较短,交错策略的WAL开销可能反而更高。
部分索引与jsonb概率列:HOT的隐形杀手
看似无害的添加可能完全破坏HOT。文章指出,一个在谓词中引用confidence的部分索引(如WHERE confidence < 0.6)会使交错回填的HOT比例降为0,因为Postgres将谓词中的列视为索引列,而回填会更新该列。同样,将模型概率以jsonb对象存储在行内,因重复键名导致堆膨胀,HOT比例从96.1%降至50.0%。建议将概率存入独立表,或使用real[]数组,以保持主表HOT。
现有表启用fillfactor:ALTER与重写的选择
对于已存在的表,ALTER TABLE ... SET (fillfactor = 90) 不会重写现有页面,但交错回填仍能达到86.2%的HOT,因为VACUUM后会在旧页面打开空隙,且新元组插入其他页面时需满足fillfactor保留空间,从而保护空隙。若需完全达到96.1%的HOT,则需通过VACUUM FULL或PostgreSQL 19的REPACK (CONCURRENTLY)重写表,但前者持有ACCESS EXCLUSIVE锁,后者需注意非MVCC安全的警告。
Q&A
为什么PostgreSQL批量回填后表的大小会翻倍?
PostgreSQL的UPDATE会为每一行写入新版本,旧版本保留到VACUUM清理。如果更新所有行且中间不执行VACUUM,就需要容纳表的两份副本。在默认fillfactor=100的页面上,新版本还会移动到其他页面,并为每个索引添加条目,导致堆和索引都膨胀。
交错批次(id % 10)和范围批次在HOT更新比例上有什么区别?
在fillfactor 90且每批后VACUUM的情况下,范围批次只有11.5%的更新是HOT,而交错批次(id % 10)达到96.1%。交错批次每次只请求每页十分之一的行,新版本大多能放入预留空间,且VACUUM及时释放旧版本空间,使后续批次能继续HOT。
如何检查回填过程中的HOT更新比例?
查询pg_stat_user_tables,计算n_tup_hot_upd / n_tup_upd。例如:SELECT n_tup_upd, n_tup_hot_upd, round(100.0 * n_tup_hot_upd / nullif(n_tup_upd, 0), 1) AS hot_pct FROM pg_stat_user_tables WHERE relname = 'tickets'; 在共享服务器上,建议使用pg_stat_reset_single_table_counters('tickets'::regclass)重置单表计数器,或记录批次前的值再相减。
检查点(CHECKPOINT)对交错回填的WAL写入有什么影响?
在每批后执行CHECKPOINT的情况下,交错回填的WAL写入量会大幅增加。例如,范围批次从850 MB增至1025 MB,而交错批次从455 MB激增至3022 MB。这是因为开启full_page_writes后,检查点后第一次修改页面会写入整页镜像,交错批次会修改所有堆页面,导致每个检查点后都需要为整个堆写入新镜像。
哪些操作会破坏HOT更新?
部分索引(如CREATE INDEX ... WHERE confidence < 0.6)会破坏HOT,因为Postgres将索引谓词中提到的列视为索引列,而回填会更新该列。此外,将模型概率存储为jsonb列也会降低HOT比例,因为jsonb重复键名使行更大,可能超出页面预留空间。
对于已有表,如何在不重写的情况下应用fillfactor并提高HOT?
可以使用ALTER TABLE ... SET (fillfactor = 90)而不重写表,这仍能提高HOT比例(例如从20.6%提升到86.2%),因为VACUUM会在旧页面上打开间隙,而新元组插入其他页面时需要满足fillfactor预留空间,从而保留间隙供HOT更新。若要完全达到96.1%的HOT,需要重写表,如VACUUM FULL或PostgreSQL 19的REPACK (CONCURRENTLY)。
将标签存储在独立表中有什么优缺点?
优点:主表保持不变,堆和索引不膨胀,WAL写入最少(例如138 MB)。缺点:每次读取标签需要连接,删除tickets行时也需要处理labels表。