PostgreSQL一线生产经验分享:我讨厌PostgreSQL的10件事 | Rick Branson


在过去的几年中,软件开发社区对流行的开源关系数据库的热爱已经达到了一个高潮。Hacker News主题有一个标题为“ PostgreSQL是世界上最好的数据库”的文章,它的字里行间充斥着讨人喜欢的正能量,并无条件地赞美它。
虽然这种称赞很多是当之无愧的,但缺乏有意义的异议却让我有些烦恼。没有软件是完美的,那么PostgreSQL的缺陷到底是什么?
自2003年以来,我就一直在生产中使用PostgreSQL,其部署范围从小(千兆字节)到中等(到PB级)。多年来,我经历了一些痛苦的生产问题获得了有关PostgreSQL特殊特性的第一手经验。

#1:灾难性的XID解决方案
这里可以阅读更多详细情况。这里我只想说,这一个可以啃硬骨头经历。有许多关于此问题导致多天停机的故事。可以用Google搜索它,您会发现许多可怜的人叙述他们踏到这枚地雷的时间经历。

#2:故障转移可能会丢失数据
如果活跃的主服务器突然出现故障,那么运行中的流复制设置几乎肯定会丢失已提交的数据。有人可能会说:“ 异步复制的代价就是这样。”但不一定非要这样。PostgreSQL支持具有法定仲裁提交的同步复制,以实现容错的持久性,但是它具有[url=https://www.postgresql.org/docs/current/warm-standby.htmlSYNCHRONOUS-REPLICATION-PERFORMANCE]严格的性能范围[/url],使应用程序复杂化。

等待不占用系统资源,但是事务锁将继续保留,直到确认传输为止。结果,由于响应时间增加和争用增加,谨慎使用同步复制将降低数据库应用程序的性能。

这种固定的仲裁复制在某些情况下很有用,但我不建议在通用用例中推荐它。它类似于Kafka的ISR复制,具有acks = all和一个最小仲裁数问题,但是可运行任意查询的事务性关系数据库中差别细微。我目前尚不知道可以成功应用仲裁提交来应对非平常的规模实现高可用性,高耐用性(高可靠性)的复制。如果有,请联系!
就关系数据库而言,Galera Cluster的组复制也不完美,但更接近理想状态。他们甚至鼓励按地理分布的复制,这对于使用仲裁提交的PostgreSQL复制设置很可能是灾难性的。

#3:低效率的复制会传播中断失败
到目前为止,流复制是生产部署中最常用的复制机制。它是物理复制的一种形式,这意味着它可以复制磁盘二进制数据本身中的更改。
每次需要通过写操作修改磁盘上的数据库页面(8KB)时,即使只是一个字节,也需要因为更改请求编辑的整个页面的副本写入预写日志(WAL)。物理流复制利用此现有的WAL基础结构作为流到副本的更改日志。
更新:有些人指出PostgreSQL仅需要在每个WAL检查点执行一次全页写操作。的确如此,但是在大多数实际系统中,大多数写入将遵循幂律分布,最终出现在检查点之间的唯一页面上。但是,更重要的是:在预测系统行为时,正确的方法是假设情况更为昂贵,尤其是如果它取决于应用程序的难以预测和高度动态的行为时。

例如,使用物理复制,大型索引构建会创建大量WAL条目,从而很容易成为复制流的瓶颈。在页面粒度的读-修改-复制过程中,主服务器可能会将硬件引起数据损坏更容易传播到副本复制服务器,我已经在生产中亲眼目睹了好几次。
这与逻辑复制相反,后者仅复制逻辑数据的更改。至少从理论上讲,大型索引构建只会导致在网络上复制单个命令。尽管PostgreSQL支持逻辑复制已有相当长的一段时间了,但是大多数部署都使用物理流复制,因为它更健壮,支持范围更广并且更易于使用。

#4:MVCC垃圾频发
与大多数主流数据库一样,PostgreSQL使用多版本并发控制(MVCC)来实现并发事务。但是,它的特定实现常常会给垃圾行的版本化及其清理(VACUUM)带来操作上的麻烦。一般而言,UPDATE操作会创建任何已修改行的新副本(或“行版本”),并将旧版本保留在磁盘上,直到可以清除它们为止。
多年来,这种情况一直在稳步改善,但它是一个复杂的系统,对于任何初次接触该问题的人来说都是一个黑匣子。例如,了解纯堆元组(HOT)及其何时启动对于繁重的就地更新工作负载(如连续保持一致的计数器列)来说可能事关成败的。默认的autovacuum设置在大多数情况下都起作用,但是如果不起作用,则good lord(烧香拜佛吧)。
相反,MySQL和Oracle使用重做和撤消日志。他们不需要类似的后台垃圾收集过程。他们做出的权衡主要是事务提交和回滚操作的额外延迟。
将来的拯救者解决方案:zheap saves us all.

#5:每次连接处理=在大规模上很痛苦
PostgreSQL为每个连接派生一个进程,因为大多数其他数据库都使用更有效的连接并发模型。由于存在一个相对较低的阈值,在该阈值上添加更多的连接会降低性能(约2个内核),最终会导致性能下降,而这又是一个较高的阈值(难以估计,高度依赖工作负载),这将导致调优困难。
使用连接池的标准方法当然可以解决问题,但是会带来额外的架构复杂性。在一个特别大的部署中,我最终不得不在第二个 pgbouncer层中分层。一层在应用程序服务器上运行,另一层在数据库服务器上运行。它总共聚合了大约一百万个客户端进程的连接。调过程有40%属于艺术性,40%用蛮力和10%的纯正运气。
进程可伸缩性在每个主要版本中都在逐步提高,但与MySQL中使用的“每连接线程数”相比,最终该体系结构的性能受到了一定的限制。
有关更多技术深度,请参见https://brandur.org/postgres-connections

#6:主键索引是“Space hog”(贪婪耗费空间)
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 ReplicationGalera Cluster)相比,存在易用性和sharp-edge-avoidance等问题,PostgreSQL中设置复制留下了很多不理想的后果。虽然逻辑复制从理论上为第三方解决方案提供了更大的灵活性,以弥补这些空白,但到目前为止,使用它代替流复制存在一些很多的警告

