数据库筑基课 - 行列混存之zedstore
文中参考文档可点击阅读原文打开, 推荐《最好的PostgreSQL学习镜像》
数据库筑基课 - 行列混存之zedstore
背景
《数据库筑基课 - 大纲》
本节: 行列混存之 zedstore 表组织结构
简介
zedstore 是来自greenplum(母公司为vmware时期) ( https://github.com/greenplum-db/postgres-archive/tree/zedstore/src/backend/access/zedstore ) 的开源行列混合存储引擎, zedstore的设计目标是要让PG/GP更好的满足HTAP(OLTP+OLAP)混合负载业务场景的诉求, 同时解决PostgreSQL内置heap存储引擎存在的一些问题(高频更新业务中可能引发的bloat、32bit xid wrap等).
zedstore的设计目标:
相比于按行存储的表, 按列存储可以提升查询少部分列的性能(扫描更少的page, 减少IO)。 与按行存储的heap表相比,减少了磁盘占用(更短的tuple head, 同类数据拥有更高的压缩比)。 按列存储, 在运算方面可以结合CPU 批量计算指令集, 提高运算性能. 成为PostgreSQL的一种标准的table access method。 完全MVCC兼容。 支持PostgreSQL的所有索引类型。 混合行列存储,引入column group理念, 例如 “a,b,c”三列经常一起查询, 可以将其存储在一起, “e,f,g,h,i,j”这几列经常一起查询, 可以将其存储在一起, 而其他列单独存储。 避免额外的toast table(引入toast page type)。 提供更好的控制膨胀(引入UNDO page type)。 通过避免rewrite table表,更快地添加/删除列或更改列的数据类型。 也可以实现按指定位置插入列, 逻辑上可以实现. (强迫症患者的福音)
1、file layout
zedstore 将page分为几种类型
meta page , 0号页面, 存储了一些路由信息, 例如zstid tree、att tree的root page blockNum、每个 free page map list 的第一个page的blockNUM. zstid btree page , 以zstid为key, 存储 zstid(逻辑行号) (多条打包存储, 采用zstid精确值+若干偏移量减少存储空间) , 以及tuples对应的undo points (包括tuple可见性). att btree page / column group's page, 以zstid为key, 存储列的值(多条打包集合存储). undo page , 仅zstid tree需要指向undo page/可见性. att tree不需要. toast page , 仅att page需要指向toast page, 超级大的列需要. free page , free page 采用list连接, 每个表有多个free page map list, 分别用于zstid、undo、每个att(含toast page). meta page存储了每个list的第一个page的blockNUM.
目前zedstore将所有的page都存储在表对应的1个数据文件中, 未来可能会将其分开, 例如按tid,att,undo,toast等单独存储在不同的数据文件中.
2、tree(s) layout
zedstore 使用tree来组织数据, 包括逻辑行号(zstid)、各个列(或column group)的数据.
zstid 是一棵btree, 每一列(或column group)是一棵btree.
3、meta page layout
meta page 是 zedstore datafile的0号页面, 存储了一些路由信息, 例如zstid tree、att tree的root page blockNum、每个 free page map list 的第一个page的blockNUM.
注意在图中未区分不同的free page map list, 但是在代码中有说明“采用了不同的list”.
4、zstid btree layout
zstid代表了逻辑行号, 对于表的一条记录来说, 如果不发生update zstid永远不会改变, 并且zstid在一张表里面是唯一的. 但是update后可能发生变化, 或者delete后并vacuum后未来的tuple可能复用已删除的zstid, 所以不要把zstid当PK来使用. 为了能兼容PG原来的tid的接口, zstid可以和ctid相互1对1的进行转换: zstid = ctid.blknum*128 + ctid.offset
注意, 图中的block ID不是ctid里的blknum, 而是zedstore datafile里的block NUM.
观察:
5、zstid page layout
zstid page 内部:
zstid(逻辑行号)连续多条打包存储(delete/update后可能存在gap), 采用Simple-8b格式(zstid精确值+若干偏移量)减少存储空间. 同时还存储了tuples对应的undo points (包括tuple可见性).
格式如下
Header | 1-16 TID codewords | 0-2 UNDO pointers | UNDO "slotwords"
详见
https://github.com/greenplum-db/postgres-archive/blob/zedstore/src/include/access/zedstore_internal.h
6、att(s) page layout
zstid btree 和 att(s) btree 的key 都是zstid, 但是它们存的value不一样:
zstid leaf page的value存储了该逻辑行的可见性、undo point. (注意物理结构是多条打包, 类似vops的瓦片式存储) att(s) leaf page的value存储了该列的值、或toast point. (注意物理结构是多条打包, 类似vops的瓦片式存储)
注意每个page 又包含了2个stream部分
lower attstream (uncompressed), 当插入数据时, 写入到uncompressed的部分, 直到page写满, 会触发压缩, 将uncompressed和compressed一起压缩后, 替换compressed的部分 (大范围压缩以提高整体压缩比). 空出来的uncompressed部分又可以继续写入, 直到不能压缩后都没有空间为止. upper attstream (compressed)
详见
https://github.com/greenplum-db/postgres-archive/blob/zedstore/src/include/access/zedstore_internal.h
观察:
7、插入
先分配zstid并写入zstid tree, 然后使用该zstid作为key, column value作为value写att btree page.
8、查询
先扫描zstid tree, 过滤不可见的tuple, 然后根据要查询的column扫描对应的att tree.
9、删除/更新
只需要修改zstid tree, 标记删除的行. vacuum时扫描zstid tree, 并清理dead zstid对应的所有att tree后, 该dead zstid才能被复用.
更新操作相当于delete+insert.
10、packing att btree page uncompressed chunks 逻辑
图中描述了数据写入到att tree leaf page时的流程, 优先写入page中uncompress的部分(提高写入速度), 如果写满了则触发压缩的逻辑.
11、索引
与MySQL 索引组织表的二级索引类似, 给zedstore的表创建索引时, key为索引字段/表达式的值, value为zstid. 使用索引扫描时回zstid tree检查tuple可见性和版本, 回att tree获取想要查询的字段值.
MySQL索引组织表、zedstore、覆盖索引的区别:
MySQL索引组织表tree的核心内容
key: PK leaf page's value: tuple values, undo pointer.
MySQL索引组织表的二级索引tree的核心内容
key: column value leaf page's value: PK's value
使用MySQL二级索引时, 通过PK回到索引组织表去获取其他字段的内容(以及可见性判断).
zedstore表zstid tree的核心内容
key: zstid(逻辑行号) leaf page's value: undo pointer
zedstore表att tree的核心内容
key: zstid(逻辑行号) leaf page's value: column (group) values
使用zedstore索引时, 通过zstid回到att tree去获取其他字段的内容, 回到zstid tree判断可见性.
PostgreSQL including index (覆盖索引)的核心内容
key: column/express value leaf page's value: ctid(heap表的物理行号), included columns value
使用覆盖索引时, 通过ctid和VM判断可见性, 覆盖字段不需要回表(除非无法判断可见性). 需要回表时回到heap table去获取其他字段的内容(以及可见性判断).
优势 & 适合场景
从zedstore的设计目标、原理来看, 它可能会比较适合HTAP(OLTP+OLAP)混合负载业务场景, 两者均可兼顾. 兼顾就意味着平庸, 两者都要做到拔尖的话可能会比较难, 除非有天才设计师出现.
从当前已发布的测试数据可以看出, 对比pg内置的heap, zedstore在批量导入性能、压缩比、查询少量列的大量数据的性能都更优秀.
劣势 & 不适合场景
由于zstid和每一列都采用B树组织, 数据块可能比较离散, seq scan或大范围扫描较多条数据时, 在HDD硬盘上体现为随机IO访问, 性能较差.
zedstore目前的版本进行了一些优化:
使用block预分配(zedstore_rel_extension_factor), 使得leaf pages物理上更加连续. (PS: 在经过大量的更新、或在大并发数据导入的情况下, “seq scan或大范围扫描较多条数据时产生大量随机IO访问”问题可能依旧存在).
zedstore不适合大量查询很多列的场景, 因为要扫描很多个att btree.
但是这可能是你的设计问题, 例如可以将需要经常一起访问的column放到一个group里, 那么访问这些字段时就只需要访问这一棵tree, 而不是很多棵tree.
不过也存在一个特殊情况很难兼顾性能: 既要经常访问单列的大量记录, 又要经常访问很多列的大量记录.
DEMO
可以使用以下任意镜像作为环境进行zedstore的安装测试
《2023-PostgreSQL Docker镜像学习环境 ARM64版, 已集成热门插件和工具》 《2023-PostgreSQL Docker镜像学习环境 AMD64版, 已集成热门插件和工具》
进入容器root用户, 安装zedstore软件
apt-get install -y docbook-xml docbook-xsl fop libxml2-utils xsltproc
cd /tmp
git clone --depth 1 -b zedstore https://github.com/greenplum-db/postgres-archive
cd /tmp/postgres-archive
./configure --prefix=/tmp/zedstore --with-lz4
make world -j 8
make install
cd contrib
make install
初始化zedstore分支的数据库集群
mkdir /tmp/pgdata
chown postgres:postgres /tmp/pgdata su - postgres
vi ~/zedstore.sh
# add by digoal
alias rm='rm -i'
alias cp='cp -i'
alias ll='ls -larth'
alias mv='mv -i'
export PGHOME=/tmp/zedstore
export PATH=$PGHOME/bin:$PATH
export LD_LIBRARY_PATH=$PGHOME/lib:/usr/local/lib:/usr/lib/aarch64-linux-gnu:$LD_LIBRARY_PATH
export PGDATA=/tmp/pgdata
export PGUSER=postgres
export PGHOST=$PGDATA
export PGPORT=5433
export PGDATABASE=postgres
export LC_ALL=en_US.UTF-8
export PATH=/tmp/cmake-3.27.9/bin:$PATH
export MANPATH=/tmp/cmake-3.27.9/man:$MANPATH
export PATH=/usr/local/go/bin:$PATH
. ~/zedstore.sh
initdb -D $PGDATA -U postgres -E UTF8 --lc-collate=C --lc-ctype=en_US.utf8
修改参数配置
vi $PGDATA/postgresql.auto.conf port = 5433 # (change requires restart)
max_connections = 100 # (change requires restart)
unix_socket_directories = '., /tmp' # comma-separated list of directories
shared_buffers = 512MB # min 128kB
dynamic_shared_memory_type = posix # the default is the first option
vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables)
bgwriter_delay = 10ms # 10-10000ms between rounds
max_wal_size = 1GB
min_wal_size = 80MB
log_destination = 'csvlog' # Valid values are combinations of
logging_collector = on # Enable capturing of stderr and csvlog
log_truncate_on_rotation = on # If on, an existing log file with the
log_timezone = 'Etc/UTC'
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'
lc_messages = 'en_US.UTF-8' # locale for system error message
lc_monetary = 'en_US.UTF-8' # locale for monetary formatting
lc_numeric = 'en_US.UTF-8' # locale for number formatting
lc_time = 'en_US.UTF-8' # locale for time formatting
default_text_search_config = 'pg_catalog.english'
启动zedstore分支数据库集群
pg_ctl start
测试体验
psql postgres=# create table tbl (id int, c1 int, c2 int, c3 int, c4 int) using zedstore;
CREATE TABLE
postgres=# \timing
Timing is on.
postgres=# insert into tbl select generate_series(1,1000000), 1000*random(), 1000*random(), 1000*random(), 1000*random();
INSERT 0 1000000
Time: 317.587 ms
postgres=# alter table tbl set (zedstore_rel_extension_factor=100);
ALTER TABLE
postgres=# insert into tbl select generate_series(1,1000000), 1000*random(), 1000*random(), 1000*random(), 1000*random();
INSERT 0 1000000
Time: 382.379 ms
postgres=# select count(distinct c1) from tbl;
count
-------
1001
(1 row)
Time: 270.564 ms
扩展问题
扩展阅读
PPT: https://www.pgcon.org/events/pgcon_2020/sessions/session/44/slides/13/Zedstore-PGCon2020-Virtual.pdf 开源项目: https://github.com/greenplum-db/postgres-archive/tree/zedstore 开源项目: https://github.com/postgrespro/vops 代码: https://github.com/greenplum-db/postgres-archive/tree/zedstore/src/backend/access/zedstore 代码: https://github.com/greenplum-db/postgres-archive/blob/zedstore/src/include/access/zedstore_internal.h 代码: https://github.com/greenplum-db/postgres-archive/blob/zedstore/src/backend/access/zedstore/zedstore_inspect.c blog: 《PostgreSQL 向量化执行插件(瓦片式实现-vops) 10x提速OLAP》 blog: https://blogs.vmware.com/opensource/2020/07/14/zedstore-compressed-columnar-storage-for-postgres/
本期彩蛋 - 数据库生态工具&信创开源数据库
用好周边工具, 数据库管理水平战胜90%老司机
1、管控软件
云猿生开源的kubeblocks, 如果你要管理很多套并且种类很多的数据库产品, 推荐选择.
https://github.com/apecloud/kubeblocks
乘数开源的clup, 专门用来管理PostgreSQL和PolarDB的集群管理软件, 如果你要管理很多套数据库, 推荐选择. 并且clup还提供了企业版、自研的连接池、分布式存储、一体机、备份平台等, 企业可以关注一下.
https://www.csudata.com/
若航开源的pigsty, 集成了300多个PG插件的PG集群和PolarDB集群管理软件, 如果你要管理很多套数据库, 并且对插件有特别多的需求, 推荐选择.
https://pigsty.cc/zh/
2、审计监控诊断优化
海信聚好看的 DBdoctor, 采用ebpf技术, 在对数据库几乎没有影响的情况下实时监控数据库和服务器的各项指标, 发现和诊断问题根因非常方便.
https://www.dbdoctor.cn/
Bytebase 的目标非常远大, 是位于您和数据库之间的中间件。它是数据库 DevOps 的 GitLab/GitHub,专为开发人员、DBA 和平台工程师打造。
https://bytebase.cc/docs/introduction/what-is-bytebase/
D-Smart, Oracle老前辈白老大他们搞的, 专注企业级市场, 将业界顶级DBA经验的产品化作品, 产品功能包括数据库监控、诊断、优化等.
https://www.modb.pro/db/567140
3、数据同步&迁移&备份恢复
NineData, 老领导出去创业做的产品, 产品涵盖了数据同步、迁移、备份、比对、devops、chatDBA等.
https://www.ninedata.cloud/home
DSG, 非常老牌的数据库同步迁移企业级产品, 支持各种数据库的异构和同构迁移, 用他们的话说, 没有dsg搞不定的迁移, 比goldengate还牛.
https://www.dsgdata.com/
通过信创并且开源的数据库:
PolarDB for PostgreSQL
https://github.com/ApsaraDB/PolarDB-for-PostgreSQL
以下PG系国产数据库也非常值得关注: HaloDB(基于PG兼容PostgreSQL、Oracle、MySQL. http://www.halodbtech.com/ )、IvorySQL(基于开源PG兼容PG、Oracle. https://www.ivorysql.org/zh-cn/ )、ProtonBase(云原生分布式数仓. https://protonbase.com/ ).
参考文档点击阅读原文获得
感谢关注我的github (https://github.com/digoal/blog) 及视频号:
彩蛋2: 近期活动预告
彩蛋3: 全国大学生数据库创新设计赛 点击报名