首页 » PostgreSQL/GaussDB » 案例:openGauss/postgreSQL 数据库手动清理膨胀Heap Bloat (dead tup)

案例:openGauss/postgreSQL 数据库手动清理膨胀Heap Bloat (dead tup)

前段时间整理过一篇《有哪些技术可以减少PostgreSQL/openGauss数据库的存储空间?》,记录过postgresql系数据库出现的膨胀表(索引也一样)可能会导致数据库空间浪费,在openGauss中发现存在一个现象,比如对一张几千万行的table做过千万级大事务更新或平时更新比例较多时,autovacuum的清理并不理想,导致出现几十倍的空间膨胀,记录一则处理案例。

背景:
from Pavan Deolasee PGCon 2008

PostgreSQL uses MVCC (Multi Version Concurrency Control) for transaction semantics
The good things:
Readers don’t wait for writers
Writer doesn’t wait for readers
Highly concurrent access and no locking overhead
The bad things:
Multiple versions of a row are created
The older, dead versions can not be easily removed because indexes don’t have visibility information
Maintenance overhead to reduce table/index bloat

查看TOP table

从pg_stat_user_tables或pg_stat_all_tables的n_dead_tup统计信息排序,工,或使用pg_stat_get_dead_tuples funtion。更多在手册中可以找到。

SELECT pg_stat_get_live_tuples(c.oid) AS n_live_tup
     , pg_stat_get_dead_tuples(c.oid) AS n_dead_tup
FROM   pg_class c;

 SELECT relid,schemaname,relname,n_live_tup,n_dead_tup,last_autovacuum,last_analyze,last_data_changed
   FROM pg_stat_all_tables
   WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
   order by n_dead_tup desc limit 10 ;

n_dead_tup :dead tup是已删除的行,dead tup可以通过 VACUUM FULL(或者如果幸运的话,普通 VACUUM)或类似的表操作来删除,从而相应地缩小物理大小。每当删除或更新一行时,旧行版本对于该事务提交后开始的所有其他事务都变得不可见 ( 使用默认的 READ COMMITTED 事务隔离)。 一旦不再有未提交的旧事务,该行就完全死亡。 这是 PostgreSQL 的 MVCC 模型处理并发所必需的。

TOAST对象

 schemaname |         relname          | n_dead_tup 
------------+--------------------------+------------
 pg_toast   | pg_toast_part_1806609843 | 2959872769
 pg_toast   | pg_toast_part_1806609840 | 2034090877
 pg_toast   | pg_toast_1806609253      |   76601524
 pg_toast   | pg_toast_1806603161      |   44095962
 sssssss    | xxxxxxxxxxxxxxx          |   16379804
 pg_toast   | pg_toast_1806604865      |   10065268

select relname,relfilenode,reltoastrelid from pg_class where oid=1806609253;

select relname,parttype,parentid,rangenum,partstrategy from pg_partition where oid=1806609843;

这里又涉及了TOAST对象,TOAST 是“ The Oversized-Attribute Storage Technique ”的缩写, relname的命名是pg_toast_<OID>,如果是分区表对应的是pg_toast_part_<OID>,PG 不允许一行数据跨页(BLOCK)存储,那么对于超长的行数据(如超过默认的8KB block size),PG 就会启动 TOAST ,具体就是采用压缩和切片的方式。如果启用了切片,实际数据存储在另一张系统表的多个行中,这张表就叫 TOAST 表,这种存储方式叫行外存储.

分区表大小

select relname,pg_size_pretty(pg_partition_size('anbob_com_table',relname)),parentid 
from  pg_partition 
where parentid=1806609735 and parttype='p' order by 1;
        relname         | pg_size_pretty |  parentid  
