目 录CONTENT

文章目录

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

DarkAthena
2024-11-15 / 0 评论 / 2 点赞 / 20 阅读 / 0 字

前言

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

测试

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

DECLARE
  v_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 IS

      v_counter NUMBER := 0;
      v_sum NUMBER := 0;
    BEGIN
return;
  ';
  -- 代码翻倍
  FOR i IN 1..trunc(v_line_count/10) LOOP
    v_sql := v_sql || '  v_counter := v_counter + 1;
      v_sum := v_sum + ' || i || ';
      IF MOD(' || i || ', 2) = 0 THEN
        INSERT INTO example_table (id, name, value) VALUES (' || i || ', ''Even'', v_sum);
      ELSE
        INSERT INTO example_table (id, name, value) VALUES (' || i || ', ''Odd'', v_sum);
      END IF;
      FOR j IN 1..5 LOOP
        v_sum := v_sum + j;
      END LOOP;
    ';
  END LOOP;

  -- 结束procedure
  v_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 |  4104592
 PLpgSQL function cache          |   2076184 |  2073416
 LocalSysCacheTopMemoryContext   |   1097888 |   650800
 ThreadTopMemoryContext          |    417288 |   400392
 DefaultTopMemoryContext         |    344608 |   270624
 CBBTopMemoryContext             |    243984 |   223904
 LocalSysCacheMyDBMemoryContext  |    253952 |   220584
 StorageTopMemoryContext         |    125744 |    91224
 Timezones                       |     83504 |    80736
 Type 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 ms

openGauss=# 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 | 17169704
 LocalSysCacheShareMemoryContext           |   5368400 |  4104592
 PLpgSQL function cache                    |   2076184 |  2073416
 LocalSysCacheTopMemoryContext             |   1097888 |   650800
 ThreadTopMemoryContext                    |    466440 |   419720
 DefaultTopMemoryContext                   |    344608 |   270624
 CBBTopMemoryContext                       |    243984 |   223904
 LocalSysCacheMyDBMemoryContext            |    253952 |   223400
 OptimizerTopMemoryContext                 |    108200 |   102888
 StorageTopMemoryContext                   |    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 |  2808640
 PLpgSQL function cache       |   2076184 |  2073416
 ThreadTopMemoryContext       |    587432 |   517960
 FunctionScan_22875809511168  |    532560 |   464656
 ExprContext                  |    516096 |   379232
 StorageTopMemoryContext      |    354384 |   328744
 global_session_memory_detail |    231224 |   227936
 session_memory_detail        |    231224 |   226112
 node_name                    |    223032 |   221184
 CBBTopMemoryContext          |    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 |  2883952
 PLpgSQL function cache       |   2076184 |  2073416
 ThreadTopMemoryContext       |    587432 |   518504
 FunctionScan_22875809511168  |    532560 |   464656
 ExprContext                  |    516096 |   379232
 StorageTopMemoryContext      |    354384 |   329152
 global_session_memory_detail |    231224 |   227936
 session_memory_detail        |    231224 |   226112
 node_name                    |    223032 |   221184
 CBBTopMemoryContext          |    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应用迁移到国产数据库,具有非常深刻的意义。

2
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论