暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

加速postgres索引创建技术探索

PolarDB 2025-04-28
273

加速postgres索引创建技术探索

我们知道,在数据库操作中,特别是在含有多个索引的表中执行插入操作时,由于每个索引插入都需要逐个访问索引页,这导致了频繁的I/O操作,成为性能瓶颈。标准流程包括表行插入后,顺序地为每个索引执行查找、读取、插入操作,这一系列步骤在高索引数量下尤为耗时。本文描述了在向包含多个索引的表中插入数据时可能遇到的I/O瓶颈问题,并探索一种通过预读取索引页来优化插入操作的方法。通过在实际插入操作之前预先读取可能需要的叶子页,以减少插入过程中的I/O延迟。通过实践分析,该方法在某些场景下能够提高约四成的buffer命中率,并在一些情况下能够显著提高插入效果。 

原理介绍

在处理多行插入、普通插入和更新操作中添加预取逻辑(ExecInsert, CopyFrom, ExecSimpleRelationInsert, ExecSimpleRelationUpdate)方法中,在插入索引前进行所有索引提前读取操作。

预取索引页的目的是在实际执行插入操作之前,对要插入的数据所在的索引页进行预读取(仅预取leaf page),主要流程如下:

1. 启动预取:ExecInsertPrefetchIndexes
      在insert/update/copy场景,启动索引预取。
2. 生成索引元组&执行预取:
    判断预取条件&预取数据组装
3. 执行预取:
    根据 B 树结构递归查找目标页,通过PrefetchBuffer实际执行预取操作,提前将这些页加载到缓冲区中。
    -对pg来讲是操作系统缓存。

测试中发现的实现缺陷:

  • 冗余实现:在预取中需要重复构建索引元组等。

  • 暂时无法在复合索引和有其他类型的索引情况下工作

  • 当前只能一次性预取所有索引和元组

  • 在仅包含一个索引的情况下也进行了预取

  • 用并行的方式来解决更具有普适性

aminsert_futures = NIL;
/* create a future for each index insert */
for (<all indexes>)
{
     aminsert_futures = lappend(aminsert_futures, aminsert(...));
}
/* wait for all the futures to finish */
await aminsert_futures;

注意,这里期望使用主干中异步方法ForeignAsyncRequest() and ForeignAsyncConfigureWait(),但是这两个方法是从上次配置异步文件的读取,对作为异步基础设施还有一段距离。

测试效果:

1G shared_buffer,5个索引。

批量数据
未启用
启用加速特性
100
1.025
0.942
1000
52.526 ms
50.245
10000
3660.453
1429.513
100000
7816.608
5008.967

分析:

-buffer命中提高约40%,部分场景insert效果显著

-在特殊场景下的刷脏更高效,预取在高压下会有刷脏放大的情况,所以效果预测会更佳。

总结分析:

从原理上讲,在insert索引操作之前通过对索引page预取(原生pg都是异步并行),理论上是在索引数量大于1的情况下都可以缩减io耗时。


文章转载自PolarDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论