• [POC&交付] Gauss DB(DWS)对接系列-数据可视化工具Grafana 7.4.3版本对接
    GaussDB Kernel提供自研的TSDB时序引擎,提供扩展的时序场景语法,以及分区管理、时序计算、时序生态函数等服务功能,此博文详细描述如何使用Grafana与DWS时序数仓对接,展示时序数据。1.DWS(时序数仓)构造测试数据Data Studio登录DWS(时序数仓)集群,创建用户、数据库及测试表--创建用户create user sxsc password 'Huawei@123';--创建数据库create database sxscdb owner sxsc encoding='utf-8' TEMPLATE template0;--赋予用户sxsc 模式public权限GRANT ALL PRIVILEGES ON SCHEMA public TO sxsc;执行结果如下:创建测试表,构造时序数据Data Studio上使用新建的sxsc用户登录sxscdb数据库创建测试表,并构造时序数据创建测试表:drop table if exists public.jiaocai;create  table if not exists jiaocai(type        text TSTag,devId      text  TSTag,PhV_phsA    double precision  TSField,PhV_phsB    double precision  TSField,PhV_phsC    double precision  TSField,PhV_neut    double precision  TSField,A_phsA      double precision  TSField,A_phsN      double precision  TSField,A_phsC      double precision  TSField,A_phsB      double precision  TSField,PhVAr_phsA  double precision  TSField,PhVAr_phsB  double precision  TSField,PhVAr_phsC  double precision  TSField,TotVAr      double precision  TSField,PhPF_phsA   double precision  TSField,PhPF_phsB   double precision  TSField,PhPF_phsC   double precision  TSField,TotPF       double precision  TSField,TotVA       double precision  TSField,PhW_phsA    double precision  TSField,PhW_phsB    double precision  TSField,PhW_phsC    double precision  TSField,TotW        double precision  TSField,PhVA_phsA   double precision  TSField,PhVA_phsB   double precision  TSField,PhVA_phsC   double precision  TSField,time        timestamp without time zone  TSTime)with (TTL='7 days', PERIOD = '1 day', orientation=TIMESERIES);COMMENT ON COLUMN public.jiaocai.PhVAr_phsA IS 'A相无功功率';COMMENT ON COLUMN public.jiaocai.PhVAr_phsB IS 'B相无功功率';COMMENT ON COLUMN public.jiaocai.PhVAr_phsC IS 'C相无功功率';COMMENT ON COLUMN public.jiaocai.TotVAr     IS '总无功功率';COMMENT ON COLUMN public.jiaocai.PhPF_phsA  IS 'A相功率因数';COMMENT ON COLUMN public.jiaocai.PhPF_phsB  IS 'B相功率因数';COMMENT ON COLUMN public.jiaocai.PhPF_phsC  IS 'C相功率因数';COMMENT ON COLUMN public.jiaocai.TotPF      IS '总功率因数';COMMENT ON COLUMN public.jiaocai.TotVA      IS '总视在功率';COMMENT ON COLUMN public.jiaocai.PhW_phsA   IS 'A相有功功率';COMMENT ON COLUMN public.jiaocai.PhW_phsB   IS 'B相有功功率';COMMENT ON COLUMN public.jiaocai.PhW_phsC   IS 'C相有功功率';COMMENT ON COLUMN public.jiaocai.TotW       IS '总有功功率';COMMENT ON COLUMN public.jiaocai.PhVA_phsA  IS 'A相视在功率';COMMENT ON COLUMN public.jiaocai.PhVA_phsB  IS 'B相视在功率';COMMENT ON COLUMN public.jiaocai.PhVA_phsC  IS 'C相视在功率';语法介绍:TSTag:维度属性字段TSField:指标属性字段TSTime:时间属性字段TTL:数据生命周期,此表数据生命周期为7天PERIOD:自动创建分区间隔,此表分区按照1天间隔创建Orientation:表属性,TIMESERIES说明是时间序列表创建存储过程insert_data(),模拟时序数据入库CREATE OR REPLACE PROCEDURE insert_data(v_number bigint)ASDECLARE  v_insert_jiaocai_string VARCHAR2(4000);  var_count               bigint;BEGIN  var_count               := 0;  v_insert_jiaocai_string :=             'insert into public.jiaocai(                                          type                                          ,devId                                          ,PhV_phsA                                          ,PhV_phsB                                          ,PhV_phsC                                          ,PhV_neut                                          ,A_phsA                                          ,A_phsN                                          ,A_phsC                                          ,A_phsB                                          ,PhVAr_phsA                                          ,PhVAr_phsB                                          ,PhVAr_phsC                                          ,TotVAr                                          ,PhPF_phsA                                          ,PhPF_phsB                                          ,PhPF_phsC                                          ,TotPF                                          ,TotVA                                          ,PhW_phsA                                          ,PhW_phsB                                          ,PhW_phsC                                          ,TotW                                          ,PhVA_phsA                                          ,PhVA_phsB                                          ,PhVA_phsC                                          ,time                                          )                                          values(                                          ''analog''                                          ,''025BHN6RK9522172_JC''                                          ,random()                                          ,random()*300                                          ,random()*300                                          ,random()*300                                          ,random()*300                                          ,random()*90                                          ,random()*300                                          ,random()*300                                          ,random()*60                                          ,random()*50                                          ,random()*40                                          ,random()*30                                          ,random()                                          ,random()                                          ,random()                                          ,random()                                          ,random()*20                                          ,random()*30                                          ,random()*40                                          ,random()*50                                          ,random()*60                                          ,random()*70                                          ,random()*80                                          ,random()*90                                          ,clock_timestamp() - interval ''7.815 hour''                                          );';  while var_count < v_number loop    EXECUTE IMMEDIATE v_insert_jiaocai_string;    var_count := var_count+1;    pg_sleep(5);    commit;  end loop;END;/ 执行存储过程,执行数据插入1000次,每5s插入一次call insert_data(1000);2.Grafana对接DWS时序数仓部署Grafana(windows版)Grafana下载地址:https://grafana.com/grafana/download?platform=windows下载完成后,在windows路径下解压文件grafana-7.4.3.windows-amd64.rar,如下图:进入bin目录双击grafana-server启动服务Grafana对接DWSGrafana登录地址:http://localhost:3000 默认用户名及密码:admin/admin,第一次登陆会提示修改密码Grafana中配置DWS数据源,点击Data Sources点击 Add data source选择PostgreSQL数据源配置DWS集群信息Save&Test保存Database Connection OK 说明连通性测试成功配置时序图标展示数据点击+Add new panel点击配置数据源,选择$DWS配置区配置要展示的时序数据,如下图:点击,可切换为SQL模式配置完成后点击apply应用此配置页面可以选择时序数据展示的时间区间及刷新频率
  • [POC&交付] GaussDB For DWS 字符集浅谈
    1.基本概念字符集(Character set):是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见的字符集有ASCII、ZHS16GBK 、ZHT16BIG5、 ZHS32GB18030字符集、Unicode字符集等。字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其它的一个集合(如电脑编码)进行配对。即在符号集合与数字系统之间建立对应关系。与字符集相对应,常见的字符编码有:ASCii,ZHS16GBK,ZHT16BIG5,ZHS32GB18030等。字符集的定义其实就是字符的集合,而字符编码则是指怎么将这些字符变成字节用于保存、读取和传输。2.字符集的前世今生1)通用字符集UCS通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的字符编码方式,采用4字节编码。又称Universal Multiple-Octet Coded Character Set,大陆译为通用多八位编码字符集,台湾译为广用多八位元编码字元集。通用字符集是所有包括了其他字符集。它保证了与其他字符集的双向兼容,即,如果你将任何文本字符串翻译到UCS格式,然后再翻译回原编码,你不会丢失任何信息。UCS包含了已知语言的所有字符。除了拉丁语、希腊语、斯拉夫语、希伯来语、阿拉伯语、亚美尼亚语、乔治亚语,还包括中文、日文、韩文这样的象形文字,UCS还包括大量的图形、印刷、数学、科学符号。通用字符集是与UNICODE同类的组织,UCS-2和UNICODE兼容。2)Unicode编码Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。包含了几乎人类所有可用的字符,每年还在不断的增加,可以看作是一种通用的字符集。Unicode定义了大到足以代表人类所有可读字符的字符集。Java语言就用到了Unicode编码,从而实现了该语言的国际通用性。Unicode 是基于通用字符集(Universal Character Set)的标准来发展, 用数字0-0x10FFFF来映射这些字符,或者说有1114112个码位(码位就是可以分配给字符的数字)。对可以用ASCII表示的字符使用UNICODE并不高效,因为UNICODE比ASCII占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。为了解决这个问题,有人发明了一种针对UNICODE的变换规则,把UNICODE字符串中的0去除. 注意这个变换规则不是通过查表实现的,而只要用一些位移操作就可以实现. 这就是UTF,他们被称为通用转换格式,即UTF(UCS Transformation Format)。常见的UTF格式有:UTF-32编码:固定使用4个字节来表示一个字符,这种编码存在空间利用效率的问题。UTF-16编码:对相对常用的60000余个字符使用两个字节进行编码,其余的使用4字节。UTF-8编码: 兼容ASCII编码;拉丁文、希腊文等使用两个字节;包括汉字在内的其它常用字符使用三个字节;剩下的极少使用的字符使用四个字节。下面重点介绍下UTF-8编码:UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式为:将Unicode二进制从低位往高位取出二进制数字,每次取6位,如上述的二进制就可以分别取出为如下示例所示的格式,前面按格式填补,不足8位用0填补。Unicode编码(16进制) UTF-8 字节流(二进制)000000 - 00007F 0xxxxxxx000080 - 0007FF 110xxxxx   10xxxxxx000800 - 00FFFF 1110xxxx   10xxxxxx 10xxxxxx010000 - 10FFFF 11110xxx   10xxxxxx 10xxxxxx 10xxxxxxUTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。例2:Unicode编码0x20C30在0x010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。3)ASCIIASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是ANSI(American National Standards Institute)美国国家标准学会的规范标准,世界上所有的计算机都用同样的ASCII方案来保存英文文字。因为ASCII没有可以利用的字节状态来表示汉字,中国人在ASCII的基础上,通过组合的方式,组合除了大约7000多个简体汉字,把这种方案叫GB2312, GB2312是对ASCII 的中文扩展。4)GB2312中文文字数目大,而且还分为简体中文和繁体中文两种不同书写规则的文字,而计算机最初是按英语单字节字符设计的,因此,对中文字符进行编码,是中文信息交流的技术基础。我国国家标准总局1980年发布《信息交换用汉字编码字符集》,标准号是GB 2312—1980,它是计算机可以识别的编码,适用于汉字处理、汉字通信等系统之间的信息交换。基本集共收入汉字6763个和非汉字图形字符682个。GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。1995年国家标准总局又颁布了《汉字编码扩展规范》(GBK)。GBK与GB 2312—1980国家标准所对应的内码标准兼容。对于人名、古汉语等方面出现的罕用字,GB2312不能处理,这导致了后来GBK及GB18030汉字字符集的出现。注意:UTF8 只是 UNICODE内码在存储/传输时的状态. 而从GB2312编码转换到UNICODE编码需要查表. UTF8 和 UNICODE 的关系 与 GB2312 和 UNICODE的关系有本质的不同. UTF8 和 UNICODE 是一个人的两个面孔, GB2312 和 UNICODE 是两个人. 所以,要实现UTF8编码到GB2312编码的转换必须先把 UTF8编码还原为UNICODE编码,再通过查表的方式,把UNICODE编码转化为GB2312编码。5)Latin1Latin1是ISO-8859-1的别名,有些环境下写作Latin1,是单字节编码,向下兼容ASCII。 3. Linux字符集提到linux字符集就不得不说locale。locale 是国际化与本土化过程中的一个非常重要的概念,对于中文用户来说,通常会涉及到的国际化或者本土化,大致包含两个方面:看中文,写中文, locale的设定与看中文关系不大,但是与写中文有很密切的关系。locale这个单词中文翻译成地区或者地域,其实这个单词包含的意义要宽泛很多。Locale是根据计算机用户所使用的语言,所在国家或者地区,以及当地的文化传统所定义的一个软件运行时的语言环境。这个用户环境可以按照所涉及到的文化传统的各个方面分成几个大类,通常包括用户所使用的语言符号及其分类(LC_CTYPE),数字 (LC_NUMERIC),比较和排序习惯(LC_COLLATE),时间显示格式(LC_TIME),货币单位(LC_MONETARY),信息主要是提示信息,错误信息, 状态信息, 标题, 标签, 按钮和菜单等(LC_MESSAGES),姓名书写方式(LC_NAME),地址书写方式(LC_ADDRESS),电话号码书写方式 (LC_TELEPHONE),度量衡表达方式(LC_MEASUREMENT),默认纸张尺寸大小(LC_PAPER)和locale对自身包含信息的概述(LC_IDENTIFICATION)。例如:上面均说明LC_CTYPE(语言符号及其分类)表示这个系统的系统现在使用的字符集是en_US.UTF-8,LC_NUMERIC(数字)等其它与语言相关的变量。通常如果其它的语言变量都未设定,仅设定LANG这个变量就可以缺省代替所有其它变量了。Locale实际就是某一个地域内的人们的语言习惯和文化传统和生活习惯。一个地区的locale就是根据这几大类的习惯定义的,这些locale定义文件放在/usr/share/i18n/locales目录下面:4.GaussDB For DWS 字符集GaussDB for dws 默认使用sql_ascii作为默认字符集,ASCII存储的是单字节流,并且没有能力判断多字节字符的有效性, 所以会一股脑存储进去,换句话说你可能在SQL_ASCII中存储了utf8编码的字符, 也存储了gbk编码的字符, 还存储了其他编码的字符. 那么要将sql_ascii转换成UTF8就不是一件易事了, 会遇到多种字符的转换工作。如果数据库的编码为SQL_ASCII(可以通过“show server_encoding”命令查看当前数据库存储编码),则在创建数据库对象时,如果对象名中含有多字节字符(例如中文),超过数据库对象名长度限制(63字节)的时候,数据库将会将最后一个字节(而不是字符)截断,可能造成出现半个字符的情况。针对这种情况,请遵循以下条件:保证数据对象的名称不超过限定长度。使用例如utf-8编码集做为数据库的默认存储编码集(server_encoding)。不要使用多字节字符做为对象名。 结合以上情况,在GaussDB for dws初始化创建数据库时需要根据业务场景设置字符集。GaussDB for dws支持的字符集(编码格式): GBK、UTF-8和Latin1编码格式,多个数据库可以设置为不同的字符集。字符集规划原则设置方法GBK如果数据库只需要支持中文,数据量很大,性能要求也很高,那就应该选择双字节定长编码的中文字符集。对GaussDB for dws来说,目前只能选择GBK 。在安装数据库时指定初始化参数-E。通过SQL语句创建数据库时指定ENCODING参数。UTF-8如果应用程序要处理各种各样的文字,或者将处理结果发布到使用不同语言的国家或地区,就应该选择Unicode字符集。对GaussDB   for dws来说,目前只能选择UTF-8 。Latin1如果数据库只需要支持ASCII收录的字符、西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号,则可以选择Latin1。在使用GaussDB for dws过程中,服务端和客户端都可以设置字符集。由于服务端与客户端可以设置为不同的字符集,所以两者字符集中单个字符的长度也会不同,所以产生的最终结果可能会与预期不一致。如果服务端与客户端设置为不同的字符集,则客户端输入的字符串会以服务端字符集的格式进行处理,很有可能产生与预期不一致的结果。操作过程服务端和客户端编码一致服务端和客户端编码不一致存入和取出过程中没有对字符串进行操作输出预期结果输出预期结果(输入与显示的客户端编码必须一致)。存入取出过程对字符串有做一定的操作(如字符串函数操作)输出预期结果根据对字符串具体操作可能产生非预期结果。存入过程中对超长字符串有截断处理输出预期结果字符集中字符编码长度是否一致,如果不一致可能会产生非预期的结果。客户端及服务端字符集设置方法:client_encoding: 客户端的字符编码类型, 根据前端业务的情况确定。尽量客户端编码和服务器端编码一致,提高效率。参数类型:USERSET(普通用户参数,可被任何用户在任何时刻设置。)取值范围:兼容PostgreSQL所有的字符编码类型。其中UTF8表示使用数据库的字符编码类型。说明:使用命令locale -a查看当前系统支持的区域和相应的编码格式,并可以选择进行设置。默认情况下,gs_initdb会根据当前的系统环境初始化此参数,通过locale命令可以查看当前的配置环境。参数建议保持默认值,不建议通过gs_guc工具或其他方式直接在postgresql.conf文件中设置client_encoding参数,即使设置也不会生效,以保证集群内部通信编码格式一致。参数设置方法:gs_guc reload -Z coordinator -Z datanode -N all -I all -c " client_encoding =utf8"SET client_encoding TO utf8; server_encoding参数说明:报告当前数据库的服务端编码字符集。该参数属于INTERNAL类型参数,为固定参数,用户无法修改此参数,只能查看。 5. 常见问题乱码及报错简单的说乱码的出现是因为:编码和解码时用了不同或者不兼容的字符集。在计算机科学中,一个用UTF-8编码后的字符,用GBK去解码。由于两个字符集的字库表不一样,同一个汉字在两个字符表的位置也不同,最终就会出现乱码。因client_encoding与server_encoding设置不一致,导致报错或者乱码。示例:测试数据:创建数据库,字符集为gbk,导入数据CREATE DATABASE gbkdb ENCODING 'GBK' template =   template0;创建测试表create table t_gbk(id int,name varchar);导入数据copy t_gbk from '/home/omm/encode/test.csv'   with(format 'csv',encoding 'gbk');查询表数据报错select * from t_gbk;问题定位:查看server_encoding及client_encoding         因为数据是按照GBK存储的,使用utf-8查看,在转码时发生错误。解决方案:设置client_encoding=’GBK’查询不再报错,但是显示仍是乱码设置crt工具字符集查询数据:问题解决。
  • [技术干货] GaussDB(DWS)云端运维系列第九期:集群安全(三权分立)
    1. 使用场景默认情况下,创建GaussDB(DWS) 集群时指定的管理员用户属于数据库的系统管理员,能够创建其他用户和查看数据库的审计日志,即权限不分立,三权分立模式为关闭。在实际业务管理中,为了保护用户的数据安全,避免系统管理员拥有过度集中的权利带来高风险,DWS支持使用不同类型的用户分别控制不同权限的三权分立模式。开启此模式后将系统管理员的权限分立给安全管理员和审计管理员。2. 三权分立模型开启三权分立后用户划分为:系统管理员、安全管理员、审计管理员和普通用户。系统管理员将不再具有CREATEROLE属性(安全管理员)和AUDITADMIN属性(审计管理员)能力。即不再拥有创建角色和用户的权限,并不再拥有查看和维护数据库审计日志的权限。‘未开启三权分立前默认用户角色权限如下表对象名称系统管理员安全管理员|审计管理员表空间对表空间有创建、修改、删除、访问、分配操作的权限。不具有对表空间进行创建、修改、删除、分配的权限,访问需要被赋权。表对所有表有所有的权限。仅对自己的表有所有的权限,对其他用户的表无权限。索引可以在所有的表上建立索引。仅可以在自己的表上建立索引。模式对所有模式有所有的权限。仅对自己的模式有所有的权限,对其他用户的模式无权限。函数对所有的函数有所有的权限。仅对自己的函数有所有的权限,对其他用户放在public这个公共模式下的函数有调用的权限,对其他用户放在其他模式下的函数无权限。自定义视图对所有的视图有所有的权限。仅对自己的视图有所有的权限,对其他用户的视图无权限。系统表和系统视图可以查看所有系统表和视图。只可以查看部分系统表和视图。开启三权分立后权限变化对象名称系统管理员安全管理员|审计管理员表空间无变化无变化。表权限缩小。只对自己的表有所有权限,对其他用户放在属于各自模式下的表无权限。无变化。索引权限缩小。只可以在自己的表上建立索引。无变化。模式权限缩小。只对自己的模式有所有的权限,对其他用户的模式无权限。无变化。函数权限缩小。只对自己的函数有所有的权限,对其他用户放在属于各自模式下的函数无权限。无变化。自定义视图权限缩小。只对自己的视图及其他用户放在public模式下的视图有所有的权限,对其他用户放在属于各自模式下的视图无权限。无变化。系统表和系统视图无变化。无变化。3. 操作步骤3.1 三权分立设置前三权分立开关:关闭用户角色3.2 设置三权分立GaussDB(DWS) 管理控制台 -> 左侧导航树 -> 集群管理选择集群 -> 单击集群名称进入集群详情 -> 单击安全设置 -> 打开三权分立开关 -> 配置安全管理员和审计管理员单击应用 -> 保存配置并勾选重启集群至此完成三权分立设置3.3 三权分立设置后三权分立开关:开启用户角色:新增安全管理员具有Create roles属性,审计管理员具有Administer audit属性
  • [存储] 打破黑盒子——GaussDB(DWS)导入导出业务监控方法总结
    GDS、COPY、\COPY 等工具是 GaussDB(DWS) 提供的数据导入导出工具,常应用于数据迁移、数据同步、数据备份/恢复等场景。GDS 等工具在数仓日常维护中使用频率较高,难免遇到性能、功能问题,此时如果能监控到 GDS 在干什么、业务进度如何,必然能为排故提供有效参考,提高排查效率。本文从系统视图、运行日志两个方面介绍导入导出的监控方法,并配合实例进行说明,最后简要介绍 PostgreSQL 14 的 COPY 监控视图进行简单对比。1. 视图目前主要有2类系统视图可以对 GDS 业务进行不同角度的实时监控:backend状态业务执行状态下面分别对这两类视图进行解释说明。1.1 backend 状态通过系统视图 pg_stat_activity、pgxc_thread_wait_status,可以获知导入/导出backend线程的执行状态,如:是否 hang、是否死锁等。pg_stat_activity 一般会过滤 state 使用,通过在查询结果中寻找相应的 query,对所在记录进行观察分析。select * from pg_stat_activity where state = 'active';查询结果类似下图,图中红框框住的是刚刚发起 GDS 导入业务。pg_stat_activity 视图提供信息有限,可与视图 pgxc_thread_wait_status 进行关联查询等待状况,确认是否存在hang以及hang的直接原因:select wait.*, stat.query, stat.state, stat.query_start from pgxc_thread_wait_status wait inner join pg_stat_activity stat on wait.tid = stat.pid;查询结果类似下图,图中红框框住的是刚刚发起GDS导入业务,wait_status 列的内容是“wait node(total 3): datanode3”,表示CN此刻等待的节点共有3个,其中一个是datanode3。这个结果是与测试环境1CN 3DN一致的。1.2 业务执行状态2021年4月发布的 GaussDB(DWS) 8.1.1 开始提供导入导出实时监控视图:pg_bulkload_statistics、pgxc_bulkload_statistics、pgxc_bulkload_progress,支持对GDS、COPY、\COPY的实时监控。与既有约定一致,pgxc_bulkload_statistics、pgxc_bulkload_progress 是在 CN 上查询,用于获取整个集群的导入导出业务的实时状况,pg_bulkload_statistics 可在各节点上执行,用于获取当前节点的导入导出业务的实时状况。注意,这类视图需要有系统管理员权限才可以访问。pg_bulkload_statistics、pgxc_bulkload_statistics 视图字段一样,详细说明见下表。字段名称字段类型字段说明node_nametext节点名称db_nametext数据库名称query_idbigint查询IDtidbigint当前线程的线程号lwtidinteger当前线程的轻量级线程号session_idbigintGDS的会话IDdirectiontext业务类型,取值包括:gds to file、gds from file、gds to pipe、gds from pipe、copy from、copy to。querytext查询语句addresstext当前导入/导出外表的locationquery_starttimestamp导入/导出开始时间total_bytesbigint待处理数据的总大小。仅GDS普通文件导入时,且该行记录来自CN节点才会显示,否则为空。phasetext当前业务阶段,包括: INITIALIZING(初始化)、TRANSFER_DATA(传输中)、RELEASE_RESOURCE(结束)。done_linesbigint已传输行数done_bytesbigint已传输字节数pgxc_bulkload_progress 视图只可用于GDS普通文件导入业务场景,本质上是基于 pgxc_bulkload_statistics 视图的聚合结果,其字段说明如下:字段名称字段类型字段说明session_idbigintGDS的会话IDquery_idbigint查询IDquerytext查询语句progresstext业务进度百分比这类视图可以直接使用,或按需使用字段过滤,或者关联其他表/视图进行查询。查询集群级导入导出的场景举例:-- 查询集群所有的导入导出业务实时状况 select * from pgxc_bulkload_statistics; -- 查询集群导入业务的实时进度 select * from pgxc_bulkload_progress; -- 另外,也可以基于这类视图进行适当的计算,如计算各 DN 传输速率: select node_name, query, round(done_bytes / 1024 / extract(epoch from(current_timestamp - query_start)), 2) || 'kB/S' as avg_speed from pgxc_bulkload_statistics; -- 计算总导入/导出速率: with trans_bytes as (select query_id, query, sum(done_bytes) as total_bytes from pgxc_bulkload_statistics group by (query_id,query)) select trans_bytes.*, stat.query_start, round(total_bytes / 1024 / extract(epoch from(current_timestamp - stat.query_start)), 2) || 'kB/S' as speed from trans_bytes, pg_stat_activity as stat where trans_bytes.query_id = stat.query_id;以上样例的执行效果如图:查询节点级导入导出业务的场景举例:-- 查询当前节点的导入导出业务的实时进度 select * from pg_bulkload_statistics; -- 与pg_thread_wait_status关联查询,查看是否有hang select stat.*, wait.wait_status, wait.wait_event from pg_bulkload_statistics stat join pg_thread_wait_status wait on stat.tid = wait.tid;以上样例的执行效果如图:2. 日志导入导出业务会在不同的执行阶段向运行日志中写入业务状态记录,所以我们也可以通过日志查看执行状况。但是由于获取日志的不便性,以及分析日志的滞后性,日志获取状况是最最最后的方法,我们推荐实时视图作为导入导出监控的首选方法。2.1 CN/DN 日志根据业务的不同阶段和类型,打印日志内容如下表所示:初始化阶段数据传输阶段结束阶段gds importsession XXXXX gds import is in the INITIALIZING state.sesssion: XXXXX gds import is transforming from INITIALIZING to TRANSFER_DATA state.Session XXXXX gds import has transformed from TRANSFER_DATA to RELEASE_RESOURCE state.gds exportsession: XXXXX gds export is in the INITIALIZING state.session: XXXXX gds export is transforming from INITIALIZING to TRANSFER_DATA state.Session XXXXX gds export has transformed TRANSFER_DATA to RELEASE_RESOURCE state.copy fromcopy from is in the INITIALIZING state.copy from is transforming from INITIALIZING to TRANSFER_DATA state.copy from has transformed from TRANSFER_DATA to RELEASE_RESOURCE state.copy tocopy to is in the INITIALIZING of state.copy to is transforming from INITIALIZING to TRANSFER_DATA state.copy to has transformed from TRANSFER_DATA to RELEASE_RESOURCE state.2.2 GDS 日志GDS 通过设置启动参数 --debug-level 可以控制打印日志级别。GDS 在关键环节上也有日志打印,如下列出的日志内容是比较重要的日志,默认日志级别可见:(1)CN/DN 与 GDS 建连时:关键内容: [HandleAccept] "ip:port" connected to GDS, fd is xxx, ptr is xxx 日志示例: 2021-07-30 16:24:08.274 72442 MT LOG: [HandleAccept] "127.0.0.1:58570" connected to GDS, fd is 4, ptr is 0xfc95d0(2)CN/DN 将要关闭连接时(只有导出业务才会有,一般是先打印DN相关的关闭信息,最后是 CN):关键内容: Session XXXX close connection in HandleRead. addr: ip:port, conn ptr is xxx 日志示例:2021-07-31 10:34:42.345 3478 WT LOG: Session 81627743246173930 close connection in HandleRead. addr: 127.0.0.1:46600, conn ptr is 0xfc7bc0(3)CN/DN关闭连接时:关键内容:xxx closed. Event: READING/WRITING 日志示例:2021-07-30 16:24:36.208 3478 WT LOG: Session: 81627743246142387 HandleError Node: datanode1 IP: 127.0.0.1:58580, fd is 17, connect ptr is 0xfc81b0 closed. Event: READING(4)主线程成功把工作分配给工作线程:关键内容:Session xxx is being transfered to worker. 日志示例:2021-07-30 16:30:06.148 72442 MT LOG: Session 81627743246142569 is being transfered to worker.3. 结合实例,学以致用3.1 例1:发现copy性能瓶颈在 COPY 导出的时候,执行视图发现同一时刻只有1个DN的传输量在增加,表明是CN是阻塞地只接收某一DN到结束,然后才开始接收下一个DN数据,存在性能问题,说明copy有较大优化空间,可以尝试使用并行读取提高性能。3.2 例2:区分是执行慢还是hang某测试集群执行 GDS 业务比平常慢很多,耗时很长仍没有结束,怀疑是 GDS hang 住。通过查询导入导出视图发现传输数据是在缓慢增加的,进一步查看发现此刻集群上有其他导入导出业务在运行,此时集群已经承压较大,业务缓慢是正常现象,由此自证清白,排除了 hang 问题。4. 知识扩展:PostgreSQL是怎么做的?PostgreSQL 14 之前并没有专门针对导入导出业务的监控工具,如果要进行监控,只能使用 pg_stat_activity、pg_locks 等通用视图。社区也注意到了这方面的缺陷,故前不久发布的PostgreSQL 14新增了对COPY命令的实时监控视图 pg_stat_progress_copy,可监控所有正在执行的COPY命令,具体的代码提交请参考 ReportProgressOfCopyCommands。每当 COPY 运行时,pg_stat_progress_copy 视图将为当前运行 COPY 命令的每个 backend 构造一行记录。下表对视图字段进行说明:字段名称字段类型字段说明pidintegerbackend进程号datidoidbackend连接的database的oiddatnamenamebackend连接的database的名字relidoid执行COPY命令的表的OID。如果从SELECT查询复制,则设置为0。commandtext正在运行的命令为:COPY FROM或COPY TO。typetext用于读/写数据的IO类型:FILE, PROGRAM, PIPE(用于COPY from STDIN和COPY to STDOUT),或CALLBACK(例如在逻辑复制的初始表同步过程中使用)。bytes_processedbigintCOPY命令已经处理的字节数。bytes_totalbigintCOPY FROM命令中源文件的大小,以字节为单位。如果不可用,则设置为0。tuples_processedbigintCOPY命令已经处理的元组数目。tuples_excludedbigint由于COPY命令的WHERE子句排除了它们而未处理的元组的数目。以下是使用 PostgreSQL 14 Beta2 版本执行 COPY 文件导入时,查询监控视图的结果:对比可知,GaussDB(DWS) 导入导出监控视图提供的信息更全面,包括具体文件名、query 语句、执行时间、执行阶段等关键信息,基于此可以进行较为复杂的聚合计算,提供不同的监控视角;PostgreSQL 14 相对比较简单,但其字段 tuples_excluded 可以额外刻画 WHERE 过滤掉的tuples。
  • [集群&DWS] GaussDB(DWS)通信库libpq重构介绍(三)
    1.libpq合并 数据结构设计Conn结构体作为内部接口、外部接口的参数之一,保存着连接信息,存在于通信连接的整个生命周期中,上层应用通过Conn结构体中保存的连接信息进行通信。3个模块各自定义了一个Conn结构体(CM_Conn、GTM_Conn、PGconn),libpq合并首先要解决的问题就是如何将3个Conn结构体合并。图 1 三个Conn结构体公共成员图 2 CM_Conn独有成员图 3 GTM_Conn独有成员图 4 PGconn独有成员从图 4‑10、图 4‑11、图 4‑12、图 4‑13可以看出,三个Conn结构体包含大量公共成员,只有少数成员是某个Conn结构体所特有的。因此,通过取并集的方式对三个Conn结构体进行合并,合并以后的Conn结构体命名为pg_conn,位于libpq-int.h头文件中,不对外暴露。图 5 CM_Result结构体定义图 6 GTM_Result结构体定义图 7 PGresult结构体定义Conn结构体包含的result成员与上层定义的通信协议直接相关,从图 4‑14、图 4‑15、图 4‑16中可以看出,由于3个模块各自的应用层交互协议差异很大,导致CM_Result、GTM_Result、PGresult结构体差异很大,除非统一3个模块的交互协议,否则,result结构体难以合并。通过讨论分析,统一3个模块的通信交互协议带来的收益不大,所以,Conn结构体合并以后,result成员类型定义为void,在CM模块、GTM模块、BACKEND模块中访问result成员时进行强制类型转换即可。合并以后的Conn结构体新增conn_type成员,用来标识Conn类型,如下图所示。图 8 LibpqConnType枚举类型图 9 Conn结构体使用方式如图 4‑18所示,3个模块各自的libpq-fe.h头文件中,通过typedef方式对pg_conn进行前置声明,别名分别为CM_Conn、GTM_Conn、PGconn,模块内部都以指针的方式使用Conn结构体,这样就可以在隐藏Conn结构体细节的前提下,使用Conn结构体进行通信。
  • [集群&DWS] GaussDB(DWS)通信库libpq重构介绍(二)
    1. Poller锁优化详细设计1.1外部接口介绍CleanConnection、pg_pooler_status、pv_total_memory_detail、pgxc_pool_reload、pg_pool_validate等pooler相关函数视图在内部实现上均有修改,但未修改接口对外表征及具体语义。1.2  空闲连接的无锁存取数据结构设计图 1‑1 空闲连接存储数据结构(优化前)图 1‑2 空闲连接数组的存入(优化前,需加锁)图 1‑3 空闲连接数组的取出(优化前,需加锁)如上方图片所示,优化前空闲连接的总数据结构是,不同的DB属性DBPool组成的全局DBPool List,不同DB间的连接不能复用。每个DBPool拥有一个存储DN节点个数NodePool的Hash表,用来存储连往不同DN的空闲连接。每个NodePool拥有一个slot指针数组,前面的freeSize是有效的连接。slot连接的取还都是加锁后操作最后一个非空指针实现的。优化前需要加锁的位置:DBPool List的查找、遍历、新增(代码未实现删除操作)。NodePool Hash的查找、遍历、新增、删除(LW_EXCLUSIVE)。Slots数组的新增、删除、遍历。图 1‑4 空闲连接存储数据结构(优化后)       无锁ringbuffer介绍,从read到max_read为可读区域,从max_read到write为正在写入但还未完全写好的区域,从write到read为可写区域,三个index数组下标的操作需为CAS操作,支持并发读写。       如上方图片所示,优化后的修改主要是将存储空闲连接的slot指针数组修改为无锁ringbuffer。       优化后去除加锁的位置:DBPool List的查找,遍历不加锁,新增保留加锁,不实现删除。(在不free list item的前提下可实现新增不加锁。不删除drop的DB如果影响性能,可以考虑挪到List尾的方式保留内存,不影响查找效率。)NodePool Hash的查找、遍历加LW_SHARED锁,新增加LW_EXCLUSIVE锁,为了保证无锁查找不做删除操作。新增仅在增删节点时使用,遍历只在视图函数中使用,不影响普通场景下查询性能。Slots的新增、删除、遍历(使用对无锁队列先pop再push的方法)。2. Agent index的无锁存取数据结构设计图 4‑6 Agent连接存储数据结构(优化前)如上方图片所示,优化前使用中的连接(以下称Agent连接)存储在每个线程自己的poolAgent数据结构中,因为遍历所有poolAgent的需求,每个线程又会把自己的poolAgent指针挂到全局的poolAgents指针数组。优化前需要加锁的位置:工作线程将agent指针在全局数组的存入和取出。工作线程将某个节连接存入Conn*指针数组。视图函数对全局poolAgents数组的遍历及对某个agent的读取。图 4‑7 Agent连接存储数据结构(优化后)如上方图片所示,优化后的修改点如下:使用无锁ringbuffer保存可用的agent index,并发存取index不再需要加锁。poolAgents全局数组中每个元素除了原有的agent指针外,增加status标志,状态为RUN时,保证agent指针内存是可读的,此时其他视图函数线程可CAS修改为HOLD状态,agent此时将无法修改为idle状态,内存不会被释放,实现无锁并发读。Conn*指针会随时free,无法实现无锁并行读。agent中Conn*连接指针中保存的socket和remotePid信息,复制一份存入ConnDef。ConnDef与agent整体一起malloc/free,在RUN状态时,此部分内存是实时可读的。优化后去除加锁的位置:agent index的并发存取。Conn*指针数组不再会被其他线程并发读,可以视为thread local变量,无需加锁。agent数组的遍历及单个agent中部分元素的并发读(需HOLD状态读保护)。
  • [集群&DWS] GaussDB(DWS)中多路IO复用介绍
    多路IO复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程。多路IO复用共有三种实现模式:selectpollepoll1、select1.1 select进行IO复用原理当一个客户端连接上服务器时,服务器就将其连接的fd加入fd_set集合,等到这个连接准备好读或写的时候,就通知程序进行IO操作,与客户端进行数据通信。大部分 Unix/Linux 都支持 select 函数,该函数用于探测多个文件描述符的状态变化。1.2 select函数原型int select( int maxfdp, //Winsock中此参数无意义 fd_set* readfds, //进行可读检测的Socket fd_set* writefds, //进行可写检测的Socket fd_set* exceptfds, //进行异常检测的Socket const struct timeval* timeout //非阻塞模式中设置最大等待时间 )1.3 使用select的步骤创建所关注的事件的描述符集合(fd_set),对于一个描述符,可以关注其上面的读(read)、写(write)、异常(exception)事件,所以通常,要创建三个fd_set,一个用来收集关注读事件的描述符,一个用来收集关注写事件的描述符,另外一个用来收集关注异常事件的描述符集合。调用select()等待事件发生。这里需要注意的一点是,select的阻塞与是否设置非阻塞I/O是没有关系的。轮询所有fd_set中的每一个fd,检查是否有相应的事件发生,如果有,就进行处理。2、pollpoll本质上和select没有太大区别,都是先创建一个关注事件的描述符的集合,然后再去等待这些事件发生,然后再轮询描述符集合,检查有没有事件发生,如果有,就进行处理。2.1 Poll使用流程创建描述符集合,设置关注的事件调用poll(),等待事件发生。下面是poll的原型:      int poll(struct pollfd *fds, nfds_t nfds, int timeout);      类似select,poll也可以设置等待时间,效果与select一样。轮询描述符集合,检查事件,处理事件。2.2 与select区别select需要为读、写、异常事件分别创建一个描述符集合,最后轮询的时候,需要分别轮询这三个集合。而poll只需要一个集合,在每个描述符对应的结构上分别设置读、写、异常事件,最后轮询的时候,可以同时检查三种事件。它没有最大连接数的限制,原因是它是基于链表来存储的。2.3 poll的缺点大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。 poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。3、epollpoll和select,它们的最大的问题就在于效率。它们的处理方式都是创建一个事件列表,然后把这个列表发给内核,返回的时候,再去轮询检查这个列表,这样在描述符比较多的应用中,效率就显得比较低下了。epoll是一种比较好的做法,它把描述符列表交给内核,一旦有事件发生,内核把发生事件的描述符列表通知给进程,这样就避免了轮询整个描述符列表。epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。epoll与select和poll的调用接口上的不同:select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。3.1 epoll的使用步骤创建一个epoll描述符,调用epoll_create()来完成。epoll_create()有一个整型的参数size,用来告诉内核,要创建一个有size个描述符的事件列表(集合)。      int epoll_create(int size)给描述符设置所关注的事件,并把它添加到内核的事件列表中。这里需要调用epoll_ctl()来完成。       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)等待内核通知事件发生,得到发生事件的描述符的结构列表。该过程由epoll_wait()完成。得到事件列表后,就可以进行事件处理了。      int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)3.2 epoll的LT和ET的区别水平触发和边缘触发的区别:只要句柄满足某种状态,水平触发就会发出通知;而只有当句柄状态改变时,边缘触发才会发出通知。LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。3.3 epoll的优点没有最大并发连接的限制,能打开FD的上限远大于1024(1G的内存上能监听约10万个端口);效率提升。不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。内存拷贝。epoll通过内核和用户空间共享一块内存来实现消息传递的。利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap 减少复制开销。epoll保证了每个fd在整个过程中只会拷贝一次(select,poll每次调用都要把fd集合从用户态往内核态拷贝一次)。
  • [其他] GaussDB(DWS)【升级】DWS.instance-package-register 任务执行失败
    问题现象:1.DWS.instance-package-register 任务执行失败问题分析:1.查看任务详情:    连接ip,port异常   2.查看cdk对应dwscontroller接口的注册,没有对应的接口注册3.dwscontroller服务容器内部端口18080没有正常启动4.查看cdk endpointkubectl get ep -ndws 查看对应的ip是否是现在容器的ip,发现ip不一致结论,cdk集群问题,重启cdk规避问题根因:    cdk集群endpoint 没有更新解决方法:    重启cdk集群,重启前,请联系cdk oncall确认重启方法
  • [集群&DWS] Gaussdb(DWS)无锁队列介绍
    无锁队列旨在解决多线程资源争抢时加锁造成的性能慢问题,Gaussdb(DWS)无锁队列已经作为公共组件可以被其他模块调用。相比网络上其他的无锁队列实现,Gaussdb(DWS)无锁队列因为其良好的设计具备了更为出色的性能,据测试,Gaussdb(DWS)无锁队列在性能上优于其他实现3倍以上,如此强大的实现,今天就一起来了解一下。一、Gaussdb(DWS)无锁队列数据结构Gaussdb(DWS)无锁队列数据结构如下所示:class ArrayLockFreeQueue { public: ArrayLockFreeQueue(); virtual ~ArrayLockFreeQueue(); int Initialize(uint32_t queueSize, MemoryContext context = NULL); uint32_t Size(); bool Push(void *data); void *Pop(); private: void **m_theQueue; //存储数组 volatile char* volatile m_written; //辅助数组:表示数据正在写入 volatile uint32_t m_queueSize; //存储数组的大小 volatile uint32_t m_indexCompare; //比较下标,用于将数组转化为环状 volatile uint32_t m_writeIndex; //写下标 volatile uint32_t m_readIndex; //读下标 volatile uint32_t m_maximumReadIndex; //最大可读下标 inline uint32_t CountToIndex(uint32_t count); };队列作为一种C++容器适配器,无锁队列也不例外,从方法上讲,其包含了普通队列的Size()、Pop()、Push()操作。从成员变量而言,无锁队列内部由一个大小为m_queueSize的数组实现,用以存储队列元素;读下标和写下标分别对应数组中对应的当前可读和可写的元素,顾名思义,Pop操作就是读出读下标对应的元素,Push操作就是将元素写入写下标对应的位置。从读下标往右,一直到写下标,都是可读元素,如下图所示:上图中,蓝色元素表示可读,黄色元素表示可写/为空,可以看出,当下标m_readIndex大于m_writeIndex时,可读元素实际上在数组中呈环状,为了在环状读写中获得正确的下标,Gaussdb(DWS)无锁队列定义了无锁队列比较下标m_indexCompare,其值为m_queueSize-1,所以当下标按序增长时,只要做index&m_indexCompare就可以得到正确下标。在这里,请读者理解一个概念,或者是在此约定,可读并不代表读到准确的数据,试想这样的一个场景:当线程A做push操作时,首先step1,将m_writeIndex更新为m_writeIndex+1;然后step2,将数据放入m_theQueue[m_writeIndex]。在这两步之间,如果读到了m_theQueue[m_writeIndex],显然会产生数据同步问题。为了避免这种情况,定义了最大可读下标m_maximumReadIndex表征可读准确数据的最大下标。(为什么不是先执行step2,再执行step1呢?,请大家思考)至此,就介绍了Gaussdb(DWS)无锁队列所有的成员变量和方法,函数CountToIndex即就是根据m_indexCompare计算访问数据的正确下标。二、Gaussdb(DWS)无锁队列方法实现无锁队列的实现,就是队列+CAS操作,本章将介绍Gaussdb(DWS)无锁队列的Push和Pop操作(1)PushPush()操作的执行逻辑如下图所示,首先获取当前最新的写下标和读下标,然后根据读写下标判断队列是否已满,判断条件为:写下标向后移动一位为读下标(由于Gaussdb(DWS)下标超出size后不做转换,所以读下标需要+size大小),有人一定会困惑,此时当前写下标不是还有一个空闲位置吗?但是由于当读下标等于写下标时,即可能代表队列空,也可能代表队列满,具有二义性。所以规定写下标向后移动一位为读下标代表满队列,此时已经不可以写了,因此,遍历队列中的所有元素,其实是遍历size-1次队列元素。若队列未满,则首先更新写下标,这样别的线程就不会和本线程写数据冲突,更新失败,需要重新获取读/写下标(已经被其他线程更新)再次更新写下标;写下标更新完成后,则将需要push的数据放置在数组中;紧接着,将辅助数组的对应位置置1,表示数据已写入,但是最大可读下标未更新。然后利用CAS操作对最大可读下标进行更新,这里多线程存在竞争,更新失败说明该线程A使用的写下标前还有线程B未更新最大可读下标,通常情况下,线程A需要等待线程B将最大可读下标更新后再更新,这里线程A的动作是什么也不干进行等待,降低了效率,但是由于Gaussdb(DWS)无锁队列有辅助数组标明了数据已经放置在数组中,因此线程A可以直接退出,由线程B后续检查后一位置的m_Written值,对最大可读下标进行更行。更新完毕后,将m_Written置0,表示最大可读下标更新完成。(1)PopPop()操作的执行逻辑如下图所示,首先获取当前最新的写下标和读下标,然后根据读写下标判断队列是否为空,判断条件之前已经分析过,读写下标相等时认为队列空。然后利用CAS操作修改读下标,返回数据即可。最后,补充说明下函数CountToIndex的使用,他的使用场景为需要根据读下标访问数组元素时先行调用。
  • [集群&DWS] GaussDB(DWS)stream线程池设计(一)
    Stream算子作为SQL join操作时频繁发生的执行算子,负责CN节点GATHER数据,DN节点REDISTRIBUTE和BROACAST数据。大集群高并发场景下,Stream算子过多可能会导致通信的性能瓶颈,引起性能劣化(2000个stream同时启动,进程初始化耗时从ms级劣化到s级),因此需要尽可能减少Stream算子。但是在某些现场环境下,存在数据倾斜、join查询不包含必要分布键等客观情况,Stream算子无法有效减少,为多表join场景下的查询时延保障带来挑战。stream线程是临时线程,随query启动和退出,负责stream算子的执行,stream线程初始化和退出都会争抢锁等进程级资源,在stream线程个数无法进一步优化的场景下,需要设计有效方案以减少stream线程初始化和退出的时间代价,将进程初始化耗时稳定在ms级,保障数据库的确定性时延查询。Stream线程未池化时的执行过程可以描述为:线程初始化—>线程任务执行—>线程退出。Stream线程池的核心思想是等stream线程执行完计划任务,保留必要且可复用的线程信息,将线程放入线程池中。线程池化后线程执行过程如下图一所示,可以描述为:步骤一线程信息初始化—>步骤二线程待唤醒后轻量级初始化(query级初始化)—>步骤三线程任务执行—>步骤四线程清理,返回步骤二。当线程等待超时、超出线程池容量(最大stream线程个数)、异常时线程退出。上图中,池化后线程初始化(thread initialize)包含线程创建、创建相关内存上下文、信号处理函数注册、内存追踪信息初始化、初始化GUC选项等操作;池化后线程轻量级/查询级初始化(light initialize)包含恢复GUC参数、初始化BackendParams、重置GUC参数等操作。stream线程池采用无锁队列实现,其目的是为了高效管理线程的出/入池操作。定义结构体ThreadSlot保存线程池中每一个线程的信息,包含:线程状态、线程号、线程对应的database oid、线程执行所需的信息StreamProducer、线程唤醒所需的锁和条件变量。当线程还未被创建时,初始化一定数量的ThreadSlot数量以预留stream线程,这些ThreadSlot被保存在数组threadSlots中。当stream线程执行完毕,需要将stream线程放置到表征可复用线程的无锁队列,称之为idleRing;当线程因为超时、异常等原因不再复用,需要退出时,将stream线程对应的ThreadSlot放置到表征未创建线程的无锁队列,称之为emptyRing。idleRing的作用是为了快速获取并复用线程池中的线程,emptyRing的作用是快速获取一个未被使用的ThreadSlot结构,以创建一个新的stream线程。由于stream线程的初始化信息和database是强相关的,如果不保留database相关的信息,那么线程初始化的时间代价仍然较高,所以线程池中的线程复用时,需要满足database信息匹配。对于设计线程池而言,每一个database都应该对应一个idleRing。综上所述,基于无锁队列的stream线程池设计如下所示:从上图可以看出,一个线程池包含预留stream线程结构的threadSlots、一个表征未创建线程的无锁队列emptyRing和表征可复用线程的无锁队列idleRing,由于每个database对应一个idleRing,因此多个idleRing被组织为链表结构。本文介绍了GaussDB(DWS)线程池总体设计思想和实现方案,更细节的状态转移和接口将在后续介绍。
  • [集群&DWS] GaussDB(DWS)stream线程池设计(四)
    一、 stream线程池实现设计Stream线程池中stream线程整体执行流程如下图所示:Stream线程池中stream线程整体执行流程如下图所示:图一、stream线程执行流程其中,GUC参数的设置逻辑为图八所示:父线程保存自己的guc_variables在syncGucVariables中,syncGucVariables是需要传递给stream的结构用以保证父子线程guc参数的一致。然后父线程在初始化streamProducer时将syncGucVariables保存在该结构中传递。Stream线程根据streamProducer初始化自己的syncGucVariables变量,首先reset所有的guc变量,然后根据syncGucVariables修正自己的variables。图二、GUC参数修改执行计划时,stream线程需要可视父线程的事务信息,因此在ExecuteStreamPlan中,stream线程池前后事务的继承和提交逻辑为:图三、事务的继承和提交逻辑二、测试场景【场景一】集群基础行为场景——建立多数据库场景Create database ***;(建立多库)分别执行带stream算子的查询;例:create table test_01(c1 int, c2 int)with(orientation=column) distribute by hash(c1);insert into test_01 select generate_series(1,100), generate_series(1,100);analyze test_01;select * from test_01 a, test_01 b, test_01 c, test_01 d, test_01 e, test_01 f where a.c2 =b.c2 and c.c2 = d.c2 and e.c2=f.c2 limit 100;查询结束,查pgxc_thread_wait_status看DN节点:预期stream线程状态为wait thread cond。且多database之间stream线程不复用。【场景二】集群基础行为场景——建立多用户场景Create user ***;(建立多用户)分别执行带stream算子的查询;(参考场景一示例)查询结束,查pgxc_thread_wait_status看DN节点:预期stream线程状态为wait thread cond。且多user之间stream线程可以复用。例:用户一执行完查询,视图中显示共有四个stream线程在线程池,用户二执行同样查询返回正确结果,视图中的stream线程个数不变,且线程号也是一致的,则说明复用。【场景三】集群基础行为场景——线程清理场景调整guc参数max_stream_pool的值,观测是否生效;预期:当设置max_stream_pool小于当前idle线程个数,支持线程个数实时减少;当设置max_stream_pool大于当前idle线程个数,将由业务驱动线程个数的增加,但是不会超过max_stream_pool。执行clean connection(ALL force),查看stream线程是否被清理;预期:该database的stream线程被完全清理。执行drop database命令,查看stream线程是否被清理;预期:该database的stream线程被完全清理。【场景4】集群基础行为场景——stream线程池性能测试分别测试50、100、300、500、1000并发下,每并发100查询的QPS,对比stream线程池性能变化。单语句stream多,并发小;单语句stream少,并发大两种场景。1.1.2  扩展场景【场景一】集群故障场景Kill dn 节点,节点被重新拉起,继续并发执行查询;集群规模性重启,并发执行查询;故障场景测试用例下,并发执行带stream算子的查询。预期:结合pgxc_thread_wait_status视图,并发查询能否继续执行,是否正常报错,无core无hang。【场景二】集群特殊业务场景模拟高并发查询,观察stream线程情况;震荡模型下,观察stream线程大规模快速建立、清理的场景是否正常运行;多用户共享一个database,多用户共享多个database,观察stream线程复用情况。预期:stream线程池正常发挥作用,不同database线程不复用,复用与user无关。
  • [集群&DWS] GaussDB(DWS)stream线程池设计(三)
    一、外部接口1.1 新增接口介绍stream线程池新增GUC参数max_stream_pool,用于控制线程池中最大可用线程的个数。默认值:65535(整数最大值)取值范围:-1~INT_MAX;-1表示不开启stream线程池max_stream_pool支持reload更新,更新规则:设置max_stream_pool小于当前可用线程个数,支持线程个数实时减少;当设置max_stream_pool大于当前idle线程个数,将由业务驱动线程个数的增加。1.2  修改接口介绍stream线程池修改pg_thread_wait_status视图中等待状态,新增wait stream cond状态表示线程池中等待被复用的stream线程,DN上可见。图一 pg_thread_wait_status视图pg_comm_status视图补充三个变量:复用stream线程的次数、使用stream线程的次数(含复用和create)、stream线程并发个数的历史峰值。performance显示是否复用线程,拿连接唤醒等开销性能数据,超出1ms输出到performance。二、  内部接口Stream线程池提供了三种接口以管理stream线程,除外部接口外,内部的接口包含被动清理接口和超时清理接口。如图六所示:图二、stream线程管理接口被动清理接口CleanStreamPool负责根据database清理stream线程,其提供三种清理模式:CLEAN_QUARTER_FREE:清理四分之一idle线程;CLEAN_ALL_FREE:清理所有idle线程;CLEAN_ALL_FORCE:清理所有dababase相关的stream线程。该接口由内部函数调用,例如dropdb、clean connection等。自动清理接口负责自行定时清理超时未使用的idle线程,其不开放给任何调用,此处接口可以理解为线程的一种管理方式。清理逻辑:max_stream_pool阈值不作强约束,尽量缓存线程,通过空闲连接利用率决定回收间隔,空闲率高时快速回收,反之慢速回收。三、接口实现逻辑接口的实现逻辑:3.1、被动清理接口int StreamThreadPool::CleanStreamPool(const char *dbName, cleanOption cleanMode)入参:dbName cleanMode返回值:清理的个数CleanMode可选:CLEAN_QUARTER_FREE、CLEAN_ALL_FREE、CLEAN_ALL_FORCE实现流程如下: 图三、CleanStreamPool接口执行逻辑3.2、超时清理接口超时清理接口是指idle状态的slot超时未被使用而自动清理,其实现在图七的wait()模块,对应StreamThreadPool::Wait()函数,其实现流程如图十一所示:可以看出当线程超时需要清理时,实际对应的操作为:将slot的状态从IDLE置为HOLD,表示该slot已被预占作为退出线程。随后返回false,函数退出。根据图七所示,wait()返回false后,stream线程会退出,调用回调函数StreamQuitAndClean(),将slot归还emptyRing,其执行逻辑如图十二所示。由图十一和十二可以看出,当slot超时退出,此时slot的状态仅仅被修改为了EXIT,而其所处的位置仍然在idleRing中,slot此时在idleRing中等待被pop,pop后发现状为EXIT,会将slot放回置empty状态中,pop的逻辑如图十二所示。  图四、wait()执行逻辑图五、slot异常退出执行逻辑  图六、slot获取执行逻辑外部接口stream线程池可通过reload参数max_stream_pool,用于控制线程池中最大可用线程的个数。具体流程如下:图七、外部guc set逻辑
  • [集群&DWS] GaussDB(DWS)stream线程池设计(二)
    一、数据结构设计stream线程池采用无锁队列实现,其目的是为了高效管理线程的出/入池操作。定义结构体ThreadSlot保存线程池中每一个线程的信息,包含:线程状态、线程号、线程对应的database oid、线程执行所需的信息StreamProducer,StreamProducer是父线程向子线程传递的唯一结构、线程唤醒所需的锁和条件变量。如下所示:typedef struct{       int status;       uint32 idx;       ThreadId tid;       Oid dbOid;       StreamProducer* streamObj;       pthread_mutex_t m_mutex;       pthread_cond_t m_cond;} ThreadSlot;定义结构体StreamThreadPool表征线程池,结构如下所示:class StreamThreadPool: public BaseObject{public:    StreamThreadPool();    void Init(int num);                             // StreamThreadPool init       int Call(StreamProducer* obj);                   // 获取idle线程 或 create 新线程    bool Wait();                                  // idle线程等待唤醒或者超时退出    ThreadSlot* GetLocalSlot();                     // get streamThreadSlot    void SetLocalSlot(int slotIdx);                   // set streamThreadSlot    StreamPool* GetLocalPool();                    // 获取streamDBPool 或 新建一个    ThreadSlot* PopSlot();                         // 从idleRing/emptyRing获取一slot    void PushToEmpty(ThreadSlot* slot);             // 将slot直接放入emptyRing    void PushToIdle(StreamPool* pool, ThreadSlot* slot); //将slot直接放入idleRing    void LocalPushToIdle();                        // 根据状态,将slot放入idleRing    void LocalPushToEmpty();                      // 根据状态,将slot放入emptyRing    int CleanStreamPool(const char *dbName, cleanOption cleanMode); //根据db信息清线程    void CleanInAllStreamPool(int desNum);           // 调整线程池中stream线程个数    int GetStreamNum();                           // 获取线程池中stream线程个数    bool Release();                                // 判断超时线程是否需要清理    bool TimeoutClean();                                // 清理超时idle线程private:    int size;    ThreadSlot* threadSlots;    ArrayLockFreeQueue emptyRing;    StreamPool* PoolListHead;}结构体中,size表示线程池中拟预留的ThreadSlot个数,ThreadSlot被保存在threadSlots数组中;无锁队列emptyRing用来保存未创建线程的ThreadSlot,对应地,idleRing用来保存空闲的已创建stream线程的ThreadSlot。由于stream线程的初始化信息和database是强相关的,如果不保留database相关的信息,那么线程初始化的时间代价仍然较高,所以线程池中的线程复用时,需要满足database信息匹配,所以一个emptyRing和一个database相匹配,保存在链表PoolListHead中,链表元素StreamPool对应结构如下:typedef struct StreamPool{       Oid dbOid;       ArrayLockFreeQueue idleRing;       struct StreamPool* next;} StreamPool;线程池中各结构间组织的直观图如下所示:图一、基于无锁队列的线程池结构上图中threadSlots可以放在idleRing(蓝色)、emptyRing(绿色)和运行空间(黄色)中,具体在下节介绍。二、stream线程状态转移DFA设计每一个记录线程信息的结构ThreadSlot中都保存了线程当前的状态status,记录线程状态的目的是为了保障线程执行过程的有序控制,也可以通过状态的互斥避免threadSlot不会被两个线程同时使用。stream线程状态转移用确定性有限状态机(DFA,definite automata)表征,共包含4个状态:STREAM_SLOT_EXIT、STREAM_SLOT_IDLE、STREAM_SLOT_HOLD和STREAM_SLOT_RUN状态。其物理含义如下:STREAM_SLOT_EXIT:线程退出状态,表示线程未被创建或线程已退出;STREAM_SLOT_IDLE:线程可复用状态,表示线程在idleRing中,可以被复用;STREAM_SLOT_HOLD:线程临时独占状态,表示线程在做进入下一个状态的准备工作;STREAM_SLOT_RUN:线程运行状态,表示线程正在执行任务。状态间转移条件如下所示,图中粗箭头表示状态机主循环部分:图二、stream线程状态转移与状态对应的,是slot所处的位置,slot所处的位置有三处,分别是idleRing、emptyRing和运行空间,slot从无锁队列中拿出,运行时所处的位置,我们称之为运行空间。各状态所处的位置情况如下所示:STREAM_SLOT_EXIT:idleRing(idle线程超时)、emptyRing(初始化或者FATAL);STREAM_SLOT_IDLE:idleRingSTREAM_SLOT_HOLD:运行空间(从无锁队列中取出)、idleRing(idle线程超时或中断);STREAM_SLOT_RUN:运行空间。Slot的位置变化和状态转移的关系如下,图中粗箭头表示状态机主循环部分:图三、stream线程状态转移和slot位置的转移关系根据各状态所处的位置情况,从idleRing中取出的slot可能有三种状态:EXIT、IDLE、HOLD。当取出IDLE状态的slot,说明线程可复用;当取出EXIT状态的slot,说明线程已退出,此时需要将slot转存到emptyRing;当取出HOLD状态,说明线程正在被使用,此时需要放回idleRing。EmptyRing中slot的状态只能是EXIT,运行空间中slot的状态要么是HOLD(刚取出还未运行),要么是RUN(正在运行),不再赘述。
  • [集群&DWS] GaussDB(DWS)通信库libpq重构介绍(一)
    通信库libpq重构解决两点问题:针对大并发短查询场景下,CN与DN建立/复用/归还/释放连接时争抢全局锁PoolerLock,造成的性能问题进行Pooler锁优化;针对当前代码仓中libpq代码冗余度高、复用性差、可维护性差、可读性差问题,对libpq代码进行整合以符合clean code要求。在现网多个局点,在高并发短查询场景下,CN与DN建连时等待PoolerLock锁耗时,导致环境CPU资源无法进一步使用,严重影响此场景下查询性能。Poller锁优化旨在高并发短查询场景下优化CN与DN建连性能,pooler建连不再制约环境CPU资源使用,全面提升高并发短查询性能。libpq代码合并旨在提高libpq代码的复用性、可读性、可维护性,符合clean code要求。一、pooler锁优化Pooler连接总体分两大类:一类空闲连接,存储在某个DBPool的NodePool中;一类是使用中的连接,存储在每个线程独立的poolAgent数据结构中,因为遍历需求,所有线程的poolAgent指针又存储在全局的poolAgents数组中。Pooler锁优化总体方案是使用无锁ringbuffer存储空闲连接和可用的agent index,实现并发空闲连接的存取不需加锁,并发agent index的存取不需加锁。针对全局的DBPool链表和NodePool Hash表,仅在新增时加互斥锁,正常查找时加共享锁。针对CleanConnection、pg_pooler_status、pv_total_memory_detail、pgxc_pool_reload、pg_pool_validate需遍历NodePool中所有空闲连接的场景,使用ringbuffer的pop接口将连接取到本线程,操作完后再push回ringbuffer,实现无锁遍历。针对以上视图接口需遍历poolAgents数组中所有正在使用连接的场景,在Agents数组中增加status标志位,做短暂的读保护。二、libpq代码合并libpq代码从功能角度可以划分为3部分:基础公共模块通信建连、断连应用层定义的通信协议除去通信协议部分,基础公共模块、通信建连及断连模块的代码实现大体相同,完全可以合并成1份,以动态库的形式提供给其他模块使用,对外只提供必要的通信建连、断连等接口,隐藏内部具体实现。首先梳理并确定3个模块公用的建连、断连流程,如图 3‑1、图 3‑2所示。在此基础上,确定建连、断连流程的内部接口与外部接口、使用的数据结构,外部接口声明、结构体前置声明位于libpq-fe.h头文件中,内部接口声明、结构体定义位于libpq-int.h头文件中。其他模块使用libpq时,只需要libpq-fe.h头文件与libpq.so,不需要关注libpq内部实现。合并过程中,对于相似的代码,提炼出公共函数,提高代码的复用性、可读性;对于差异较大的代码,采用注册回调函数的方式(startupPacket构建、result成员内存释放),保留差异逻辑。合并完成后,3个模块各自目录下的冗余代码或者冗余文件都被删除。合并以后的代码实现存放在interfaces/libpq目录下,头文件存放在include/libpq目录下。 图 2‑1 公用建连流程图 2‑2 公用断连流程图 2‑3 libpq合并文件结构libpq重构的详细设计,将在后面具体介绍。
  • [集群&DWS] GaussDB单节点集群安装方法
    GaussDB的单机集群安装可以方便开发人员快速定位问题,有助于初学者了解产品使用,本文将介绍两种简单的单机集群安装的方法。方法一:利用fastcheck起集群1、下载源码并编译。2、执行 make fastcheck p=***** runtest=no注意:在执行make fastcheck命令前需要指定GAUSSHOME的路径方法二:利用执行脚本起集群1、首先,需要确保环境变量是正确的,环境变量配置文件可按照自己的代码路径更改即可:test -s ~/.alias && . ~/.alias || true export LESS=eFR export CODE_BASE=/data1/……/gaussdb export GAUSSHOME=$CODE_BASE/mppdb_temp_install export LD_LIBRARY_PATH=/gs_tools/lib:/gs_tools/lib64:/gs_tools/Python-2.7.15/lib:/gs_tools/gcc-8.3.0/lib:/gs_tools/gcc-8.3.0/lib64:/gs_3rd/local/release/lib export PATH=$GAUSSHOME/bin:$PATH export LD_LIBRARY_PATH=$GAUSSHOME/lib:$LD_LIBRARY_PATH2、将mpp_deploy.py脚本放入gaussdb/mppdb_temp_install路径下,修改port(仅此一项)。3、在gaussdb/mppdb_temp_install路径下,执行./mpp_deploy.py --help查看相关使用方法, 通常,安装的命令:./mpp_deploy.py -c 2 -d 4 (2 4分别为CN、DN的个数)4、常见问题:常见问题一:安装后gsql连不上解决方案:export下列环境变量export PGHOST=/……/build/regress/tmp export LD_LIBRARY_PATH=/……/gaussdb/mppdb_temp_install/lib:$LD_LIBRARY_PATH export GAUSSHOME=/……/gaussdb/mppdb_temp_install常见问题二: Installation node group is not defined in current cluster解决方案:上次关集群太暴力,需要重新卸载安装mpp_deploy.py脚本见附件,删除txt后缀。
总条数:2746 到第 页
上滑加载中