慎重选型:PG还是MySQL?Uber的案例不能作为参考!

高强 2016-08-09 09:54:33

本文原作者:Markus Winand,原文链接:www.yumpu.com/en/document/view/53683323/migrating-uber-from-mysql-to-postgresql

译者:高强,DBAplus社群联合发起人,擅长Oracle、AIX、Linux、PostgreSQL和DB2等产品的实施、运维和故障处理。曾参与多个省级政府单位项目的实施和运维工作,具有丰富的运维经验。

 

编者按

◆  ◆  ◆  ◆  ◆ 


Uber的数据库从PostgreSQL更换到MySQL如此大的动作,引起了业界的热烈讨论,歪果仁也做出了细致的分析。为此,DBAplus社群也把最新出炉的资讯翻译过来与大家分享,希望能一起讨论、共同提升。

 

作为一个独立的第三方公益社群,我们无意支持谁不支持谁,只要是纯技术的探讨,都欢迎大家在留言区回复讨论,各抒己见。

 

正文

◆  ◆  ◆  ◆  ◆ 


几天前Uber发布了文章“Uber为何将PostgreSQL换成MySQL”。我没有立刻阅读该文章,因为我当时正忙于家里的事。但我的邮箱被填满了关于这个问题的邮件——比如“PostgreSQL真的那么怂吗?”据我的了解,PostgreSQL没有那么糟,因此这些邮件让我很想知道这篇文章到底在搞什么鬼。下面我来分析一下Uber文章中的问题。


在本文中,我会解释为什么我认为Uber的文章不能作为数据库选型的一般性建议,为什么MySQL可能很适合Uber,还有为什么切换成功后可能会导致更多问题,而不是仅仅衡量数据存储这么简单。


关于Update操作

首要的问题,也是Uber一文中着重强调过但细节提供不尽完善的问题:PostgreSQL总是在更新行记录的时候,更新表上所有相关的索引。而使用InnoDB的MySQL,仅需要更新包含变更字段的索引。PostgreSQL所用的方法会因为更新了非索引列而导致更多的磁盘I/O(文中叫“写放大”)。如果这对于Uber来说是个大问题,那更新操作可能会占他们整体负载中很大一部分开销。


