相关:
《Postgresql源码(18)PGPROC相关结构》
《Postgresql源码(65)新快照体系Globalvis工作原理分析》
《Postgresql快照优化Globalvis新体系分析(性能大幅增强)》
《Improving Postgres Connection Scalability: Snapshots》

一些历史悠久的分析:
《Postgresql源码(28)获取快照GetSnapshotData流程分析和性能问题》

PG的新快照体系Globalvis分析

  • 本篇介绍快照优化作者Andres Freund的优化总结文章。
  • 本篇中有很多个人理解,不一定准确,推荐阅读:《Improving Postgres Connection Scalability: Snapshots》。
  • 本篇文章作为新版本PG快照分析的第一篇,后面几篇新代码再做展开分析。

先看下这个优化的性能提升效果,在高连接数下会有明显提升:

一、概念回顾

1 MVCC

  • 传统锁机制无法做到读、写的并发操作,因为数据只有一个版本。引入mvcc后,数据存在多版本,可以做到读一个版本、写另一个版本,实现读写并发。
  • 如果实现这样机制,需要每个行版本记录xmin(可见开始位点visible since),xmax(可见结束位点visible until)。
  • 位点即时间戳,PG中使用自增正整数表示。

2 快照

  • 只有时间戳还是不够的,决定我当前能否看到一个元组,我还必须知道创建、删除元组的时间戳所代表的事务是否已经提交了。为了避免脏读,只有提交的事务才会被看做已生效。
  • PG使用快照来记录哪些事务正在运行中。
  • PG的SnapshotData结构中,xip记录了所有运行中的事务ID。
