背景
客户应用大量存储过程,并且创建连接后每次只调用一个函数,获得结果关闭连接,也就是大量短连接场景。为了最大限度的提高最大并发数与TPS,我们做了很多尝试。
PL/pgSQL引擎介绍
PostgreSQL 存储过程引擎设计非常先进,预留的接口可以实现非常丰富的语言支持,我们熟知的包括:C、PL/pgSQL、Python、Perl,还有非官方的 Java、PHP、JavaScript 等等。PL/pgSQL是最常用的官方过程语言,性能虽然无法与C语言相比,但胜在开发效率高,有测试表明它比其他几种官方语言引擎要快得多。为最大可能的提高函数调用的吞吐量,飞象数据对函数引擎进行了深入细致的分析,并且做了大量尝试和改进。外部模块加载官方自带或者我们自定义的模块在使用前,PG调用 load_external_function(src/backend/utils/fmgr/dfmgr.c) 进行载入:
for (file_scanner = file_list; file_scanner != NULL && strcmp(libname, file_scanner->filename) != 0; file_scanner = file_scanner->next) ; if (file_scanner == NULL) {
复制
如果模块对应的库文件尚未载入,依次执行载入(pg_dlopen)、校验(magic_func)、初始化(_PG_init)。
初始化(_PG_init)
载入和校验比较简单,是一个相对固定的开销;初始化部分相对复杂,并且不同模块会完全不同,以PL/pgSQL为例包括:1、参数注册2、哈希表创建3、回调函数注册
PL/pgSQL 函数执行过程首先找到引擎入口函数:
flying=# \df+ plpgsql_call_handlerList of functions -[ RECORD 1 ]-------+--------------------- Schema | pg_catalog Name | plpgsql_call_handler Result data type | language_handler Argument data types | Type | normal Volatility | volatile Parallel | unsafe Owner | quanzl Security | invoker Access privileges | Language | c Source code | plpgsql_call_handler Description |
复制
引擎的执行入口函数并没有什么特别,也只是一个 C 语言实现的函数而已。而后,函数信息传递给 plpgsql_call_handler 做解释执行(参见 PL/pgSQL 性能提升之一预编译)。预加载PG使用过程中我们已经遇到过很多要求预加载的例子,比如:pldebugger、plprofiler,它们都要求写在 shared_preload_libraries,在数据库启动时载入和初始化。通过源代码可以看到,预加载模块与其它模块的加载过程是一样的。如果我们在 shared_preload_libraries 中添加 plpgsql,fork出来的新后端进程第一次调用函数时不再需要初始化,尤其在短连接场景下应该会有明显重用作用。实测pgbench 参数 -c 4 -j 4 -C
修改前
duration: 600 s number of transactions actually processed: 537207 latency average = 4.468 ms tps = 895.338221 (including connections establishing) tps = 2497.664396 (excluding connections establishing)
复制
修改后
duration: 600 s number of transactions actually processed: 666096 latency average = 3.603 ms tps = 1110.153868 (including connections establishing) tps = 4741.567239 (excluding connections establishing)
复制
结论短连接预加载 plpgsql 模块,在我们的测试case中可以得到一倍左右的性能提升。对于C 语言实现的模块,同样场景下可以同样处理来获得性能提升。
敬请关注飞象数据