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

加速 PostgreSQL 索引创建技术探索

原创 PolarDB-PG 2024-09-23
189

关于 PolarDB PostgreSQL 版

PolarDB PostgreSQL 版是一款阿里云自主研发的云原生关系型数据库产品,100% 兼容 PostgreSQL,高度兼容Oracle语法;采用基于 Shared-Storage 的存储计算分离架构,具有极致弹性、毫秒级延迟、HTAP 、Ganos全空间数据处理能力和高可靠、高可用、弹性扩展等企业级数据库特性。同时,PolarDB PostgreSQL 版具有大规模并行计算能力,可以应对 OLTP 与 OLAP 混合负载。

简介

我们知道,在数据库操作中,特别是在含有多个索引的表中执行插入操作时,由于每个索引插入都需要逐个访问索引页,这导致了频繁的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耗时。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论