------------------------+----------------+------------
 anbob_com_table_201901 | 13 GB          | 1806609735
 anbob_com_table_201904 | 13 GB          | 1806609735
 anbob_com_table_201907 | 14 GB          | 1806609735
 anbob_com_table_201910 | 11 GB          | 1806609735
 anbob_com_table_202001 | 5623 MB        | 1806609735
 anbob_com_table_202004 | 10 GB          | 1806609735
 anbob_com_table_202007 | 11 GB          | 1806609735
 anbob_com_table_202010 | 8572 MB        | 1806609735
 anbob_com_table_202101 | 6607 MB        | 1806609735
 anbob_com_table_202104 | 9830 MB        | 1806609735
 anbob_com_table_202107 | 11 GB          | 1806609735
 anbob_com_table_202110 | 9228 MB        | 1806609735
 anbob_com_table_202201 | 5831 MB        | 1806609735
 anbob_com_table_202204 | 9721 MB        | 1806609735
 anbob_com_table_202207 | 9428 MB        | 1806609735
 anbob_com_table_202210 | 8163 MB        | 1806609735
 anbob_com_table_202301 | 7432 MB        | 1806609735
 anbob_com_table_202304 | 429 GB         | 1806609735  <<<<<<
 anbob_com_table_202307 | 550 GB         | 1806609735  <<<<<<
(19 rows)

Note:
可见202304分区是我们迁移到openGauss系后的时间,后面膨胀非常严重。相比前几个月大了近40多倍,可以count有效记录并没有业务量级的增长。

auto vacuum日志

2023-09-13 14:38:40 CST [47987247023872]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=65015502.3849,xid=0  0 [VACUUM] LOG:  normally vacuum rel "anbob_com.anbob_com_table" freeze 2000000000 OldestXmin 622657833, FreezeLimit 3, freezeTableLimit 3
2023-09-13 14:39:02 CST [47987247023872]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=65015502.3849,xid=0  0 [VACUUM] LOG:  "anbob_com_table": found 0 removable, 0 nonremovable row versions in 0 out of 276268 pages
2023-09-13 14:39:02 CST [47987247023872]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=65015502.3849,xid=0  0 [BACKEND] LOG:  automatic vacuum of table "anbob_com.anbob_com_table": could not (re)acquire exclusive lock for truncate scan
2023-09-13 14:39:02 CST [47987247023872]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=65015502.3849,xid=0  0 [BACKEND] LOG:  automatic vacuum of table "anbob_com.anbob_com_table": index scans: 0
2023-09-13 14:39:05 CST [47994093831936]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=650158d6.3848,xid=0  0 [VACUUM] LOG:  normally vacuum rel "anbob_com.anbob_com_table" freeze 2000000000 OldestXmin 622657833, FreezeLimit 3, freezeTableLimit 3

2023-09-13 14:43:30 CST [47994093831936]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=650158d6.3848,xid=0  0 [VACUUM] LOG:  "anbob_com_table": found 2475 removable, 251395 nonremovable row versions in 76579 out of 278221 pages
2023-09-13 14:43:34 CST [47994093831936]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=650158d6.3848,xid=0  0 [BACKEND] LOG:  automatic vacuum of table "anbob_com.anbob_com_table": could not (re)acquire exclusive lock for truncate scan
2023-09-13 14:43:34 CST [47994093831936]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=650158d6.3848,xid=0  0 [BACKEND] LOG:  automatic vacuum of table "anbob_com.anbob_com_table": index scans: 1
2023-09-13 14:43:34 CST [47994093831936]: user=anbob,db=anbob_com,app=dn_6001,client=localhost,sid=650158d6.3848,xid=0  0 [VACUUM] LOG:  normally vacuum rel "anbob_com.anbob_com_table" freeze 2000000000 OldestXmin 622684051, FreezeLimit 3, freezeTableLimit 3

控制文件

