当前位置: 首页 >> 技术文章 >> 论PostgreSQL在实际场景中的十大缺陷
论PostgreSQL在实际场景中的十大缺陷
发布时间:2020-04-07 发布人:Rick Branson 200

作者:Rick Branson

来源:https://medium.com/@rbranson/10-things-i-hate-about-postgresql-20dbab8c2791 

译者:孙雪,彭冲,刘伟


最近几年,软件开发社区对流行的开源关系型数据库的热爱已经达到了一个高潮。 在所有Hacker News议题中,有这样一篇文章,“PostgreSQL是世界上最好的数据库”,文章中处处表达出对PostgreSQL数据库无条件的认同和热爱,这也反映出一种社区狂热化现象。

 

虽然这类的称赞有很多是合理的,但缺乏实际意义的观点却让我有些烦恼。 没有一个软件是完美的,那么我们就客观讨论一下,PostgreSQL到底存在什么样的缺陷?


自2003年以来,我一直在生产环境中使用PostgreSQL,其部署数据量范围从小型(千兆字节)到中等(到PB级)不等,因此我的主要关注点在于pg构建的运行业务系统能够保证持续可用。 多年来,我遇到过很多生产环境下的issue问题,让我对于PostgreSQL数据库有了很多直观的一手体验。

 

接下来我会一一分享与大家探讨这些缺陷。

 

缺陷1:灾难性的XID解决方

 

关于这一点建议你查看更多资料,毫不避讳地说,这个缺点真的很让人头疼。 该问题导致过很多长时间停机的故障,长达数天。 如果你查看足够的材料,比如用Google搜索,就会发现许多人都在这个功能上踩雷。 几乎所有不具备高级专家经验的PostgreSQL技术人员,都会遇到这个问题。

 

或许将来某个时候,XID可能会过渡为使用64位整数,但是在那之前,我们仍然要继续应对这个挑战。 不过好一点的是,与飞机上的应用软件不同,这个故障我们是可以尽量去避免的,只要不使用这个功能的话。

 

缺陷2:failover故障可能会丢失数据

 

如果运行中的主服务器突然出现故障,那么运行中的流复制设置几乎肯定会丢失已提交的数据。 有人可能会说:“异步复制的代价就是这样。” 但并不是所有的异步复制都会丢失数据。 PostgreSQL虽然支持同步复制优选提交的机制,以实现容错的持久性,但是如果要保证较小的性能影响范围,就会对应用程序提出更复杂的设计要求。

 

这种情况下,虽然等待不会占用系统资源,但是事务锁会继续保留,直到确认转移为止。 导致的结果是,为了避免响应时间增加和资源争用增加,需要谨慎使用同步复制,因为可能会将降低数据库应用程序的性能。

 

同步复制优选提交在某些情况下很有用,但我不推荐在通用用例中使用。这个机制有点类似于Kafka的ISR复制,具有acks = all和一个已定义的min_isr,但是在运行任意查询的时候,根据目标端数据库类型及事务处理原理不同,会表现出细微的差别。我还没有了解到过,通过failover故障转移,有过成功应用仲裁提交,在数据规模较大的环境中实现高可用性,高耐久性的复制案例。如果各位读者有这样的案例,我愿意一听!

 

就关系数据库而言,Galera Cluster的组复制也不完美,但更接近理想状态。 他们甚至鼓励按地理分布的复制,但是这对于使用仲裁提交的PostgreSQL复制设置很可能是灾难性的。

 

缺陷3:低效率的复制会传播失败

 

到目前为止,流复制是生产部署中最常用的复制机制。 它是物理复制的一种形式,可以复制磁盘二进制数据本身中的更改。

 

每次需要通过写操作修改磁盘上的数据库页面(4KB)时,即使只是一个字节,也将使用请求的更改进行编辑的整个页面的副本写入预写日志(WAL) 。 物理流复制利用此现有的WAL基础结构作为流到副本的更改日志。

 

例如,使用物理复制,大型索引构建会创建大量WAL条目,从而很容易成为流复制的瓶颈。 页面粒度的读取-修改-复制过程会导致主机上由硬件引起的数据损坏,更容易将损坏传播到副本,这种故障我个人在生产中亲眼目睹过。

 