#9:没有计划提示是荒谬的教条
计划者提示使查询能够指示查询计划者使用原本不会使用的策略。PostgreSQL开发团队多年来一直拒绝支持查询计划程序提示,这似乎是一种足够聪明的编译器参数的形式。
我确实了解他们的理由,这主要是关于防止用户使用应通过编写适当查询而解决的查询提示来攻击问题。但是,当您看到生产数据库在突然而意外的查询计划变更下激增至完全崩溃时,这种哲学似乎是残酷的家长式作风。
在许多情况下,给计划者的提示可以在几分钟内缓解问题,并为工程团队提供他们为查询进行适当修复所需的时间或几天。虽然是涉及禁用某些查询规划战略,一些间接的解决方法,他们是有风险的,绝对不应该任何时间压力下使用。

#10:无块压缩
InnoDB在MySQL中的页面压缩通常可以将存储空间减少一半,并且从性能角度来看几乎是“免费的”。PostgreSQL将自动压缩较大的值,但这对于将数据存储在关系数据库中的最常用的方式没有用。对于大多数RDBMS用例,一行通常为几百个字节或更少,这意味着压缩仅在跨多行或成块应用时才真正有效。
对于PostgreSQL核心的数据结构来说,块压缩确实很难实现,但是尽管有一些缺点 MySQL InnoDB存储引擎采用的“打孔”策略在实践中似乎效果很好。

2020年4月7日更新:“ MySQL在Facebook上”一举成名的Mark Callaghan在此质疑我的说法,即打孔压缩hole-punching在实践中“相当不错”。事实证明,正如我之前认为的那样,世界上最大的MySQL安装从未使用过打孔压缩。他们确实成功地使用了较旧版本的InnoDB压缩的成功,但是在几年前迁移到MyRocks之前。
虽然打孔压缩对某些人似乎确实有效,但是有些注意事项。如果您正在运行Percona的MySQL版本,那么MyRocks是更好的选择。如果不是这样,对于闪存中非常繁重的读取工作负载,经典的InnoDB表压缩似乎是一个更安全的选择。马克没有指出主要生产问题的任何特定情况,但指出他“怀疑文件系统是为每页打孔而设计的,我会担心隐晦的故障。”

在PostgreSQL世界中广泛使用的通用块压缩也用在leverages ZFS中,它似乎对人们来说确实很好用。ZFS如今已成为Linux上的生产级现实,但无疑带来了一些管理上的开销,而这些开销在XFS或ext4等更“现成的开箱即用”文件系却不存在。

总结...
您可能应该仍然使用PostgreSQL,通常,我建议从PostgreSQL开始,然后尝试弄清楚为什么它不适用于您的用例。
PostgreSQL非常成熟,设计精良,功能丰富,通常没有锋利的边缘,并且在绝大多数用例中都表现出色。它也不受主要公司赞助商的约束,包括出色的文档资料,并拥有一个专业的,包容的社区。
好消息是,可以通过使用托管数据库服务(例如Heroku PostgreSQLCompose PostgreSQL用于PostgreSQL的Amazon RDS用于PostgreSQL的[url=https://cloud.google.com/sql]Google Cloud SQL)[/url]来减轻或消除由本文中提到的许多问题引起的痛苦。如果您可以使用其中一项服务,为了爱所有神圣的东西,请这样做!
我很自豪地说,我已经在PostgreSQL的基础上构建了将近20年的软件,尽管存在缺陷,但我仍然是坚定的拥护者。鉴于我多年来由其令人难以置信的开发团队所见证的进步,我可以说,大多数(如果不是全部)这些问题将在适当的时候得到解决。