本文由 dbaplus 社群授权转载。
PostgreSQL 作为一款许可开放,功能齐备的开源关系数据库,在当前提倡自主可控的大背景下,正受到越来越多企业的重视。苏宁从 2017 年开始引入 PostgreSQL,到 2019 年双 11 前 3 年间已上线 3000 多 PostgreSQL 实例,运行在我司各个不同的业务线。我们用 PostgreSQL 替换掉大量的商业数据库,不仅为公司节省了很多成本,而且通过灵活运用 PostgreSQL 的特色功能,甚至大大提升了业务的使用体验。
本次分享主要介绍苏宁引入 PostgreSQL 的背景和历程,以及我们在实际使用 PostgreSQL 中积累的一些经验。
早期苏宁的数据库全部采用商业数据库。2013 年,我们引入了 MySQL。到了 2016 年,MySQL 已经被大量使用,但是,核心业务仍然依靠商业数据库承载。当时我们意识到继续依赖国外商业数据库,除了每年需要支付高昂的许可和维保成本,对我们提升自身的数据库运维水平更好地支撑业务发展也存在诸多的弊端。因此我们在 2016 年启动了去商业数据库的调研工作,并于 2017 年开始逐步推进。
通过摸底现有业务数据库的使用情况,我们发现当时已大量使用的 MySQL 在功能和性能上离成熟商业数据库都有一定的差距,在某些场景下难以替代商业数据库,特别是需要执行一些复杂 SQL 的场景。因此我们在现有 MySQL 的基础上又考察对比了另一款非常流行的开源数据库 PostgreSQL。
PostgreSQL 和 MySQL 最大的差异可以概况为两点。第一,在使用场景上,MySQL 支持的 SQL 特性比较少,在大数据量和复杂查询下性能也容易出问题,适合比较简单的 OLTP 业务;第二,PostgreSQL 的功能更加全面,可同时支持 OLTP 和 OLAP 类的业务,和 Oracle、DB2 这样的商业数据库更加接近。
详细的一些特性对比可参考下面的表格。
注 1:参考 http://www.sql-workbench.net/dbms_comparison.html
以上对比基于 MySQL 5.7 和 PostgreSQL 10。MySQL 8.0 在 SQL 特性上有非常明显的提升,比如 DDL 事务一致性保证,支持窗口函数和 CTE。
通过实际的测试对比,我们发现 PostgreSQL 在某些场景下比 MySQL 有明显的优势:
OLTP 测试,如下图:
OLAP 测试,如下图:
在可靠性方面,根据我们的测试和实际的生产运行,发现 PostgreSQL 也优于 MySQL。
1)复制延迟
MySQL 在高并发写入场景很容易产生复制延迟,相同测试条件下 PostgreSQL 不仅写入的 TPS 更高而且没有观察到复制延迟。根据我们的测试,MySQL 从库应用事务的速度在 1w/s 左右,PostgreSQL 备库应用事务的速度则在 10w/s 以上。
产生这么大差异的原因在于 MySQL 是逻辑复制,主库的每条 SQL 在从库上都要完整的执行一遍,MySQL 的从库即使启用并行复制,也难以达到主库的并行度。PostgreSQL 的物理复制,备库直接修改被变更的数据块即可,所以应用日志的速度很快。为确保 MySQL 的稳定运行,我们要求业务在使用 MySQL 时,单库的写入 TPS 不超过 5000。
2)故障恢复
在故障模拟测试和实际的生产运行环境中,我们发现 MySQL 在遇到宕机,RAID 卡重置等硬件故障后容易出现主备数据不一致或者数据损坏无法启动的问题;同等条件下 PostgreSQL 出问题的概率小得多,通常重启就可以自动恢复。
对于普通的宕机我们可以通过切备机快速恢复生产,但是如果是断电导致的大面积宕机,就需要数据库在供电恢复后能快速启动快速恢复。宕机恢复对数据库来说是一项基本能力,但是在一些极端条件下,数据库也可能无法自动恢复。
下面这个表是在数据库的存储设备缺少掉电保护情况下发生断电故障后的测试数据。
从上面可以看出 PostgreSQL 表现出了很强的健壮性,我们分析其主要原因有下面几点:
作为对比,MySQL 使用了 fuzzy checkpoint,每隔 7 秒甚至更短的时间就要进行一次 checkpoint。在 flush 不能确保持久化的情况下,很近时间内产生的数据不一致就会导致数据库无法恢复,即使用了强制恢复模式(innodb_force_recovery=6)。
另外,极端情况下数据文件出现坏页又不能通过备份恢复时,PostgreSQL 支持设置一个参数再重启就可将坏掉的页面清零快速恢复生产;MySQL 没有类似的功能,而且 MySQL 的索引组织表的页面之间有逻辑关系,技术上要做到这一点也比较困难。
通过技术选型,我们决定引入 PostgreSQL,通过 MySQL 和 PostgreSQL 的组合来替代线上的商业数据库。在具体系统的数据库选型上,还需要考虑业务的使用习惯,周边工具配套等实际情况。
总体上遵守以下的规范进行数据库选型:
我们上线的第一个 PostgreSQL 业务系统是一个实时大数据处理系统。这个系统的主要业务流程是从各个其它业务系统里面抽取相关数据放到它的数据库明细表里,然后再定时通过存储过程汇总明细表生成报表提供给分析平台进行展示。
这个系统一个很大的特点就是对数据库的性能要求特别苛刻,当时使用的是单库的商业数据库,平时数据库的 CPU 利用率都在 45%以上,大促期间更是超过 80%,可以说不堪重负。而且为了支撑今后的业务发展,这个系统必须在 2017 年双 11 前扩容十倍的容量,很显然单机的数据库已经没有任何性能扩容的空间,无法满足这一需求。
这个系统对数据库的使用主要包含下面 3 个不同的场景,其中每一个场景都对数据库有很高的性能要求。
1)明细表更新
实时更新包含 400 多个字段的宽表。数据加载速度要求达到 5w/s 以上,其中 90%是 UPDATE。
2)报表计算
支持 200+/min 的实时报表计算
3)报表和明细查询
支持高并发的报表和明细查询
考虑到这个系统当前对数据库的业务需求和未来的发展规划,我们希望扩容方案是基于 SQL 的开源分布式数据库。我们比较了几个候选方案,考虑到和业务场景的匹配度和今后的运维的便利性,最终选择了 Citus,一个能把多个单机的 PostgreSQL 变成分布式数据库集群的插件。
下面是这个系统使用 Citus 的部署架构,为了优化性能,我们做了一些架构上的优化。明细表更新不经过 Coordinator 节点,而是先到 Coordinator 节点批量查询待更新记录的位置信息,再直接到对应位置的 Worker 上以批量 INSERT ON CONFLICT 的方式更新明细数据。
根据 POC 压测的结果,把原单库的商业数据库替换成 1CN + 8 个 Worker 的 Citus 集群后,性能提升了 10 多倍,圆满达成扩容目标。
新的系统从 2017 年上线后至今已平稳运行多年,生产集群规模也从最初上线的 4 个 Worker 逐步扩容到 16 个 Worker,而且 CN 和 Worker 平时的 CPU 利用率都保持在 10%左右,很好的支撑了业务的发展。
从 2017 上线第一套 PostgreSQL 以来,截止 2019 双 11,我们已经部署了 3000+PostgreSQL 实例,其中 80%以 Citus 集群的形式部署,其余是普通的 PostgreSQL。
使用 PostgreSQL 的应用,既有 OLTP 类的业务也有 OLAP 类的业务。下面介绍其中的 3 个业务案例。
我们在很多计费结算类业务中使用了 PostgreSQL + Citus 的数据库架构,其中规模最大的系统是物流的计费平台。它有几百个数据库节点,多个大表的数据量超过百亿。这个系统原来使用的数据库是商业数据库,应用在业务层做分库分表。在业务层维护大量数据库节点的方式,大大增加了日常开发和管理上的成本。
举一个简单的例子,如果要在某个表上添加一个字段,需要先在几十个库的几百张分表里依次把字段加上,然后再修改上百个应用的配置,中间不能有任何错误和遗漏。而一次发布不会只改一个表,所有实际上每次发布光编辑发布脚本就是一项很大的工作。
后来我们把这个系统的数据库从一堆单机商业数据库迁移到了分布式的 Citus。迁移后不仅省掉了商业数据库的许可和维保成本,而且应用层去掉复杂的分库分表逻辑,使用体验得到了极大的提升。
迁移到 Citus 后,以前一些不容易做甚至没法做的事情变得可以做而且简单了,比如执行一些跨库的查询,包括跨库事务。这个系统中大量的业务请求是涉及跨库事务的。Citus 实现了基于 2PC 的分布式事务,支持分布式死锁检测和故障时的自动事务恢复,透明地支撑了业务的跨库访问。Citus 上的日常 DDL 发布也很简单,只要在 Citus 的 Coordinator 节点对逻辑表执行 DDL 就可以了,使用体验上和在普通单库上的 DDL 发布没什么区别。
对电商来说,实时把握客户留存实施精准营销是一项非常重要的功课。但是对于有数亿会员和大量商品的大型电商平台,常规的处理方式是很低效的。通过探索和演进,现在我们使用 citus + pg_roaringbitmap 插件的技术方案。
roaringbitmap 是一种高效的 bitmap 压缩存储格式,在标签类的应用中,已被业界广泛使用。pg_roaringbitmap 插件则把 roaringbitmap 作为一种新的数据类型引入到了 PostgreSQL 里。这体现了 PostgreSQL 非常容易扩展的特点,不仅数据类型,索引类型,FDW 甚至存储过程语言等等都可以扩展。再结合 Citus 的水平扩展能力,我们实现了百亿级标签的实时存储和查询。
苏宁有大量线下门店,为支撑线下业务的运营,需要基于地理位置为用户提供个性化的搜索和推荐服务。并且在大促期间,需要支撑非常高的访问量。我们使用 PostgreSQL + PostGiS 插件支持位置数据的存储和高效查询,通过一主多从和基于 JDBC 多主机 URL 的读写分离水平扩展数据库处理能力。大促期间会临时增加从节点提升数据库的吞吐能力,最多时扩容到 11 个节点。
在使用 PostgreSQL 的过程中,我们踩过一些坑也积累了一些经验。下面我介绍三个 PostgreSQL 使用过程中需要注意的地方。
Gin 索引是 PostgreSQL 的一个特色功能,很多研发小伙伴反映,数据库从商业数据库迁到 PG 后,他们最惊喜的 PG 特性就是 GIN 索引。很多系统的用户都有灵活的数据查询需求,查询条件涉及的字段不固定,之前应用开发者不得不为每种查询组合都创建一个 Btree 索引。这样做导致需要创建的索引非常多,不仅占用空间,影响更新性能,而且仍然难以覆盖所有查询场景。
对于这种搜索类的场景,有些业务会将数据导入到 Elasticsearch 进行查询。但是这种方案增加了系统的复杂度,需要保证 ES 中的数据和原始数据库的数据的保持一致,而且 ES 的索引更新相对与原始数据还存在一定延迟。而使用 PostgreSQL 的 Gin 索引,不存在这些问题。
Gin 的基本原理和 ES 的倒排索引一样。它把每个(列号,Key 值)作为倒排索引的键,将匹配这些键的元组的物理位置,即 TID,作为值存在 Entry Tree 中。其中 TID 值存储在 Entry Tree 的叶子节点。匹配同一个 Key 的元组比较少时,TID 集合以列表的形式存储;匹配同一个 Key 的元组比较多时,TID 集合以 Btree 的形式存储,即 Posting Tree。新插入的元组先写入 Fast update list,达到一定数量时在批量合并到 Entry Tree,这一优化可以大大提升插入数据到 Gin 索引的性能。另外 Gin 索引存储 TID 集合时做了压缩处理,因此对于低基数的索引字段,Gin 索引能显著地节省存储空间。
和常规的 Btree 索引相比,Gin 索引主要有如下优势:
下面的测试数据可以更直观的反映 Gin 索引的优点。
对于低基数的索引字段,Gin 占用空间甚至不到 Btree 的十分之一。
在性能上,多字段 and 组合查询时 Gin 的性能可达到 Btree 的 3 倍。
但是 Gin 也不是万能的,它也有一些短板,有些场景下使用 Gin 索引会适得其反。概况起来主要有以下几点,需要注意:
PostgreSQL 是多进程架构,每一个连接对于一个进程。每个进程的私有内存空间中会缓存一些元数据,比如系统表数据,表定义,执行计划等等。如果使用不当,可能会由于后端进程私有内存占用过大导致系统内存不足,导致内存 SWAP 等问题。
我们可以用下面的命令查看某个后端进程的私有内存分配情况:
gdb --batch-silent -ex ‘call MemoryContextStatsDetail(TopMemoryContext,100)’ -p ${后端进程号}
复制代码
执行上面的命令后,相关输出反应在 PostgreSQL 的日志文件中。
上面这个图里,私有内存的大部分被元数据缓存占用了,即 CacheMemoryContext。
通常,元数据缓存中占用内存最多的是下面两个系统表
当数据库里表,分区以及表字段很多时,它们的元数据会占用的内存也会比较多。下表的例子可以反映这一点。
从上面的数据我们可以知道,表相关的元数据缓存占用内存的大小主要和下面几个因素有关:
如果在使用 PostgreSQL 的过程中出现内存不足的问题,我们可以采取以下回避措施:
PostgreSQL 的 MVCC 实现机制和其它传统的关系数据库不太一样。更新记录时不是在原地更新并且把修正的前镜像记录到 UNDO 日志,而是在数据文件中把原来的记录标记为“被删除”再插入一条新的记录,以后再通过 VACUUM 把这些”被删除“记录占用的空间回收掉。也就是说 PostgreSQL 中只有 REDO 日志,没有 UNDO 日志。在这种 MVCC 设计下,PostgreSQL 回滚事务可以立即完成,和事务大小无关,回滚事务时只需要在 CLOG 事务状态文件中标记这个事务的状态为 ABORTED 即可。
PostgreSQL 的 MVCC 实现机制带来的问题是如果垃圾回收不及时容易导致数据文件膨胀,这也是很多人吐槽 PostgreSQL 的地方。不过,不能单纯地把 PostgreSQL 的 MVCC 机制视作一个槽点,只是 MVCC 的不同实现方式而已。不知道大家注意到没有,很多新兴的分布式数据库库,也都采用了类似 PostgreSQL 标记删除的 MVCC 机制。
作为 PostgreSQL 的使用者,我们需要做的事是要确保 PostgreSQL 的垃圾回收工作能够正常运作。具体有下面几件事情要做。
1)autovacuum 参数调优
PostgreSQL 中有一个后台的 autovacuum 进程专门负责回收垃圾,我们可以根据数据库的配置和业务特点对 autovacuum 进行合理的参数设置,确保 autovacuum 回收垃圾的速度足够快,对数据库负载冲击又比较平滑。
比如下面几个参数:
其中,最需要修改的是 autovacuum_vacuum_cost_limit,其含义是一次回收最多消耗多少 cost 则暂停一会(autovacuum_vacuum_cost_delay 的设定值)。默认值为 200,这个值对于现代的设备显得太小,容易导致垃圾回收速度跟不上垃圾产生的速度,使用 SSD 时可以考虑把它设置为 10000。
另外,autovacuum 默认在表中被更新的元组比率达到 20%的时候启动垃圾回收,对于亿级别的大表,会导致一个问题。就是一次垃圾回收的任务太大,垃圾回收时间过长。
所以建议采用以下优化措施:
关于 autovacuum,详细可以参考一下这篇文章:
http://www.postgres.cn/v2/news/viewone/1/387
2)自动释放过长事务
PostgreSQL 在垃圾回收时会保留对当前的存活事务可见的死元组。如果数据库中有一个执行时间特别长的事务,那么在这个事务存活期间数据库中产生的所有垃圾元组都无法被回收。这种长事务有时侯不是来自我们正常的业务访问,而是来自一些异常场景。我们可以在数据库中设置一些超时参数,使得在异常情况下,PostgreSQL 可以及时把事务终止。
可以参考下面几类超时参数设置:
idle_in_transaction_session_timeout = 3600s
复制代码
lock_timeout = 60s
复制代码
tcp_keepalives_idle = 60tcp_keepalives_count = 10tcp_keepalives_interval = 5
复制代码
3)监控垃圾回收相关的数据库状态
除了前面介绍的措施,我们还需要对垃圾回收相关的一些数据库指标进行监控,一旦发生异常可以及时发现和处理。以下是我们在生成环境部署的一些和垃圾回收相关的监控项,大家可以参考一下。
参考资料
Q&A
Q1:目前 PosgreSQL 的 recovery 功能与 Oracle 相比怎么样?
A: PostgreSQL 本身的备份恢复功能很完备,支持恢复到指定时间点,使用也很方便。另外还有很多第三方的开源备份管理工具,比如 pg_rman,barman 等等,可以更加方便的管理备份。所以,我觉得 PG 的备份恢复不弱于 Oracle 这样的商业数据库。
Q2:老师你们的高可用方案是怎样的?
A:我们基于 PG 原生的流复制搭建 HA 集群,对外提供 VIP 由普通的读写业务访问。只读业务在 JDBC 的 URL 上同时所有个节点的实际 IP,通过 pgjdbc 的多主机 URL 功能进行读写分离和读负载均衡。
Q3:用 PosgreSQL 的时候,什么时候开始表分区?实现方式是怎样?
A: 我们内部规范要求单表控制在 1 亿记录或 10GB,超过这个标准建议分区或分片。分区的话如果是 PG10 或以下版本,建议使用 pg_pathman 插件。PG10 及以前版本的分区在分区数很大时性能会比较差。如果是更高的 PG 版本,特别是 PG12 以后就建议直接使用原生的分区了。分片我们使用 Citus 进行分库分表。
Q4:请问你们的监控、自动化运维方面用了什么工具?
A: 监控上我们主要用了 Zabbix 和 Prometheus,其他的日常运维方面主要使用的自研的工具和平台。
Q5:3000+实例如何管理?有运维系统支撑下,需要多少人力?
A: 我们主要运维工作都通过内部的运维平台支撑,实现了自动化。如果只是单纯 PG 的维护的话,我觉得 10 个人左右甚至更少是可以支撑的。
Q6:MySQL 如何迁移到 PostgreSQL 呢?有什么需要注意的事项吗?
A:我们用 mysql_fdw 进行迁移,这也是很方便的一种方式。关于迁移的注意事项,除了数据类型是适配,需要注意 PG 一定要使用 UTF8 编码。另外 PG 不支持\0000 这个特殊的 Unicode 字符。当然这个字符也没是什么实际意义,主要是有一些应用系统数据入库不规范,在以前的 DB2 或 MySQL 库里不小心写入的这个字符,后面迁移到 PG 的时候就需要规范化,把这个非法字符删掉。
Q7:PosgreSQL 使用内存方面有什么好的监控方式吗?经常会遇到连接爆内存不足,但实际上系统还有内存。
A:可以使用 OS 级别的内存监控,比如可用内存量和 swap,一旦出现 swap 都需要及时处理。如果系统还有内存但连接爆内存不足就要具体问题具体分析了,需要参考当时出错的信息。建议把问题的复现方法提供出来。
Q8:citus 的高可用如何实现的呢?是每个 worker 节点都搭建一个从库吗,cn 节点是否也需要搭建一个从库?
A:Citus 支持 2 种高可用方式,一种是多副本分片,由 CN 节点在写数据的时候同时写多个副本到不同 worker 上;另一个是使用 PG 原生的高可用,比如流复制。
多副本的高可用方式,维护简单,但使用起来有一些限制,比如不支持 Citus MX,写入性能下降明显等等,Citus 官方手册上也认为多副本高可用只适合 append only 的业务场景。
因此,Citus 更加通用的高可用方式是由底层 PG 做高可用,即 CN 节点以及下面每个 Worker 节点都采用流复制的方式部署一对主备机器。
Q9:有没有一个搭建 citus 集群的具体实施说明呢?
A:Citus 部署实施相关的问题,可以参考一下《Citus 生产部署和维护.pptx》(https://pan.baidu.com/s/1eRQsdAa)。
另外,Citus 集群里每个节点都是 PG,这些 PG 节点的参数配置以及 HA 部署和普通的 PG 是一样的,参考 PG 的相关资料即可。
Q10:磁盘是怎么规划的?如何规划性能更好?使用 raid5、raid10 还是单盘 raid0?
A:对磁盘的规划需求,PG 和其他数据库应该是类似的。raid5、raid10 还是单盘 raid0 这其实是一个可靠性,性能还有成本的取舍,大家可以根据自己的情况判断。我们目前用的是 raid5。
Q11:max_locks_per_transaction 这个参数有推荐的值吗?感觉这个参数和内存使用有很大的关系。
A:这个默认值是 64,通常默认值应该就可以覆盖绝大多数情况不需要修改。每个锁占用的空间都不是很大,只要不把这个参数调得特别大,锁应该不是最消耗内存的地方。
Q12:Pacemake 集群安全吗?
A:Pacemaker+corosync 的 HA 方案中它的集群元数据存在每个集群节点上并实时同步,但是它的元数据的同步协议不是强一致的协议,一旦网络分区,有脑裂风险。
所以,基于 Pacemaker 的 PG HA 需要配置物理的 fence 设备防止脑裂,特别是一主一从的架构。
作者介绍:
陈华军, 苏宁易购架构专家,负责数据库产品的相关设计工作,十年以上数据库相关工作经验。PostgreSQL 中文社区核心组成员,主要负责 PostgreSQL 中文手册翻译项目的维护。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货