这与逻辑复制相反,后者仅复制逻辑数据更改。 至少从理论上讲,大型索引构建只会导致在网络上复制单个命令。 尽管PostgreSQL已经支持逻辑复制已有相当长的一段时间了,但是大多数部署都使用物理流复制,因为它更健壮,支持范围更广并且更易于使用。

 

缺陷4:MVCC垃圾回收频发

 

与大多数主流数据库一样,PostgreSQL使用多版本并发控制(MVCC)来实现并发事务。 但是,其特定的实现通常会给垃圾行的数据版本及其清理(VACUUM)带来操作上的麻烦。 一般来说,UPDATE操作会为任何已修改的行创建新副本(或“行版本”),将旧版本保留在磁盘上,直到可以清除它们为止。

 

多年来,这种情况一直在稳步改善,但它是一个复杂的系统,对于任何初次接触该问题的人来说都是一个黑匣子。 比如说,你是否了解堆内元组(HOT)及其何时启动对于繁重的就地更新工作负载(如连续保持一致的计数器列),这种操作很可能会失败。 默认的自动真空设置在大多数情况下都有效,但是,如果真的失效了,那后果就不堪设想。

 

相较而言,MySQL和Oracle使用Redo和undo日志。 他们不需要类似的后台垃圾收集过程。 为了实现整个功能所作出的权衡牺牲主要是事务提交和回滚操作的额外延迟。

 

虽然这样会有延迟产生,但是也许是在遥远的将来的某个时候,这个功能就会变得很有价值了。

 

缺陷5:每次连接处理=规模化痛苦

 

PostgreSQL为每个连接生成一个进程,而其他大多数数据库都使用更有效的连接并发模型。 由于存在一个相对较低的阈值,在该阈值上添加更多的连接会降低性能(约2个内核),最终会导致性能下降,而性能下降的比例可能会很高(难以估计,高度依赖于工作负载),就会使得性能调优的难度增加。

 

使用连接池的标准方法当然可以解决问题,但是会带来额外的架构复杂性。 在一次特别大规模的部署中,我最终不得不在第二个pgbouncer层中分层。 一层在应用程序服务器上运行,另一层在数据库服务器上运行。 它总共聚合了大约一百万个客户端进程的连接。这时候对于调优工作来说,大概只有40%的技术含量,剩下大概要花40%的蛮力,还需要10%的碰运气了。

 

进程可扩展性在每个主要版本中都在逐步提高,但与MySQL中使用的“每连接线程数”相比,最终该体系结构的性能还是受到了一定的限制。

 

有关更多技术详情,请参见https://brandur.org/postgres-connections  。

 

缺陷6:主键索引简直是浪费空间

 

PostgreSQL中的表有一个主键索引和称为堆的独立行存储。 其他数据库将它们集成在一起或支持“索引组织表”。 在PostgreSQL的这种机制下,主键查找过程直接指向具体的行数据,而不需要辅助获取完整行以及必需的额外CPU和I / O利用率。

 

PostgreSQL中的CLUSTER命令会根据索引重新组织表以提高性能,但实际上不适用于大多数OLTP的情况。 它是以互斥锁重写整个表,从而阻止任何读取或写入。 PostgreSQL不维护新数据的群集布局,因此该操作必须定期运行。 因此,如果你不能接受数据库长时间脱机,这种机制就无法使用。

 

但更关键的是,索引组织的表可以节省空间,因为索引不需要单独的行数据副本。 对于对于主要由主键覆盖的小行的表(例如联接表),这可以轻松地将表的存储空间减少一半。

 

比如针对下表,该表存储任意对象的社交获得的“赞”:

 

CREATE TABLE likes (

object_type INTEGER NOT NULL,

object_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY,

user_id BIGINT NOT NULL,

created_at TIMESTAMP WITH TIME ZONE NOT NULL,

PRIMARY KEY(object_type, object_id, user_id)

);

 

PostgreSQL将维护与基表存储区分开的主键索引。 该索引将为每行包含object_type,object_id和user_id列的完整副本。 每行28个字节中的20个(大约70%)将被复制。 我们想一下,如果PostgreSQL支持按索引组织的表,则不需要消耗所有这些额外的空间。

 

缺陷7:大版本升级可能需要停机

 

