走进元数据不一致系列(一)——Catalog is missing n attribute(s) for relid xxx

前言

元数据不一致问题往往会导致业务上的报错,在现网环境中,“元数据不一致”一词常常让客户、一线、二线甚至研发兄弟们谈虎色变,但是很多人其实并不清楚“元数据不一致”到底是什么,该如何处理。元数据不一致真的有那么可怕吗?接下来本文将以一种常见的元数据不一致场景为例,带领读者揭开元数据不一致的神秘面纱

声明

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节点,每个节点1cn4个主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_attributeattnum大于0的记录表示非隐藏列


2.根据报错信息中的detail,我们可以确定在dn6001上,pg_class中该表的relnatts字段值为4,而pg_attribute中缺少了attnum13的字段记录;该表在dn6001pg_class中的oid62439689

3.dn6001上确认问题现象(如何连接某个dn不在此赘述)

select oid,* from pg_class where oid = 62439689;


可以看到在pg_classrelnatts的值确实是4

select * from pg_attribute where attrelid = 62439689;


可以看到pg_attribute中确实缺少了attnum13的列

注意,当发生元数据不一致时,一定要先判断不一致时到底哪一侧是正确的,在此场景中就是要判断是pg_class中的relnatts值记录错误,还是pg_attribute中记录缺失

判断的依据,往往是根据cn或其他正常未报错的dn来判断,或从业务侧进行判断

(由于pg_attribute中一张表的attnum一定是连续的,因此根据上图可以快速判断是pg_attribute中缺少记录,但是如果上图存在的两条记录attnum1,2而不是2,4,就要根据其他cndn的表定义来进行判断)

cn查看表定义,确认是4个字段,pg_class中记录正确,pg_attribute中缺失字段ac


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查询结果一致,则需要手动恢复一致性,此场景一般根据正常cndnpg_attribute记录,将缺失的记录在dn6001补齐

 

恢复步骤:

a.在正常的cndn查询该表在pg_attributeattnum13的字段

cn5001执行:

select oid,* from pg_class where relname = 'testmissingattr' and relnamespace = (select oid from pg_namespace where nspname = 'public'); #同一张表在不同的cndnoid都是不一样的,要重新查询


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;

提交事务使修复生效

到这里,此场景已经修复完毕,表可以继续正常使用

注意事项:

  1. 所有操作都放在事务中做,执行结果不符合预期之后及时回滚
  2. 找对cn/dn
  3. 系统表字段不要混淆

总结

本文以一个经典的元数据不一致场景为例,介绍了元数据不一致的原理、定位及修复,下期博文将继续介绍元数据不一致的其他经典场景,帮助大家更好的理解元数据不一致现象

附:场景构造

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;

(完)