加速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个索引。
分析:
-buffer命中提高约40%,部分场景insert效果显著
-在特殊场景下的刷脏更高效,预取在高压下会有刷脏放大的情况,所以效果预测会更佳。
总结分析:
从原理上讲,在insert索引操作之前通过对索引page预取(原生pg都是异步并行),理论上是在索引数量大于1的情况下都可以缩减io耗时。
文章转载自PolarDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