针对大型的数据库, 一些主要版本升级需要数小时的停机时间,才能实现数据的完全转移。如果使用典型的流复制机制,无法通过升级副本并执行故障转移来优雅地做到这一点。 而磁盘二进制格式在大版本之间不兼容,因此,主副本之间的有线协议实际上也是不兼容的。

 

希望逻辑复制最终将完全取代流复制,以便使得用户能够启用在线滚动升级策略。 我之前进行大规模水平部署时,我们在自定义基础架构上进行了重大工程投入,并且使用额外的基于触发器的复制系统(也用于分片迁移),最终才保证了在不停机的情况下进行这些升级的。

 

缺陷8:有点繁琐的复制设置

 

公平地说,MySQL的即用型复制要麻烦得多,但是与某些NoSQL存储(如MongoDB和Redis)或某些面向集群的复制系统(如MySQL Group Replication和Galera Cluster)相比,其易用性 以及避免边缘化的观点,在PostgreSQL中设置复制仍有很多不足之处。 从理论上讲,逻辑复制为第三方解决方案提供了更大的灵活性,以弥补这些空白,但到目前为止,使用它代替流复制存在很大的问题。

 

缺陷9:缺乏Planner hints机

 

一般数据库中, 通过Planner hints提示,可以引导用户使用自己不会使用的策略。 但PostgreSQL开发团队多年来一直拒绝支持Planner hints程序提示,认为这好像是一种更聪明的编译器参数的形式。

 

我确实理解他们的理由,这主要是为了防止不法用户使用应通过编写适当查询而解决的查询提示来攻击的问题。 但是,当你看到生产数据库在突然而意外的查询计划变动下急剧陷入崩溃时,没有任何提示也不知道怎么操作的时候就比较恼火了。

 

在许多情况下,给用户的hint提示可以在几分钟内缓解问题,并为工程团队节省他们为查询进行适当修复所需的时间,比如几小时甚至几天。 尽管有一些间接的解决方法涉及禁用某些查询计划器策略,但它们存在风险,因此绝对不应无任何限制地使用。

 

当然如果能同时满足这两种需求那就很完美了。

 

缺陷10:无块压缩

 

InnoDB在MySQL中的页面压缩通常可将存储空间减少一半,并且从性能角度来看几乎是“免费的”(不受影响)。PostgreSQL只支持自动压缩较大的数值,但这对于将数据存储在关系数据库中的最常用的方式没有用(很少有特别大的值)。 对于大多数RDBMS用例,一行通常为几百个字节或更少,这意味着压缩仅在跨多行或成块应用时才真正有效。

 

对于PostgreSQL核心的数据结构来说,块压缩确实很难实现,但是尽管有一些缺点, MySQL InnoDB存储引擎采用的“打孔”策略在实践中似乎效果还不错。

 

PostgreSQL世界中唯一被广泛使用的通用块压缩设置利用了ZFS,很多人觉得比较好用。 ZFS如今已成为Linux上的生产级实现,但无疑也带来了一些管理上的开销,而对于XFS或ext4等更“现成的”文件系统而言,ZFS却不存在。

 

以上,说了那么多。

 

建议你仍然使用PostgreSQL,而不要盲目使用其他任何方式来存储理想情况下要保存的数据。 但是呢,我会建议在你使用PostgreSQL的时候,从一开始就尝试弄清楚,有哪些特性是适合你的,还有哪些不适合。

 

PostgreSQL非常成熟,设计精良,功能丰富,没有国界限制,并且在绝大多数场景中都表现出色。 它也不受主要公司赞助商的约束,包括还有出色的文档资料,并拥有一个专业的,包容的社区。

 

好消息是,可以通过使用托管数据库服务(例如Heroku PostgreSQL,Compose PostgreSQL,基于PostgreSQL的Amazon RDS或基于PostgreSQL的Google Cloud SQL)来减轻或消除由本文中提到的许多引起痛苦的问题。 如果你可以使用其中一项服务,我强烈建议为了保护你的核心数据,请使用相关服务来避免这些问题

 

我很自豪地说,我已经在PostgreSQL的基础上构建了将近20年的软件,尽管存在缺陷,但我仍然是坚定的拥护者。 鉴于我多年来令人难以置信的开发团队所见证的进步,我可以说,大多数(如果不是全部)这些问题将在适当的时候得到解决。