作为应用最广泛的关系型数据库,PostgreSQL应用B+ tree索引去管理其数据存储(默认B+ tree page大小为8KB),通过存储表空间中的所有row version实现MVCC(多版本并发控制)。因此,PostgreSQL总是没有直接在一个B+ tree page中更新一行,而是首先将新的行版本存储在一个新的位置,并依赖于后台真空过程来回dead row version所占用的表空间。因此,更新操作的TPS(每秒事务)性能很大程度上取决于PostgreSQL是否可以将新row version储存在与旧row version相同的页面中:
- 如果承载旧行版本的页面已满,则PostgreSQL必须将新的行版本存储在另一个页面中。因此,PostgreSQL必须相应地通过操作(拆分或创建)一个或多个附加页面来修改b+ tree结构。这将导致额外的CPU使用和TPS性能下降。
- 如果承载旧row version的页面有足够的空白空间,那么PostgreSQL只需在该页面中添加新的row version。通过保持b+tree结构的完整,可以大幅降低CPU的使用,从而提高TPS性能。
以上事实揭示了TPS性能和使用存储空间的一个基本平衡:当将数据加载到PostgreSQL时,如果我们不完全填充B+tree page,并保留一定数量的空余空间来吸收之后的更新,这样可以提高TPS的性能。然而,这同时增加了更多的B+ tree page,从而消耗了更多的存储空间。如图1所示,PostgreSQL允许用户通过使用一个名为填充因子的参数配置来实现这样的平衡。 通过在10到100之间调整填充因子的百分比,来控制B+ tree page填充量。它的默认值是100,即每个页面都100%填充了inserted row,因此不会为之后的更新预留任何空间。
当PostgreSQL在正常的ssd/hdd上运行时,无论一个页面有多满,每个页面总是消耗8KB的物理存储空间。这需要通过填充器配置来实现TPS性能与存储成本之间的平衡。正如我们在之前博客中所讨论的,存储内的透明压缩可以在使用4KB block I/O接口的情况下实现几乎可变大小的block I/O。因此,当PostgreSQL在具有内置透明压缩的存储驱动器上运行时,一个页面的用户数据填充量直接决定了页面所消耗的物理存储空间。这可以在很大程度上实现了PostgreSQL在TPS性能与存储成本之间的平衡,也就是说,当我们降低填充因子的值,为每个页面之后的更新留下更多的空间,而且物理存储使用不会成比例增加。因此,通过启用几乎可变大小的block I/O,存储内透明压缩允许PostgreSQL充分降低填充系数,以在不牺牲物理存储成本的前提下提高TPS性能。
为了进一步验证这一设想,我们使用Percona序列台-TPCCOLTP基准测试在PostgreSQL上进行了实验。我们使用了一个具有32核3.3GHzXeonCPU的服务器,然后考虑了两种不同的数据集大小:740GB和1.4TB,并在一个正常的NVMe SSD和我们的CSD2000上进行了实验(64个客户端线程)。图2显示了将填充因子分别设置为100和75时,TPS性能和物理存储空间使用情况。在相同的填充因子下,PostgreSQL在正常的NVMe SSD和我们的CSD2000驱动器上实现了相同的TPS。当将填充因子从100减少到75时,TPS性能均提高了约33%。在740GB数据集,填充因子配置为100下,存储内透明压缩可以将物理存储使用从740GB减少到178GB。当将填充因子从100减少到75时,正常NVMe SSD上的物理存储使用率按比例从740GB增加到905GB,而CSD2000上的物理存储使用率仅从178GB略微增加到189GB。在1.4TB数据集的结果上也可以观察到类似的现象。实验结果清楚地表明,通过简单地配置填充因子参数,PostgreSQL可以从存储内的透明压缩中显著获益,而无需改变任何源代码。