typedef struct SnapshotData
{TransactionId xmin;            /* all XID < xmin are visible to me */TransactionId xmax;            /* all XID >= xmax are invisible to me */TransactionId *xip;uint32      xcnt;           /* # of xact ids in xip[] */
...

3 快照获取

优化之前PG是如何获取快照的?

  • 每个PG进程都对应一个PGPROC结构、一个PGXACT结构(PG14把PGXACT合到PGPROC里面了)
  • PGPROC数组在初始化的时候就全部申请好了,再找PROC的时候为了避免每次都遍历整个数组,在procArray->pgprocnos柔性数组中记录了排序过后的PGPROC数组的索引,参考这篇:《Postgresql源码(18)PGPROC相关结构》。
  • 在构建快照时,GetSnapshotData会遍历procArray->pgprocnos找到所有存在的PGPROC、PGXACT结构,收集所有的xid。
  • 在构建快照时,还有一些细节:
    • 为了方便,GetSnapshotData会计算全局最久PGXACT->xmin,用于在访问的过程中做一些vacuum的事情,回收元组。
    • 为了实现savepoint,一个backend需要记录多个事务ID。
    • 为了效率,vacuum忽略一些执行中的backend。
    • 备机上的快照计算完全不同(以前做过分析,文章写得很简陋,后面再补一篇)

4 历史优化

2011年已经发现GetSnapshotData存在瓶颈,当时做的优化是把PGPROC里面把快照需要的变量拆出来,放到PGXACT中,这样数据结构小很多,可以装到一个cpu cache line中。

二、识别当前的瓶颈点

从上述分析中可以看出,遍历所有连接的复杂度为O(#connection),快照计算成本随连接数线性增加。

理论上有两种提高扩展性的方法:

  1. 寻找复杂度更低的算法,避免O(n)
  2. 对每个连接做更少的操作,减少单次操作的时间。

旧版本的快照获取步骤:

xmin = global_xmin = inferred_maximum_possible;
for (i = 0; i < #connections; i++)
{int procno = shared_memory->connection_offsets[i];PGXACT *pgxact = shared_memory->all_connections[procno];// compute global xmin minimum// 记录全局做小xminif (pgxact->xmin && pgxact->xmin < global_xmin)global_xmin = pgxact->xmin;// nothing to do if backend has transaction id assigned// 未启动事务直接跳过if (!pgxact->xid)continue;// the global xmin minimum also needs to include assigned transaction ids// 全局最小xmin也要包含最小的事务IDif (pxact->xid < global_xmin)global_xmin = pgxact->xid;// add the xid to the snapshot// 活跃事务记录到快照的xip中snapshot->xip[snapshot->xcnt++] = pgxact->xid;// compute minimum xid in snapshot// 记录最小的xid到快照中if (pgxact->xid < xmin)xmin = pgxact->xid;}snapshot->xmin = xmin;
// store snapshot xmin unless we already have built other snapshots
if (!MyPgXact->xmin)MyPgXact->xmin = xmin;
RecentGlobalXminHorizon = global_xmin;

这里最重要的一点:

  • 循环体起始只需要计算活跃事务就好了,但是,这里还顺便计算了全局xmin。这不是快照的一部分,但可以方便的同时计算,只需要很小的代价(or so we thought…)。

  • 代码作者花了很长时间,试图去理解为什么遍历几千个元素,要花费如此昂贵的代价。


针对瓶颈点,作者做了三层优化。

1 瓶颈点&优化:ping pong

问题主要来自于这行代码(这里的MyPgXact->xmin的含义是:最小的运行中的xid,不包括lazy vacuum)

xmin = global_xmin = inferred_maximum_possible;
for (i = 0; i < #connections; i++)
{......xid = pgxact->xmin;                            //  <---------------- 这里读if (TransactionIdIsNormal(xid) && NormalTransactionIdPrecedes(xid, globalxmin))globalxmin = xid;
}if (!TransactionIdIsValid(MyPgXact->xmin))MyPgXact->xmin = TransactionXmin = xmin;     //  <---------------- 这里改...

因为这个值要记录运行中的最小xid,所以连接的事务提交、终止都需要更新这个值,在正常运行的系统中,由于xid是不断推进的,这个值的更新频率非常高;另一方面这个值又在循环中不断读取,所以造成缓存不断失效的问题。

在当前最常见的CPU微架构中,每个核心私有L1和L2缓存,每个CPU插槽上所有核共享L3缓存。

  • 第一点:假设有8核的CPU,8个连接在并发的构建8个快照,每个连接都需要读取他的七个PGXACT,然后修改自己的PGXACT;这时读取的其他七个PGXACT也在被不停的修改中,这样读取的PGXACT就会经常失效了。可能刚刚缓存好,别人把xmin修改了导致缓存的PGXACT失效,需要从内存重新拿上来。

  • 第二点:xmin其实是不需要访问的,需要的是计算出一个全局最小xid。但是不访问xmin也没什么用,因为xmin和xid在一个cache line上,xmin虽然不访问,但是会修改。 改了就会让整个cache line失效,导致xid的访问也很慢。

具体修改在这里,删除了读取的逻辑;保留了修改的逻辑。因为修改是必须的,读取其实是不必要的。

关于第二点:

代码作者优化掉了RecentGlobalXminHorizon(用于清理死元组),引入了两个没那么准确的新值:

  • A:确定比A小的都已经可以清理了
  • B:确定比B大的都无法清理

在A、B中间的元组会引入昂贵的准确计算,避免在GetSnapshotData中计算引发上述问题。

20220815补充:注意在循环体内xmin被不断的读才是最大的问题,循环外只是最后更新一次看到的最小值;这样在循环体内不读xmin了,xmin就算被别人更新了,当前的cacheline也不会失效。所以只要避免了高频的读,就避免了大量cacheline失效的问题。

2 瓶颈点&优化:数据离散访问

PGXACT的存放位置是离散的,可能在allPgXact大数组中的任意几个位置,不连续。(参考《Postgresql源码(18)PGPROC相关结构》)在使用时,通过连续的pgprocnos数组中记录的index找到活跃的PGXACT。

GetSnapshotData
...//优化前,通过pgprocnos拿到索引,通过索引在离散的allPgXact数组中拿到xidnumProcs = arrayP->numProcs;for (index = 0; index < numProcs; index++){int            pgprocno = pgprocnos[index];volatile PGXACT *pgxact = &allPgXact[pgprocno];
...//优化后,在密集数组other_xids中拿xidfor (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++){TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);

问题就是数据是离散的,将xids单独拿出来放到连续存储的密集数组中,可以显著提高命中率。

3 瓶颈点&优化:快照缓存

  • 经过上面两个优化(都在优化O(n)中的n),在连接数很高时计算一个快照仍然是一个昂贵的操作。

  • 经过上面优化后,快照中不在维护RecentGlobalXmin,这样就可以做一个更大的改进:如果我们可以确定快照没变,就不用重新计算了。以前这是不可行的,因为RecentGlobalXmin比快照本身的更新频率要高很多。

  • 快照唯一需要更新的时机是:之前运行的一个事务提交了。(快照最根本的功能是提供运行事务id列表,列表中最大的那个就是xmax了,事务提交后就相当于不在运行事务列表中了,在做可见性判断时如果不在运行事务列表中,就知道事务已经提交或回滚了,这就需要继续走标志位 或 clog判断了;但是如果事务ID在运行事务列表中,那就一定没提交,不需要做任何进一步判断)

  • 因此,作者设计了一个xactCompletion内存计数器,记录提交或终止的事务数量。

    • 当要求重新计算快照时,我们检查计数器,如果没变可以直接复用上一个快照。
    • 如果计数器变了,那么需要重新计算快照。

Postgresql快照优化Globalvis新体系分析(性能大幅增强)相关推荐

  1. 【Android 性能优化】应用启动优化 ( 安卓应用启动分析 | Launcher 应用启用普通安卓应用 | 应用进程分析 )

    文章目录 一. Launcher 应用 startActivitySafely 方法分析 二. Launcher 中的 startActivity(View v, Intent intent, Obj ...

  2. Android 系统性能优化(30)---Android性能全面分析与优化方案研究

    Android 性能优化 1.结合以下四个部分讲解: 性能问题分类 性能优化原则和方法 借助性能优化工具分析解决问题 性能优化指标 2性能问题分类 1.渲染问题:过度绘制.布局冗杂 2.内存问题:内存 ...

  3. 抖音 Android 性能优化系列:新一代全能型性能分析工具 Rhea

    本文选自「抖音 Android 性能优化」系列文章. 「抖音 Android 性能优化」系列文章是由抖音 Android 基础技术部门技术专家倾力打造的技术干货内容,和大家分享基础技术团队在打造极致用 ...

  4. JVM性能优化之GC日志分析

    JVM性能优化之GC日志分析 文章目录 JVM性能优化之GC日志分析 前言 一.GC日志参数 GC日志参数 常用的垃圾收集器配置 大对象回收 二.GC日志分析工具 GCeasy JVM memory ...

  5. 字节跳动Android三面视频解析:framework+MVP架构+HashMap原理+性能优化+Flutter+源码分析等

    前言 对于字节跳动的二面三面而言,Framework+MVP架构+HashMap原理+性能优化+Flutter+源码分析等问题都成高频问点!然而很多的朋友在面试时却答不上或者答不全!今天在这分享下这些 ...

  6. VS2015--win32project配置的一些想法之在 Visual Studio 2015 中进行调试的同一时候分析性能...

    出处: https://msdn.microsoft.com/zh-cn/magazine/dn973013(en-us).aspx 很多开发商花了绝大多数时间获取应用程序才干正常发挥作用.更少的时间 ...

  7. 性能优化实战|使用eBPF代替iptables优化服务网格数据面性能

    目前以 Istio[1] 为代表的服务网格普遍使用 Sidecar 架构,并使用 iptables 将流量劫持到 Sidecar 代理,优点是对应用程序无侵入,但是 Sidecar 代理会增加请求时延 ...

  8. mysql属性配置提高查询_MYSQL性能优化-安装时优化参数配置提高服务性能

    MYSQL性能优化一直是个头痛的问题,目前大多都是直接把页面html静态页面或直接使用了缓存技术,下面我就mysql本身的性能优化来分享一下. 安装时优化参数配置提高服务性能 在Linux下安装Mys ...

  9. java 性能 优化_Java十大简单性能优化

    java 性能 优化 关于" web scale "这个流行词有很多炒作,人们花了很多时间来重新组织他们的应用程序体系结构,以使其系统"规模化". 但是什么是扩 ...

最新文章

  1. 2014025679 《嵌入式系统程序设计》第五周学习总结
  2. C++ gets, getline ( string流)
  3. 河北地质大学硕士专业介绍:计算机类
  4. 【leetcode】443. String Compression
  5. C# 中的回车换行符 表示
  6. XML--XML从入门到精通 Part 1 认识XML
  7. Java EE与Java SE:Oracle是否放弃了企业软件?
  8. CompletableFuture详解~设置任务结果
  9. 【jvm】jvm jstack使用 Java线程Dump分析
  10. MediaPlayer的错误修复
  11. 笔记本电脑如何连接手机热点_如解决Mac连接手机热点出错问题 ?
  12. paddle2.0实现DNN(minst数据集)
  13. 基于正点原子STM32的OLED显示实验
  14. eversync safari_印象笔记 Evernote 同步插件 for WordPress
  15. mac 协议的 类型
  16. 《GPU编程与CG语言之阳春白雪下里巴人》阅读笔记 第五章+第六章
  17. php ansiix99mac,华擎推出X99E-ITX/ac主板,终于可以把Haswell-E装进小钢炮
  18. Notepad快捷键Shift+Enter失效,无法启用向前搜索。
  19. 什么是FOUC?如何避免FOUC?
  20. PCB板布线经验~~

热门文章

  1. c4d fbx大小_C4D设计模型大全,多达23.7GB
  2. MCP2515+SJA1000通讯调试记录
  3. 枚举进程:ring3-ring0
  4. 不支持发行版本5 解决方法
  5. 选型帮推荐:上海音锋机器人【托盘四向车】
  6. IT行业人才招聘观察
  7. 电池充电语音警报V9.0.33——让手机充电更安全
  8. 1725. 组队井字游戏
  9. python数据评估
  10. 有感于新闻联播对威客(witkey)的报道