【MogDB】MogDB5.2.0重磅发布第八篇-支持PLSQL编译全局缓存

news/2024/11/22 7:47:00/

前言

在我之前的文章中有提过,原生PG对于重度存储过程的应用系统适配,具有一个致命缺陷,即原生PG中的plsql是会话级缓存,这意味着每个会话在第一次执行某个存储过程时,都需要对这个存储过程进行编译,并且将编译结果缓存到本会话,由此引发巨大的内存占用(应用连接池多连接)以及首次调用时的严重性能问题(应用短连接)。

测试

下面在openGauss 6.0.0版本中测试,使用匿名块构造了一个约两万行的存储过程,并且创建进了数据库,包含有循环、判断、DML、赋值、简单计算表达式,但实际这个存储过程begin后的第一行就是return,避免执行不同语句的时间干扰对性能的判断。

DECLAREv_sql CLOB := '';v_table_sql CLOB := '';v_line_count int:=20000;
BEGIN-- 生成建表语句v_table_sql := 'CREATE TABLE example_table (id NUMBER PRIMARY KEY, name VARCHAR2(100), value NUMBER);';execute immediate  v_table_sql;-- 初始化procedure创建语句v_sql := 'CREATE OR REPLACE PROCEDURE example_pro ISv_counter NUMBER := 0;v_sum NUMBER := 0;BEGIN
return;';-- 代码翻倍FOR i IN 1..trunc(v_line_count/10) LOOPv_sql := v_sql || '  v_counter := v_counter + 1;v_sum := v_sum + ' || i || ';IF MOD(' || i || ', 2) = 0 THENINSERT INTO example_table (id, name, value) VALUES (' || i || ', ''Even'', v_sum);ELSEINSERT INTO example_table (id, name, value) VALUES (' || i || ', ''Odd'', v_sum);END IF;FOR j IN 1..5 LOOPv_sum := v_sum + j;END LOOP;';END LOOP;-- 结束procedurev_sql := v_sql || '  v_sum := v_sum / 50000;--DBMS_OUTPUT.PUT_LINE(''Average Sum: '' || v_sum);END ;
';-- 执行生成的procedure创建语句execute immediate v_sql;
END;
/

然后开启计时,比如PG/OG系数据库在自带命令行客户端中的方式为

\timing on

手动调用几次这个存储过程

call example_pro();

并且尝试更换会话去执行这个存储过程

[og600@kylinv10sp3-node1 ~]$ gsql -r
gsql ((openGauss 6.0.0 build aee4abd5) compiled at 2024-09-29 18:39:52 commit 0 last mr  )
Non-SSL connection (SSL connection is recommended when requiring high-security)
Type "help" for help.openGauss=# \timing on
Timing is on.
openGauss=# call example_pro();example_pro
-------------(1 row)Time: 1141.496 ms
openGauss=# call example_pro();example_pro
-------------(1 row)Time: 1.327 ms
openGauss=# \c postgres
Non-SSL connection (SSL connection is recommended when requiring high-security)
You are now connected to database "postgres" as user "og600".
openGauss=# call example_pro();example_pro
-------------(1 row)Time: 1150.205 ms
openGauss=# call example_pro();example_pro
-------------(1 row)Time: 1.399 ms
openGauss=#

可以看到每个会话首次执行与第二次执行的时长差异巨大。如果是个短连接的应用,执行一次就断开连接,执行十次需要十几秒,但是实际存储过程本身的执行时间可能才十几毫秒!

然后观察下首次执行这个存储过程前后,该会话的内存使用情况。