$ pg_controldata 
pg_control version number:            923
Catalog version number:               201611171
Database system identifier:           13553184229945870278
Database cluster state:               in archive recovery
pg_control last modified:             Wed 13 Sep 2023 02:59:28 PM CST
Latest checkpoint location:           2298/BCF7FA88
Prior checkpoint location:            2298/BBBF7C40
Latest checkpoint's REDO location:    2298/BBBDFE28
Latest checkpoint's TimeLineID:       1
Latest checkpoint's full_page_writes: off
Latest checkpoint's NextXID:          622807106
Latest checkpoint's NextOID:          3423305728
Latest checkpoint's NextMultiXactId:  177
Latest checkpoint's NextMultiOffset:  351
Latest checkpoint's oldestXID:        13017
Latest checkpoint's oldestXID's DB:   15935
Latest checkpoint's oldestActiveXID:  622804693
Latest checkpoint's remove lsn:          0/2294BA
Time of latest checkpoint:            Wed 13 Sep 2023 02:58:38 PM CST
Minimum recovery ending location:     2298/C3905A98
Backup start location:                0/0
Backup end location:                  0/0
End-of-backup record required:        no
Current wal_level setting:            logical
Current max_connections setting:      3050
Current max_prepared_xacts setting:   3000
Current max_locks_per_xact setting:   256
Maximum data alignment:               8
Database block size:                  8192
Blocks per segment of large relation: 131072
WAL block size:                       8192
Bytes per WAL segment:                16777216
Maximum length of identifiers:        64
Maximum columns in an index:          32
Maximum size of a TOAST chunk:        1996
Date/time type storage:               64-bit integers
Float4 argument passing:              by value
Float8 argument passing:              by value
Database system TimeLine:             30

交换分区

create table anbob_com.anbob_com_table_p07 as select * from anbob_com.anbob_com_table partition (anbob_com_table_202307)

-- add not null 
-- create index same as orginal table.

alter table anbob_com.anbob_com_table exchange partition (anbob_com_table_202304) with table anbob_com.anbob_com_table_p04 without validation  ;

\dt+ anbob_com.anbob_com_table_p07
                                             List of relations
 Schema  |        Name         | Type  |  Owner  |  Size  |             Storage              | Description 
---------+---------------------+-------+---------+--------+----------------------------------+-------------
 anbob_com | anbob_com_table_p07 | table | anbob_com | 552 GB | {orientation=row,compression=no} | 

truncate table anbob_com.anbob_com_table_p07;

drop table anbob_com.anbob_com_table_p07;

Note:
确认历史月份分区为静态数据后,可以使用交换分区的方式收缩物理大小,因为vacuum无法收回,vacuum full(类似oracle的alte table move)同样无法online要加锁影响业务并且需要一倍的空间放临时表,对于当前在线业务且空间紧张,交换分区优选。但交换分区的alter table需要排他锁,如果有堵塞用户可以临时kill。

select pg_cancel_backend(locker_pid) from dba.ase where locked_query like 'alter table anbob_com.anbob_com_table%';

注意: 当表truncate ,drop 都e有可能不会在OS 层释放空间存储,trunate还可能找不到文件句柄。 mogdb是线程模式,在主进程下可以找deleted 句柄,但无法释放, 需要重启DB,或等使用的线程退出。

延迟备库
以上操作注意做好备份,确认在数据一致后再trucate table或drop table. 我们有配置延迟同步备库,可以防范误操作,但是如果备库意外crash,启动备库里如果使用gs_ctl start -D 没有指定database role, 会直接启动数据库到primary role激活, failover无法再回退,记住增加 -M standby,这是一个很危险的涉及, postgresql不会出现,因为它是有配置文件静态. 控制文件显示的不一定是replay最新的读的xlog,建议使用函数查询pg_last_xlog_replay_location或 pg_last_xact_replay_timestamp(). 对于延迟备库无法指定要应用到的截止位置, 如果做了误操作,需要使用延迟备库找回,需要有主库操作的时间或位置,采用提前move走xlog的方式,或使用select pg xlog replay_pause暂停应用,pg_xlog_replay_resume恢复已暂停的应用手动逐渐释放,但可能速度不可控.

对于备库应用到的位置应该是参考控制文件,控制文件修改有严格的校验,但我们有一个非常规的方法和工具,测试可以修改控制文件中的位置,后期等待验证恢复误激活(failover)的standby备库.

— over —

打赏

对不起,这篇文章暂时关闭评论。