前言
元数据不一致问题往往会导致业务上的报错,在现网环境中,“元数据不一致”一词常常让客户、一线、二线甚至研发兄弟们谈虎色变,但是很多人其实并不清楚“元数据不一致”到底是什么,该如何处理。元数据不一致真的有那么可怕吗?接下来本文将以一种常见的元数据不一致场景为例,带领读者揭开元数据不一致的神秘面纱
声明
1.此系列博文的初衷是构建更加强大的生态社区,提升大家对GaussDB(DWS)的认识,在现网运维过程中减少工具人的角色,因此只讲解元数据不一致的原理和应急方法,不讨论“是否为产品质量问题”等敏感话题
2.此系列博文所有数据内容均为实验环境构造,非客户数据,不涉及客户隐私
3.此系列属于技术原理类博文,不建议客户、一线、二线在读完此系列之后直接处理生产环境的元数据不一致问题
什么是元数据不一致
在执行DDL操作时,会将表的元信息记录在系统表中,例如,执行一个简单的create table test(a int,b int)操作,会在pg_class中新增一条数据来记录表信息,在pg_type中新增一条数据来记录与这张表对应的存储数据类型,在pg_depend中新增一条数据来记录pg_class中的数据和pg_type中的数据之间的依赖关系,在pg_attribute中新增九条数据来记录字段a、字段b以及表的7个默认隐藏字段。
当系统表之间的信息不匹配时,就出现了元数据不一致的场景,例如,当pg_class中记录还在,而pg_attribute中对应记录不存在时,访问该表就会报错。
常见的系统表之间依赖关系如下图
一种经典问题场景
元数据不一致的场景有很多,这里我们先看一种比较经典的场景:Catalog is missing n attribute(s) for relid xxx
(这里在测试环境上已经构造好了问题场景,构造方法会附在博文最后)
测试环境:3节点,每个节点1个cn,4个主dn
集群拓扑:
问题现象:在cn5001上查询select * from public.testmissingattr时报错:dn_6001_6002: Catalog is missing 2 attribute(s) for relid 62439689
问题分析:
1.首先分析报错信息,Catalog is missing n attribute(s) for relid xxx的直接报错原因,是pg_class中该表的relnatts字段与pg_attribute中该表非隐藏列的记录数不匹配
pg_attribute中attnum大于0的记录表示非隐藏列
2.根据报错信息中的detail,我们可以确定在dn6001上,pg_class中该表的relnatts字段值为4,而pg_attribute中缺少了attnum为1和3的字段记录;该表在dn6001上pg_class中的oid为62439689
3.到dn6001上确认问题现象(如何连接某个dn不在此赘述)
select oid,* from pg_class where oid = 62439689;
可以看到在pg_class中relnatts的值确实是4
select * from pg_attribute where attrelid = 62439689;
可以看到pg_attribute中确实缺少了attnum为1和3的列
注意,当发生元数据不一致时,一定要先判断不一致时到底哪一侧是正确的,在此场景中就是要判断是pg_class中的relnatts值记录错误,还是pg_attribute中记录缺失
判断的依据,往往是根据cn或其他正常未报错的dn来判断,或从业务侧进行判断
(由于pg_attribute中一张表的attnum一定是连续的,因此根据上图可以快速判断是pg_attribute中缺少记录,但是如果上图存在的两条记录attnum为1,2而不是2,4,就要根据其他cn或dn的表定义来进行判断)
在cn查看表定义,确认是4个字段,pg_class中记录正确,pg_attribute中缺失字段a和c
3.由于系统表的查询都是优先从syscache中扫描,而syscache都是带索引扫描,因此我们需要将索引关掉,再查询一遍,来确认是仅有索引中缺少这两条记录,还是pg_attribute中确实缺少这两条记录。
关闭索引再次查询:
start transaction read only;
set enable_indexscan=off;
set enable_bitmapscan=off;
explain select * from pg_attribute where attrelid = 62439689;
确认执行计划是走seq scan之后,再次查询
select * from pg_attribute where attrelid = 62439689;
rollback;
再次查询的结果,可以分为两种情况讨论:
- 关闭索引后,查询正常
这说明数据本身没有缺失,只是索引文件中缺失了,因此只要重建索引即可修复
在dn上对系统表重建索引:
start transaction read write;
reindex table pg_attribute;
commit;
需要注意的是,在cn上不能直接对系统表重建索引,会报错cannot PREPARE a transaction that modified relation mapping,因此如果是cn上需要重建系统表的索引,就要对系统表做vacuum full进行修复
vacuum full pg_attribute;
- 关闭索引后,查询结果不变
这说明是pg_attribute中确实缺少了记录,此时我们优先查到对应的备dn(在此案例中为6002)是否正常,如果备dn正常,则停止集群,从备dn拷贝对应文件到主dn
如何找到表的对应文件,不在这里赘述,可参考博文:GaussDB(DWS)磁盘使用率相关问题定位指南https://bbs.huaweicloud.com/blogs/174806
如果备dn与主dn查询结果一致,则需要手动恢复一致性,此场景一般根据正常cn或dn的pg_attribute记录,将缺失的记录在dn6001补齐
恢复步骤:
a.在正常的cn或dn查询该表在pg_attribute中attnum为1和3的字段
在cn5001执行:
select oid,* from pg_class where relname = 'testmissingattr' and relnamespace = (select oid from pg_namespace where nspname = 'public'); #同一张表在不同的cn和dn上oid都是不一样的,要重新查询
select * from pg_attribute where attrelid = 574785;
查到缺失的两条记录,根据此信息在dn6001上补齐
在dn6001执行
start transaction read write;
insert into pg_attribute values (62439689,'a',23,-1,4,1,0,-1,-1,'t','p','i','f','f','f','t',127,0,0,NULL,NULL,NULL,NULL);
insert into pg_attribute values (62439689,'c',23,-1,4,3,0,-1,-1,'t','p','i','f','f','f','t',127,0,0,NULL,NULL,NULL,NULL);
select * from pg_attribute where attrelid = 62439689;
确认已经补齐
select * from public.testmissingattr limit 1;
查询该表不再报错
commit;
提交事务使修复生效
到这里,此场景已经修复完毕,表可以继续正常使用
注意事项:
- 所有操作都放在事务中做,执行结果不符合预期之后及时回滚
- 找对cn/dn
- 系统表字段不要混淆
总结
本文以一个经典的元数据不一致场景为例,介绍了元数据不一致的原理、定位及修复,下期博文将继续介绍元数据不一致的其他经典场景,帮助大家更好的理解元数据不一致现象
附:场景构造
在cn执行
create table public.testmissingattr(a int,b int,c int,d int);
在任一dn执行
start transaction read write;
delete from pg_attribute where attrelid = (select oid from pg_class where relname = 'testmissingattr' and relnamespace = (select oid from pg_namespace where nspname = 'public')) and attname = 'a';
delete from pg_attribute where attrelid = (select oid from pg_class where relname = 'testmissingattr' and relnamespace = (select oid from pg_namespace where nspname = 'public')) and attname = 'c';
commit;