解析背景:
Greenplum AO列存,主要面向OLAP场景。表的列很多,分析其中少部分列,通过AO列存方式,大块的IO读,降低IO成本,提升分析速度。AO以前是appendonly,不支持update/delete,后来支持了,内部也改叫append optimized表。
一、AO列表存储设计
一张AO列存表主要涉及到表有pg_appendonly、pg_aoseg_<relid>、pg_aoblkdir_<relid>、pg_aovisimap_<relid>,这些表归属于pg_aoseg空间。
1. 表定义
pg_appendonly
存放该AO表额外的一些元数据,可以当作pg_class的扩展表。
字段名 | 类型 | 描述 |
relid | oid | 表的唯一标识,和pg_class中这张表对应的oid字段相同 |
blocksize | integer | ao表的块大小,默认32 k |
safefswritesize | integer | 最小安全写入数据块大小 |
compresslevel | smallint | 压缩率 |
checksum | boolean | 是否校验。创建一个块的对应校验码,读取时进行校验,保证数据的一致性 |
compresstype | name | 压缩类型 |
columnstore | boolean | 1:列存,0:行存 |
segrelid | oid | pg_aocsseg_<relid>表对应的oid |
blkdirrelid | oid | pg_aoblkdir_< relid >表对应的oid |
blkdiridxid | oid | pg_aoblkdir_< relid >表索引对应的oid |
visimaprelid | oid | pg_aovisimap_< relid >表对应的oid |
visimapidxid | oid | pg_aovisimap_< relid >表索引对应的oid |
参考链接: https://gpdb.docs.pivotal.io/6-11/ref_guide/system_catalogs/pg_appendonly.html
pg_aocsseg_< relid >
存放AO表所属的segment文件的元数据信息。
字段名 | 类型 | 描述 |
segno | integer | 段号 |
tupcount | bigint | 元组数 |
varblockcount | bigint | 元组占用的块数 |
vpinfo | bytea | Seg文件大小信息相关。如eof:文件有效的结尾;eof_uncompressd:没有压缩文件的文件结尾 |
modcount | bigint | 表被操作的次数 |
formatversion | smallint | 版本信息 |
state | smallint | 文件状态 |
参考链接:https://gpdb.docs.pivotal.io/6-11/ref_guide/gp_toolkit.html
pg_aoblkdir_<relid>
存放AO表的元组行号和文件块关系的元数据信息。
字段名 | 类型 | 描述 |
segno | integer | 段号 |
columngroup_no | integer | 列号 |
first_row_no | bigint | 元组首行号 |
minipage | bytea | 块页面摘要 |
pg_ aovisimap _<relid>
字段名 | 类型 | 描述 |
segno | integer | 段号 |
first_row_no | bigint | 元组首行号 |
visimap | visimap | 采用位图方式标记元组的删除和修改信息 |
2. 关键字段说明
AOTupleId
AOTupleId(TID)是元组的唯一标识,格式为(segfile#,row#)。数据结构如下:
结构定义 | 说明 |
typedef struct AOTupleId { uint16 bytes_0_1; uint16 bytes_2_3; uint16 bytes_4_5; } | 前7个bit定义segno 再用25个bit存放行号 剩余16个bit作为保留位 对于heap表,前32位含义blockno+rownum,唯一定位元组位置。为了保证兼容,AO表和heap表的所占用位数一致 最后两字节,heap表为offset且大于0;为了兼容,AO表做了加1的操作 |
更多信息参看gpdb项目:/src/include/access/appendonlytid.h中AOTupleId和/src/include/storage/itemptr.h中ItemPointerData。
minipage
minipage是的数据块摘要信息,存放在pg_aoblkdir_<relid>表,当AO列表创建索引就会生成辅组表pg_aoblkdir_<relid>。基于索引扫描,key找到对应的TID(segno,rownum),然后通过rownum从minipage中快速定位数据所在的block,最后从block中读取元组数据。
数据结构如下:
结构定义 | 说明 |
typedef struct d { int32 _len; int32 version; uint32 nEntry;//Minipage个数 MinipageEntry entry[1]; }minipage typedef struct MinipageEntry { int64 firstRowNum;//首行 int64 fileoffset;//文件中的偏移 int64 rowCount;//块中元组数 } MinipageEntry | 采用block方式写元组数据,每次写对应生成Minipage数据 数据结构定义,参看/src/include/cdb/cdbappendonlyblockdirectory.h TID的rownum定位Minipage 采用的是二分查找法。更多信息参看/src/backend/access/appendonly/appendonlyblockdirectory.c中find_minipage_entry函数 |
二、实验探索
为了简化过程,这里以utility模式登录其中的一个segment库。Greenplum版本信息如下:
1. 登录SegmentDB
(注:不同的环境端口号可能不同,这里登录端口号为6005的SegmentDB)
2. 创建col_table列存表
create table col_table(c1 int,c2 varchar) with(appendonly=true,orientation=column);
3. 查看pg_class
select oid,relname,relfilenode from pg_class where relname='col_table';
- oid(16387):col_table的唯一标识
- relfilenode(16385):操作系统中对应的物理文件名。
如下所示:
4. 查看pg_appendonly
- blocksize(32768):块大小32k。
- segrelid(16392):pg_aoseg_< relid >表的oid。
- blkdirrelid(0):pg_aoblkdir_<relid>表的oid。当前表无索引,因而为0。
- visimaprelid(16394):pg_aovisimap_<relid>表的oid。
5. 创建索引
create index col_table_idx on col_table(c1);
再次查看pg_appendonly表,blkdirrelid发生变化。如下图:
6. pg_class查看辅组表信息
辅组表名称
select oid,relname,relfilenode from pg_class
where oid in(16387,16392,16398,16400,16394,16396);
追加col_table元组
这里的ctid为(0,2),为什么不是(0,1)?AOTupleId为了兼容heap表的TID,做了加1操作。
再次追加col_table元组
新的元组ctid(0,202),为什么不是(0,3)?因为每个block缺省的seqnum=100,每次block操作元组rownum至少增加100。
7. 辅组表内容查看
再追加一条记录辅组表的信息会发生对应变更。
8. 操作系统文件查看
删除SegmentDB的col_table
通过master建表
- 并发追加元组
- 数据文件
- 看到4个文件,16400.1和16400.129对应一个事务操作;16400.2和16400.130对应另一个事务操作;16400.1和16400.2对应第一列;16400.129和16400.130对应第二列。
- AOTupleId中用7个bit表示段号,所以可以支持128个并发对AO列存表同时追加元组的操作。
总结
AO列存优点 | 分析速度快,对于OLAP场景,每次分析涉及的字段较少,采用GP的AO表的这个设计可以保证行级严格事务,这在列存数据库中不多见,这样的设计有利于高吞吐数据量的加载。同时通过块的方式提高数据的压缩比。 |
ao列存缺点 | 每个列至少对应一个文件。例如集群有10个primary,10个mirror,一张有500列按天分区的列表,对应年的文件数>(10+10)*365*500=3650000。因而需要结合数据量和查询特征来设计合理的segment和分区的个数。 GP5、GP6存储空间占用大,如果表建索引,频繁的小批量加载数据会导致pg_aoblkdir_<relid>占用非常大的存储空间。GP7有brin索引可以考虑,索引文件会非常小,小到可以忽略不计。 |
本文介绍AO列存辅组表,以及相关设计进行解读,为更好的使用和理解AO列存表提供帮助,用户需要根据业务场景选择是否要采用AO列存。
三、参考链接
作者:王爱军
20年来一直工作在一线的老码农,目前就职于中兴通讯。主要工作方向为5G网络管理系统架构,近期在使用和研究Greenplum。