openGauss=# select  pg_size_pretty(sum(usedsize)) from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid();pg_size_pretty
----------------8900 kB
(1 row)Time: 14.220 ms
openGauss=# select  contextname,totalsize,usedsize from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid() order by usedsize desc limit 10;contextname           | totalsize | usedsize
---------------------------------+-----------+----------LocalSysCacheShareMemoryContext |   5368400 |  4104592PLpgSQL function cache          |   2076184 |  2073416LocalSysCacheTopMemoryContext   |   1097888 |   650800ThreadTopMemoryContext          |    417288 |   400392DefaultTopMemoryContext         |    344608 |   270624CBBTopMemoryContext             |    243984 |   223904LocalSysCacheMyDBMemoryContext  |    253952 |   220584StorageTopMemoryContext         |    125744 |    91224Timezones                       |     83504 |    80736Type information cache          |     73232 |    70464
(10 rows)Time: 21.794 ms
openGauss=# call example_pro();example_pro
-------------(1 row)Time: 1134.256 ms
openGauss=# call example_pro();example_pro
-------------(1 row)Time: 1.259 msopenGauss=# select  pg_size_pretty(sum(usedsize)) from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid();pg_size_pretty
----------------25 MB
(1 row)Time: 19.403 ms
openGauss=# select  contextname,totalsize,usedsize from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid() order by usedsize desc limit 10;contextname                | totalsize | usedsize
-------------------------------------------+-----------+----------PL/pgSQL function context_281474977359207 |  25173800 | 17169704LocalSysCacheShareMemoryContext           |   5368400 |  4104592PLpgSQL function cache                    |   2076184 |  2073416LocalSysCacheTopMemoryContext             |   1097888 |   650800ThreadTopMemoryContext                    |    466440 |   419720DefaultTopMemoryContext                   |    344608 |   270624CBBTopMemoryContext                       |    243984 |   223904LocalSysCacheMyDBMemoryContext            |    253952 |   223400OptimizerTopMemoryContext                 |    108200 |   102888StorageTopMemoryContext                   |    125744 |    91224
(10 rows)Time: 14.663 ms

可以发现该会话多了一个PL/pgSQL function context_*的内存占用,差不多就是执行存储过程后,增加的17MB内存。
做个粗略的计算,假设某应用系统有200个这样的存储过程(约400万行plsql代码),连接池有200个并发,那么光存储过程就能把内存吃掉 17*200*200/1024 MB=660 GB
而且该例子中的存储过程还比较简单,没有复杂表达式,变量也少,也没有使用自定义类型,否则内存占用会更多,有些国产高配的1TB内存服务器都玩不转了。

很多大行核心系统都是千万级的PLSQL代码,而且并发数肯定也不止200个,所以这个问题必然是PG/OG系数据库要解决的一个核心问题。

MogDB 5.2.0的突破

根据以上的测试,可以发现该问题的核心在于,存储过程的编译产物是在当前会话里,不能被其他会话共享,因此解决这个问题的关键,就是让存储过程的编译产物放进共享内存。于是MogDB在5.2.0版本新增了特性–GLOBAL PLSQL CACHE(全局PLSQL缓存)。

1.相关参数

enable_global_plsql_cache
参数描述:控制是否启用global PL/SQL cache功能。
参数级别:该参数属于POSTMASTER类型参数。修改需要重启生效
取值范围:布尔型。
默认值: on 。即默认打开global PL/SQL cache。

plsql_global_cache_max_memory
参数描述:Global PL/SQL cache的内存大小限制,用于存放编译的PL/SQL function/package。当插入的内存的function/package大小大于此参数值时,会对global_cache做内存清理
参数级别:该参数属于SIGHUP类型参数。
取值范围:5MB - 1TB
默认值: 1GB

plsql_global_cache_clean_timeinterval
参数描述:清理global_cache的时间间隔。
参数级别:该参数属于SIGHUP类型参数。
取值范围:5分钟 - 36500天
默认值:30分钟

plsql_global_cache_clean_percent
参数描述:清理一次global_cache时,至少要清理的global_cache空间比例。
参数级别: 该参数属于SIGHUP类型参数。
取值范围:1 - 100
默认值:20(即至少清理20%)

2.相关函数

缓存信息查询函数
select * from plsql_global_cache_info();

字段说明

  • database : 该PL/SQL cache function/Package所在的database Oid。
  • xmin: 该PL/SQL cache function/Package 记录的xmin。
  • trigrel_oid: 对于PL/SQL cache trigger function,记录触发表的Oid。
  • collation: 该PL/SQL cache function/Package的collationOid
  • Namespace: 该PL/SQL cache function/Package 所在的schema Oid。
  • Oid: 该PL/SQL cache function/Package的Oid。
  • Name : 该PL/SQL cache function/Package的名称。
  • type_enum: 该PL/SQL cache 的类型:function/procedure/pkg_spec/pkg_body。
  • search_path: 该PL/SQL cache function/Package编译时的search_path。
  • valid:该PL/SQL cache function/Package 是否有效。当前该记录被删除或repalce后,该标记会设置为false,并且待ref_count为0后,清除该cache。
  • ref_count: 该PL/SQL cache function/Package的引用计数。
  • used_count:该PL/SQL cache function/Package的使用次数。
  • total_space:该PL/SQL cache function/Package 占用的空间大小,单位为字节。
  • inval_items:该PL/SQL cache function/Package依赖的其他object。
  • is_all_func_compiled:该字段为区分Package spec和body。当该字段为false时,表明该记录为pkg spec,不包含body。当该字段为true时,表面该记录为pkg body并包含pkg spec。
