在过去的几年里,软件开发社区对流行的开源关系数据库的热爱达到了狂热的程度。这个HackerNews线程覆盖了一篇标题为“PostgreSQL是世界上最好的数据库”的文章,它的裂缝中充满了可爱无情的马屁精,这是这种现象的一个很好的例子。虽然赞美当然是当之无愧的,但缺乏有意义的异议让我有点恼火。没有软件是完美的,那么PostgreSQL的缺陷到底是什么?自2003年以来,我一直在生产中使用PostgreSQL,部署范围从小型(千兆字节)到中型(到PB)。我的观点主要来自构建和运行至少持续可用的系统。不用说,多年来由于一些痛苦的生产问题,我获得了PostgreSQL的特殊特性的第一手经验。#1:灾难性的XID解决方案在此处阅读更多内容。可以这么说,这可以咬人。有许多关于此问题导致多天停机的案例。继续用谷歌搜索,你会发现很多穷人写下了他们踏上这个矿井的经历。几乎任何没有配备高级专家的简单PostgreSQL安装最终都会被使用。在未来的某个时候,XID可能会过渡到使用64位整数,但在那之前,我们一直坚持使用它。我认为至少我们可以庆幸,与某些飞机软件不同,有一个过程可以阻止它按预期发生。#2:故障转移时的数据丢失如果活动主服务器突然出现故障,运行中的流复制设置几乎肯定会丢失已提交的数据。有人可能会说:“这就是异步复制的代价。”但它不一定是。PostgreSQL支持使用仲裁提交的同步复制以实现容错持久性,但它具有更严格的性能限制,使应用程序复杂化。等待不占用系统资源,但事务锁会一直持有,直到转账被确认。因此,明智地使用同步复制会由于响应时间增加和争用增加而降低数据库应用程序的性能。这种固定的仲裁复制在某些情况下很有用,但我不建议将其用于一般用途的用例。它类似于Kafka的ISR复制,具有acks=all和quorummin_isr,但具有运行任意查询的事务关系数据库的所有细微差别。我目前不了解quorum-commit在非平凡规模的高可用性、高持久性复制方面的成功应用。如果是这样,请联系!GaleraCluster的组复制也不完美,但就关系数据库而言,它更接近理想状态。他们甚至鼓励地理分布式复制,这对于使用quorum-commit的PostgreSQL复制设置来说可能是灾难性的。#3:低效的复制传播腐败流式复制是目前生产部署中最常用的复制机制。它是一种物理复制形式,这意味着它会复制磁盘上二进制数据本身的变化。每次需要通过写入操作修改磁盘上的数据库页面(8KB)时,即使它只是一个字节,也会将使用请求的更改编辑的整个页面的副本写入预写日志(WAL).物理流式复制利用现有的WAL基础设施作为流式传输到副本的更改日志。更新:一些人指出PostgreSQL只需要为每个WAL检查点执行一个完整的页面写入。这是事实,但在大多数真实系统中,大多数写入将遵循幂律分布,最终在检查点之间的唯一页面上结束。但是,更重要的是:在预测系统行为时,正确的方法是假设情况更昂贵,特别是如果它取决于应用程序的不可预测和高度动态的行为。例如,对于物理复制,大型索引构建会创建大量WAL条目,从而很容易成为复制流程的瓶颈。页粒度读取-修改-复制过程导致主机上硬件引起的数据损坏,更容易传播到副本,这是我在生产中亲眼目睹的。这与逻辑复制形成对比,逻辑复制仅复制逻辑数据更改。大型索引构建,至少在理论上,只会导致通过网络复制单个命令。虽然PostgreSQL很早就支持逻辑复制,但大多数部署都使用物理流复制,因为它更健壮、支持更广泛、更易于使用。#4:MVCC频繁出现垃圾与大多数主流数据库一样,PostgreSQL使用多版本并发控制(MVCC)来实现并发事务。然而,它的具体实现往往会给垃圾行版本及其清理(VACUUM)带来操作上的麻烦。通常,UPDATE操作会创建任何已修改行的新副本(或“行版本”),并将旧版本保留在磁盘上,直到它们被清除。多年来,情况稳步改善,但这是一个复杂的系统,对于任何第一次接触该问题的人来说都是一个黑匣子。例如,了解仅堆元组(HOT)及其启动时间对于大量就地更新工作负载(例如持续维护一致的计数器列)而言可能是成败攸关的。默认的auto-vac设置大部分时间都有效,但是,天哪,当它不起作用时。相反,MySQL和Oracle使用重做和撤消日志。他们不需要类似的后台垃圾收集过程。他们所做的权衡主要是事务提交和回滚操作的额外延迟。也许在遥远的未来某个时候,Zapp会拯救我们所有人。#5:每个连接的进程=大规模的痛苦PostgreSQL为每个连接分叉一个进程,因为大多数其他数据库使用更高效的连接并发模型。由于有一个相对较低的阈值,超过该阈值添加更多连接会降低性能(大约2个核心)并最终导致性能下降,这又是一个高阈值(难以估计,高度依赖于工作负载),这可能导致硬-调整问题。使用连接池的标准方法当然可以解决问题,但会引入额外的体系结构复杂性。在一个特别大的部署中,我最终不得不在第二个pgbouncer层中分层。一层运行在应用程序服务器上,另一层运行在数据库服务器上。它总共聚合了来自大约一百万个客户端进程的连接。调整需要40%的黑魔法、40%的蛮力和10%的纯运气。每个主要版本都逐步改进了进程可伸缩性,但最终与MySQL中使用的“每个连接的线程数”相比,该体系结构在性能上有所限制。有关更多技术深度,请参阅https://brandur.org/postgres-connections。#6:主键索引是“太空猪”PostgreSQL中的表有一个主键索引和一个称为堆的单独行存储。其他数据库将它们集成在一起或支持“索引组织表”。在这种安排下,主键查找过程直接导致行数据,而无需二次获取完整行和必要的额外CPU和I/O利用率。PostgreSQL中的CLUSTER命令根据索引重组表以提高性能,但对于大多数实际的OLTP情况并不实用。它使用互斥量重写整个表,防止任何读取或写入。PostgreSQL不维护新数据的集群布局,因此必须定期运行此操作。因此,只有当您可以使数据库长时间脱机时,它才真正有用。但更关键的是,索引组织表可以节省空间,因为索引不需要行数据的单独副本。对于主要由主键覆盖的小行的表(例如连接表),这可以轻松地将表的存储空间减少一半。考虑下表,它存储了任意对象的社交“喜欢”:object_type,object_id,user_id));PostgreSQL将维护与基表存储分开的主键索引。索引将包含每一行的object_type、object_id和user_id列的完整副本。每行28个字节中的20个(~70%)将被复制。如果PostgreSQL支持索引组织表,则不会消耗所有这些额外空间。#7:主要版本升级可能需要停机一些主要版本升级需要停机数小时才能为大型数据库转换数据。使用典型的流式复制机制,不可能通过提升副本并将其故障转移来优雅地完成此操作。磁盘上的二进制格式在主要版本之间不兼容,因此,主副本之间的线路协议实际上也是不兼容的。希望逻辑复制最终能完全替代流复制,实现在线滚动升级策略。当我进行大规模横向扩展部署时,我们在自定义基础设施方面进行了大量工程投资,以使用额外的基于触发器的复制系统(也用于分片迁移)而无需停机这些升级。#8:有点麻烦的复制设置公平地说,MySQL开箱即用的复制要麻烦得多,但与一些NoSQL存储(如MonNoDB和Redis)或面向集群的复制系统(如MySQLGroupReplication和GaleraCluster)相比,从易用性和避免边缘情况的角度来看,它在PostgreSQL中设置复制还有很多不足之处。从理论上讲,逻辑复制为第三方解决方案提供了更大的灵活性来填补这些空白,但到目前为止,在使用流复制代替它方面存在一些重大警告。#9:荒谬的无计划提示教条使用规划器提示,查询可以指示查询规划器使用原本不会使用的策略。PostgreSQL开发团队多年来一直拒绝支持查询规划器提示,这似乎是一种足够聪明的编译器参数形式。我确实理解他们的理由,主要是为了防止用户使用查询提示攻击问题,应该通过编写适当的查询来解决。但是,当您发现您的生产数据库在突然和意外的查询计划更改下突然全面崩溃时,这种哲学似乎是残酷的家长式作风。在许多情况下,给计划者的提示可以在几分钟内缓解问题,从而使工程团队可以花费数小时或数天的时间来对查询进行适当的修复。虽然有一些间接的解决方法涉及禁用某些查询规划器策略,但它们存在风险,在任何时间压力下都不应使用。那个象牙塔一定很好。#10:无块压缩InnoDB在MySQL中的页面压缩通常将存储空间减少一半,从性能的角度来看几乎是“免费”的。PostgreSQL会自动压缩较大的值,但这对于在关系数据库中存储数据的最常见方式没有用。对于大多数RDBMS用例,一行通常只有几百个字节或更少,这意味着压缩只有在跨多行或分块应用时才真正有效。块压缩确实很难用PostgreSQL的核心数据结构实现,但尽管存在一些缺点,MySQLInnoDB存储引擎采用的“打孔”策略在实践中似乎效果很好。2020年4月7日更新:“Facebook上的MySQL”成名的马克卡拉汉质疑我关于打孔压缩“在实践中效果很好”的说法。事实证明,世界上最大的MySQL安装从未像我之前认为的那样使用打孔压缩。然而,他们确实成功地使用了对早期版本稍作修改的InnoDB压缩版本,但在几年前迁移到MyRocks之前,他们取得了成功。虽然拳击压缩似乎对某些人有效,但有一些警告使拳击压缩不像本垒打。如果您正在运行Percona版本的MySQL,那么MyRocks是更好的选择。如果不是,经典的InnoDB表压缩似乎是闪存中读取密集型工作负载的更安全选择。Mark没有指出主要生产问题的任何具体实例,但指出他“怀疑文件系统是为每页打孔而设计的,我会担心不明显的故障。“PostgreSQL世界中唯一广泛使用的通用块压缩设置使用ZFS,它似乎对人们来说工作得很好。ZFS是当今Linux上的生产级现实,但它肯定会引入一些管理开销,而XFS或ext4,ZFS不存在。说了这么多......你可能仍然应该使用PostgreSQL而不是其他任何东西来存储你理想中想要保留的数据。一般来说,我建议从PostgreSQL开始,然后尝试弄清楚为什么它不适用于您的用例。PostgreSQL非常成熟,设计良好,功能丰富,通常没有锋利的边缘,并且适用于绝大多数用例。它也不受主要公司限制的赞助的供应商,包括出色的文档,并拥有一个专业的、包容的社区。好消息是,您可以通过使用托管数据库服务来做到这一点,例如HerokuPostgreSQL、ComposePostgreSQL、AmazonRDSforPostgreSQL或GoogleCloudSQL)来减轻或消除由本文中提到的许多问题引起的痛苦。如果您可以使用其中一项服务,为了对万物的热爱,请使用!基于将近20年构建在其之上的软件,尽管它存在缺陷,但我仍然是坚定的拥护者。鉴于我多年来见证了一个令人难以置信的开发团队所取得的进步,我可以说大多数(如果不是全部)这些问题将在适当的时候得到解决。