但是,Uber文章中存在着一些问题,文章中并未提及PostgreSQL的Heap-Only-Tuples(HOT)特性。在PostgreSQL的源码(地址:https://github.com/postgres/postgres/blob/master/src/backend/access/heap/README.HOT)中有说明,HOT对于个别案例如“当一行数据被反复的更新,并且索引列上没有数据变化”。在这种情况下,PostgreSQL可以实现:当新旧行版本存储在同一个页上的时候,数据库更新,并且不碰任何索引。这种情况可以通过设置fillfactor来调整更新空间的保留比例。假设Uber工程师知道这方法,就意味着HOT没有解决他们的问题,因为这些高频的update操作至少涉及一个索引列。


这项假设也获得了文中该句的支持:“如果我们有一张定义了一打(12个)索引的表,在一个字段上做了更新,该字段只涉及一个索引则必会传报到所有的12个索引中,以便传递新数据行的ctid”。他明确地说到“只涉及单个索引”这是一个边缘情况---仅有1个索引---否则PostgreSQL的HOT特性便会解决该问题。


作者旁注:我真的非常好奇他们使用的索引是否会减少---对于我来说重新设计索引是项挑战。但是,有个非常大的可能性是:这些索引的使用太保守、太节约,未能起到他们的重要作用。


关于SELECT操作

另一个关于Uber使用案例的描述也引起了我的注意:文中解释了MySQL的InnoDB使用聚簇索引并且也承认“这个涉及意味着InnoDB在进行次关键字查找的时候相比PostgreSQL有一点劣势,因为必须使用InnoDB的两个索引进行搜索,Postgres仅用一个。”我之前曾经写过该问题: (“the clustered index penalty”) in context of SQL Server.(原文地址:http://use-the-index-luke.com/blog/2014-01/unreasonable-defaults-primary-key-clustering-key)


引起我注意的是,Uber将索引惩罚描述为一个“小缺点”。而在我看来,如果你在次索引上运行很多查询的话,这是一个相当大的劣势。如果对于Uber来说,这仅仅是一个小缺点的话,这可能暗示他们的索引使用的相当少。也就是说,Uber几乎全部都用主键进行扫描(然后是非聚簇索引去承担惩罚)。注意我写的是“查找”并不是“查询”。原因是聚簇索引惩罚影响任何使用where子句的语句——不仅是select。它还影响经常基于主键的更新操作。


最后还有另外一个遗漏提示我关于Uber的疑问:他们没有提到PostgreSQL的有限的纯索引扫描。尤其是在一个更新任务繁重的数据库中,PostgreSQL纯索引扫描的执行确实不太令人满意。我甚至说过这是一个影响我大多数客户的问题。我早就在2011年写过博客。在2012年的《PostgreSQL 9.2 got limited support of index-only scans 》(仅对绝大多数静态数据有效)。在2014年,我甚至在PgCon上提出了我在这方面的关心:https://wiki.postgresql.org/wiki/Pgcon2014unconferenceBitmapIndexOnlyScan。


但是,Uber没有抱怨相关问题。所以查询速度不是他们的问题。我猜测查询速度已经通过在feplicas(后面会提到)和可能对主键方面做了限制这样的工作中解决了。


到现在为止,Uber的使用案例似乎更适合键值存储。再进一步看:InnoDB是一个非常坚固的并且是流行的键值存储引擎。甚至有很多的包可以将InnoDB与一些(非常有限)前端SQL绑定:MySQL和MariaDB是最流行的,我认为。


但是严肃地说:如果你需要基本的键值存储并且偶尔会运行一条简单的SQLchaxun ,MySQL(或者MariaDB)是合理的选择。我猜这至少是一个比随机NoSQL键值存储更好的选择,满足一个初步的需求,提供有限的类似SQL的查询语言。而Uber在另一方面则是在InnoDB和MySQL上建立了他们自己的东西(“Schemaless”)。


索引平衡

文章中最后一个关于索引的说明:使用了字眼“rebalancing”在B树索引中。甚至引用了维基百科文章“Rebalancing after deletion.”不幸的是,维基百科上的文章不总是适用于数据库索引,因为维基百科上维持着的描述到的算法会至少满足一个节点的一半的需求。为了提高并发性,PostgreSQL使用了 Lehman, Yao 的变异的 B-trees,它提高了该需求,因此能够也允许了稀疏索引。附注一点,PostgreSQL仍然会去除索引中的空页。但是,这只是一个侧面的问题。


真正让我担心的是这一句:“B-tree的一个基本问题是它们必须定期重新平衡,...”这里我想澄清的是:这不是一个每天都需要运行的周期性的行程。索引的平衡是被每个索引的更改所维护的(甚至更糟,嗯哼?)。但是这文章继续写到“...并且这些平衡操作可以完全改变树的结构,因为子树被移动到了硬盘上的新位置。”如果你现在认为“rebalancing”包括大量的数据移动,那你就误解了它。


B-tree中最重要的操作就是节点分裂。就像你可能猜到的那样,一个节点分裂发生在节点不能承载这个属于它的新条目时。为了给你一个大概的数字,发生一次可能涉及100次插入。节点分裂分配新节点,移动一半的条目到新节点并且使其与前面的、后面的还有父节点连接。在这里Lehman, Yao省略了大量的锁操作。在一些情况下,新节点不能被直接加入到父节点,因为父节点没有足够的空间来存放子条目。在这种情况下,父节点被分裂并且一切重复进行。


在最坏的情况下,分裂会上升到根节点,在根节点分裂的同时会在其上方放置新的根节点。在此次案例中,一个B-tree曾经变得更深。注意,根节点有效分裂会使整个树变浅并且保持均衡。但是,这不意味着大量的数据移动。在最坏的情况下,有可能会初级每层的3个节点和新根节点。要明确:绝大多数现实世界中的索引没有超过五层的。甚至更要明确:在最差的情况下——根节点分裂——10亿次插入操作可能会发生五次分裂。


在其他情况下可能不需要遍历整个树。毕竟,索引维护不是“周期性的”,甚至不是很频繁,并且永远都不会改变树的结构。至少是磁盘上的物理结构。


物理复制

我在此文章中关注PostgreSQL的下一个主要内容:物理复制。文章在谈到索引“再平衡”话题的另一个原因是Uber曾经遇到一个PostgreSQL复制的Bug,导致下游服务器数据损坏(该Bug“仅影响了部分Postgres 9.2版本,并且已经早就被修复了”)。


因为PostgreSQL 9.2仅提供物理复制,一个复制bug“可能引起很大部分的二叉树变得完全无效”。详细来说:如果一个节点分裂被不正确的复制出去,那么它再也不会指向正确的子节点,该子节点会变成无效。这是绝对正确的,就像这样一句话: “如果有Bug,坏事便会发生”。你不需要改变大量数据来破坏树结构:一个小小的指针足够了。


Uber文章提到过物理复制的其他问题:1、巨大的复制流量;2、部分取决于update引起的写放大;3、并且升级数据库版本的时候需要停机窗口。尽管第一个对于我来说有意义,但是我真的是不敢苟同第二个。(但是在PostgreSQL-hacker的邮件列表里曾经有人提到过)


根据PostgreSQL文档,有两种方式处理这个问题:(1)延迟流复制,提供一个可配置的超时,这样读事务可以正常完成。如果一个查询没有及时完成,杀掉查询并且继续应用流复制。(2)配置复制提供回馈,发送给主库,发送的内容就是关于正在运行的查询,以便主库不会用vacuum清理任何从库仍然需要的行版本信息。Uber文章提及了第一个方法,但是从未提到过第二个方法。相反,该文章还指责了其开发人员。


关于开发者

文章中得意地写到:“比如,一个开发人员有一些代码,用于给用户通过邮件发送收据。取决于是如何写的,该代码可能隐式的有一个数据库事务,会保持会话直到邮件完成发送。尽管让你的代码保持着数据库事务同时还执行者无关的I/O阻塞是不好的,但现实就是绝大多数开发人员不是数据库专家,并且不可能都懂得这个问题,特别是使用了ORM,这回掩盖很多底层的细节,例如事务。”


很不幸,我理解并且同意这个论点。不仅对于“绝大多数开发人员不是数据库专家”,我甚至会说绝大多数工程师都有对数据库都对数据库了解得太少,因为每个接触SQL的开发人员都需要了解事务——不仅仅是数据库专家。


为程序员提供SQL培训是我目前的工作。我在各种规模的公司都做过这样的培训。如果说有一件事我可以确定的话,那就是认识到这些SQL写得非常糟糕。文中的“打开的事务”问题恰好让我确定了几乎没有任何开发人员知道只读事务的存在。绝大多数开发人员仅仅知道事务可以用来做回退。我曾经遇到过有很多这样的误解的开发者,所以我已经准备了幻灯片来解释一下并且已经给好奇的读者上传了。


关于成功

这是我想写的最后一个问题:一个公司雇佣越多的员工,那这个公司的水平就越接近其平均水平。夸张的说,如果你雇佣整个星球的人,你将准确地获得所有人的平均水平。雇佣更多的人确实仅是提升了采样规模。


打破这个规律的两种方法是:


  1. 只雇佣最好的开发者,这种方法非常困难,因为你可能要花费很长时间才能找到合适的人。

  2. 雇佣中等水平的人并提供培训。这就需要一个新员工入职后有比较长的上手时间,也可能需要现有的员工花时间去对新员工进行培训。两种方法面临共同的难题都是时间。因为你的业务要保证快速发展,单你可能没多少时间去等待,所以你只能雇佣那些对数据库了解并不多的开发者(2014年的经验数据)。也就是说,对一个想要快速发展的公司而言,更换技术比更换人员要来得容易。


随着时间的变化,成功因素也会影响技术栈的需求。在早期阶段,创业者需要开箱即用的技术,这些技术随即可用并且足够灵活,可以用于他们的业务。SQL是个很好的选择,因为它确实是灵活的(你可以用任何方式查询你的数据)并且很容易找到至少懂一点SQL的人。很好,那我们开始吧!对于很多也许是绝大多数公司来说,故事就此结尾了。甚至这些公司获得了普通程度的成功并且他们的业务也增长了,他们也可能会永远保持在有限的SQL数据库使用上。


有一小撮幸运的创业者最终超越了SQL的使用。截止到事发之时,他们有方法访问更多(几乎是无限的?)的资源然后一些奇妙的事情发生了:他们认识到如果替换掉通用的数据库,然后针对系统进行定制开发的话会解决很多问题。就在这时一个新的NoSQL数据库诞生了。在Uber里,他们叫他Schemaless。


Uber对于数据库的选型

到目前为止,我相信Uber不需要像他们文章所建议的那样用MySQL替换掉PostgreSQL。似乎他们实际上是用自己定制的解决方案替换了PostgreSQL,而这个方案恰好是MySQL/InnoDB目前锁支持的。


似乎文章仅解释了作为Schemaless无模式的数据后端来说,MySQL/InnoDB相比PostgreSQL来说是一个更合适。对于使用Schemaless的人来说,可以听从Uber的建议。


可悲的是,文章中有些地方说明不详细,只是给读者留下一个PostgreSQL很糟糕的印象。而没有提及,为何他们当年要求采用Schemaless,并在2013年从MySQL迁移到PostgreSQL。


精选专题(点击蓝色标题可阅读全文)

近期活动:

Gdevops全球敏捷运维峰会广州站


峰会官网:www.gdevops.com

活动预告