移除缓存函数

plsql_cache_clean
plsql_cache_drop_object

3.MogDB实测

[mogdb26200@kylinv10sp3-node1 ~]$ gsql -r -U system -W oracle -d testdb
gsql ((MogDB 5.2.0 build e0c9adc2) compiled at 2024-09-28 02:48:39 commit 0 last mr 1804 )
Non-SSL connection (SSL connection is recommended when requiring high-security)
Type "help" for help.testdb=> \timing on
Timing is on.
testdb=> select  pg_size_pretty(sum(usedsize)) from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid();pg_size_pretty
----------------9394 kB
(1 row)Time: 20.083 ms
testdb=> select  contextname,totalsize,usedsize from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid() order by usedsize desc limit 10;contextname          | totalsize | usedsize
------------------------------+-----------+----------SessionCacheMemoryContext    |   3534048 |  2808640PLpgSQL function cache       |   2076184 |  2073416ThreadTopMemoryContext       |    587432 |   517960FunctionScan_22875809511168  |    532560 |   464656ExprContext                  |    516096 |   379232StorageTopMemoryContext      |    354384 |   328744global_session_memory_detail |    231224 |   227936session_memory_detail        |    231224 |   226112node_name                    |    223032 |   221184CBBTopMemoryContext          |    236736 |   217256
(10 rows)Time: 24.702 ms
testdb=> call example_pro();example_pro
-------------(1 row)Time: 1.370 ms
testdb=> call example_pro();example_pro
-------------(1 row)Time: 1.072 ms
testdb=> select  pg_size_pretty(sum(usedsize)) from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid();pg_size_pretty
----------------9471 kB
(1 row)Time: 24.194 ms
testdb=> select  contextname,totalsize,usedsize from dbe_perf.global_session_memory_detail where sessid ~pg_backend_pid() order by usedsize desc limit 10;contextname          | totalsize | usedsize
------------------------------+-----------+----------SessionCacheMemoryContext    |   3602144 |  2883952PLpgSQL function cache       |   2076184 |  2073416ThreadTopMemoryContext       |    587432 |   518504FunctionScan_22875809511168  |    532560 |   464656ExprContext                  |    516096 |   379232StorageTopMemoryContext      |    354384 |   329152global_session_memory_detail |    231224 |   227936session_memory_detail        |    231224 |   226112node_name                    |    223032 |   221184CBBTopMemoryContext          |    236736 |   217256
(10 rows)Time: 23.035 ms
testdb=>

从这个测试结果可以看到,MogDB 5.2.0的会话内存占用,在首次调用存储过程后,并无显著增长,而且首次执行速度相比第二次执行速度,差异并不大。
我们再进行一次粗略计算,和前面的假设一样,某应用系统有200个这样的存储过程(约400万行plsql代码),连接池有200个并发,由于PLSQL编译结果共享了,不在单个会话内,所以存储过程的内存占用约为 17*200*1024 MB=3.3 GB 远低于无 global plsql cache时660GB占用!而且并发数越高,plsql代码量越大、越复杂,启用global plsql cache节省的内存越多!

全局PLSQL缓存还能干什么?

MogDB又做了一个创新。
对于重度存储过程使用的应用软件而言,尤其那种迭代开发了十几、甚至数十年的应用软件,代码内部存在不计其数的逻辑分支,软件升级后的回归测试,尤其在国产数据库替换时,很难保证所有的代码都被执行过,这个时候就需要有存储过程覆盖率这个功能。很多数据库产品设计plsql覆盖率这个功能只能支持单会话内统计,但真实的场景期望是不要按单个会话统计,而是应用多个连接真正把业务运行起来,所有场景都测完后,再去看这个覆盖率,从而对测试场景进行查漏补缺。
存储过程覆盖率简单来说可以理解为有个数据,里面有每个存储过程的每一行代码,并且有记录每一行是否有执行到,对多会话进行统计就必须要有个公共的区域来进行这个数据的统计。
既然做了全局PLSQL缓存,这不就是现成的公共区域么,而且plsql代码也已经有了,不需要再次获取。而且由于不是用表进行的统计,大大降低了并发时的锁冲突时间。
因此MogDB 5.2.0把存储过程覆盖率这个功能做到了全局PLSQL缓存内。
后面有时间再单独介绍这个功能。

总结

其实openGauss相比原生postgresql,在内存的优化上的确也做了不少工作,比如global plancache–全局计划缓存global syscache–全局系统缓存(数据字典),而且将PG的进程模型改为线程模型,避免了这种内存上下文在进程间频繁交换导致的性能问题。MogDB 5.2.0实现了global plsql cache,并默认开启,这对于拥有重度存储过程代码的ORACLE应用迁移到国产数据库,具有非常深刻的意义。

  • 本文作者: DarkAthena
  • 本文链接: https://www.darkathena.top/archives/mogdb-5.2.0-support-plsql-global-cache
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处

http://www.ppmy.cn/news/1548975.html

相关文章

.NET9 - 新功能体验(一)

被微软形容为“迄今为止最高效、最现代、最安全、最智能、性能最高的.NET版本”——.NET 9已经发布有一周了,今天想和大家一起体验一下新功能。 此次.NET 9在性能、安全性和功能等方面进行了大量改进,包含了数千项的修改,今天主要和大家一起体…

力扣 无重复字符的最长字串-3

无重复字符的最长字串-3 class Solution { public:// 解决方法:双指针int lengthOfLongestSubstring(string s) { // 如果字符串为空,直接返回0if (s.length() 0)return 0;// 如果字符串不为空,字符串每个字符都不同的情况下,最…

鸿蒙NEXT开发案例:血型遗传计算

【引言】 血型遗传计算器是一个帮助用户根据父母的血型预测子女可能的血型的应用。通过选择父母的血型,应用程序能够快速计算出孩子可能拥有的血型以及不可能拥有的血型。这个过程不仅涉及到了简单的数据处理逻辑,还涉及到UI设计与交互体验的设计。 【…

引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例

引用类型的局部变量线程安全问题 背景注意事项分析步骤拆解(文字描述时序图) 背景 最近博主在看B站上的一个JUC并发编程视频中,碰到了一个比较有争议性的局部变量线程安全讨论问题。 先贴代码如下: Slf4j(topic "c.Threa…

数组作为函数参数--选择排序

#include<stdio.h> #define n 6 void s(int a[])//写a的话&#xff0c;是地址 { int i,j; for(i0;i<n-2;i) { for(ji1;j<n-1;j) { if(a[j]<a[i]) { int ta[i]; a[i]a[j];…

大模型时代的具身智能系列专题(十三)

迪士尼研究中心 瑞士苏黎世迪斯尼研究中心致力于不同领域的业务活动&#xff0c;其中包括电影、电视、公园和度假村以及消费产品。我们针对所有这些领域进行科研工作。我们开发能使我们将后道生产元素整合到前级生产中的技术。由此可节省许多昂贵的效果&#xff0c;这些效果最…

python爬虫初体验(五)—— 边学边玩小游戏

1. 打开浏览器 利用webbrowser 模块的 open()函数可以启动一个新浏览器&#xff0c;打开指定的 URL。 import webbrowser webbrowser.open(http://inventwithpython.com/) 2. 猜数字游戏 # -*- coding: utf-8 -*- # This is a guess the number game. import randomsecretN…

iOS 18.2 Beta 4开发者预览版发布,相机新增辅助功能

昨天刚连发iOS 18.1.1正式版、iOS 17.7.2正式版&#xff0c;今天凌晨&#xff0c;苹果就又发布iOS 18.2 Beta 4&#xff0c;据了解iOS18.2正式版将在 12 月初发布。那么这次的更新又有哪些变化呢&#xff1f;下面我们就来一起了解一下。 iOS 18.2 Beta 4的版本号为&#xff1a;…