转载

Uber的底层存储从Postgres换成MySQL之后

最近,Uber软件工程师Evan Klitzke写了一篇 文章 介绍系统的底层存储由 Postgres 换成MySQL的原因。

背景

早期的Uber后台软件由Python写成,数据存储使用Postgres。后期随着业务的飞速发展后台架构也变化巨大,演进成了微服务加数据平台。数据存储也由Postgres变成了 Schemaless ——Uber自主研发的以MySQL做为底层的高可用数据库。Uber的数据库主要存储的是Trip数据,就是一个叫车订单从下单起,到上车、下车、付费等的全过程跟踪及处理。从2014年初起,由于业务增长迅猛,Uber的原有基础架构已经无法继续支撑业务。改进的项目花了将近一年时间。

Uber的底层存储从Postgres换成MySQL之后

对于新的数据库存储系统,Uber的主要关键需求是:

  1. 要有能力通过增加服务器而线性地增加容量。增加服务器不但要增加可用的硬盘容量,还要减少系统的响应时间。
  2. 需要有写缓冲能力,万一持久化到数据库失败时,仍可以稍后重试。
  3. 需要通知下游依赖关系的方式,数据变更要能无损的通知出去。
  4. 需要二级索引。
  5. 系统要足够健壮,可以支持7*24服务。

在调查对比了Cassandra、Riak和MongoDB等等之后,Uber技术团队没有发现能完全满足需求的现成解决方案。而再考虑到数据可靠性、对技术的把握能力等因素,他们决定自己开发一套数据库管理系统——Schemaless,一个键值型存储库,可以存放JSON数据而无需严格的模式验证,是完全的无模式风格。用MySQL作底层存储,其中只有顺序写入,在MySQL主库故障时支持写入缓冲。并有一个数据变更通知的发布-订阅功能(命名为trigger),支持数据的全局索引。

Schemaless项目技术负责人Jakob Thomsen 认为 :

Schemaless的强大与简单更多是因为我们在存储节点中使用了MySQL。Schemaless本身是在MySQL之上相对较薄的一层,负责将路由请求发送给正确的数据库。借助于MySQL第二索引及InnoDB的BufferPool,Schemaless的查询性能很高。

在Evan Klitzke的文章中,他是从Postgres与 Innodb 的底层存储机制对比开始的,后面提到了他们碰到的若干Postgres问题:

  • 写入效率不高
  • 数据主从复制效率不高
  • 表损坏问题
  • 从库上的 MVCC 支持问题
  • 难于升级到新版本

在Postgres的底层设计中,它的行数据是不可修改的,每个不可修改的行都叫做“元组”,每个唯一的元组都由一个唯一的 ctid 标志,ctid也就实际指出了这个元组在磁盘上的物理偏移量。这样对于一行修改过的数据来说,就会对应着在物理上有多个元组。表是有索引的,主键索引和第二索引都以B树组织,都直接指向ctid。

除了ctid之外还有一个关键字段prev,它的默认值为null,但对于有数据修改的记录,新的元组里面的prev字段里存储的就是旧元组的ctid值。

Uber的底层存储从Postgres换成MySQL之后

与Postgres相对应的是,MySQL的InnoDB引擎主键索引和第二也都以B树组织,但是索引指向的是主键,而主键才真正指向数据记录。而且,InnoDB的数据是可以修改的。两者实现MVCC的机制不同,MySQL依靠UNDO空间中的 回滚段 ,而不是象Postgres依靠在数据表空间对同一条数据保持多份。

Uber的底层存储从Postgres换成MySQL之后

Postgres和InnoDB都通过 WAL (Write Ahead Log)来保证数据可以在数据库上安全写入,但对于主从库的数据复制实现原理并不同。Postgres会直接把WAL发送到从库上,让从库也执行WAL来复制数据。而MySQL则是发送Binlog,在从库上应用Binlog。

由此,再来看看Uber对于Postgres有哪些不满意:

写放大

一般来说大家介意 写放大 的问题是由于对SSD磁盘的使用。SSD磁盘是有寿命的,它的写入次数是有限的(虽然数字很大)。这样如果应用层只是想写入少量数据而已,但数据落入磁盘时却变大了许多倍,那大家就会比较介意了。比如你只是想写入1K的数据,可是最终却有10K数据落盘。

Postgres的写放大问题主要表现在对有索引的表进行数据更新上。因为Postgres的索引都是指向元组的ctid,而元组又是不可更新的,所以当你更新一条记录时,它会创建一个新的元组存入磁盘,并且要针对所有的索引,为每个索引都创建一条新记录来指向新的元组,不管你更改的字段和这个索引有没有关系。这样对于WAL来说,Postgres更改一条记录操作会写入新的完整记录,再加上多条索引记录。

作者注:不过MySQL的InnoDB其实也是有写放大问题的。InnoDB是以数据页的形式组织数据的,Linux上默认数据页的大小是16K。这样当你更改了一条记录时,最终会把这条记录所在的数据页整页刷回磁盘,设想一下你可能只是改了一个小字段,也许只有4个字节,可是最终却会导致16K字节的写入。

另外,Postgres的这个设计也是有其好处的,它的第二索引直接指向元组的ctid,这样在读取数据时效率就非常高。相对应地,通过MySQL的第二索引去读数据会经历“第二索引——主键——数据”的过程,MySQL的读效率不如Postgres。这是一个经典的读写性能权衡问题,在此Evan没有给出具体的数字让我们体会他们的业务特征。

主从复制

Postgres的写放大问题最终也反应在了主从复制的日志传输上,变成了流量放大问题。Postgres的主从复制传输的是WAL日志,所以对于一条数据更新来说,它要传输新的数据,还要传输这张表上每一条索引修改的日志。这样的流量放大在同一机房内还稍可接受,但对于跨机房的情况,传输速度和价格等问题让Uber产生了顾虑。Uber是有跨机房从库的,一方面是容灾,另一方面是WAL的备份,以备有时需要靠它来搭建新的从库。

MySQL的确没有引起流量放大。MySQL的主从复制依靠的是Binlog,它只是记录这条数据的修改,而不在乎这张表上到底有多少索引,所以可以认为与Postgres相比,它的Binlog是一种对数据修改的“逻辑”描述。MySQL从库上应用Binlog日志时,如果有第二索引涉及了改动的字段,那就更新第二索引,否则第二索引压根不需要修改。而且,MySQL有三种不同的 Binlog格式 ,包含了不同数量的信息来供使用者选择:

  • Statement:只传输DML的SQL语句,如:UPDATE users SET birth_year=770 WHERE id = 4。这种模式日志量最小,但在某些场景下和对某些字段来说容易出错。
  • Row:对于更改了的数据,会把修改前和修改后的所有字段值都打印在Binlog中。这种模式日志量最大,但也最严谨,越来越多的公司在转向这种日志格式。很多日志解析工具更是只工作在这种模式下。
  • Mixed:上面两种的结合体,MySQL会根据不同的语句来自行判断。这种模式日志量居中。

数据损坏

Uber使用Postgres 9.2时曾经因为一个BUG导致了很大的故障。当时由于硬件升级的原因他们做了主从切换,结果就引发了这个BUG导致各个从库的数据全都乱掉了,而且还没有办法判断哪个从库的哪些数据是正确的或者乱的。最终他们确认了新的主库上的数据全部正确后,用新主库的数据把所有从库数据全覆盖了一遍,才算过了这一关。可是一朝被蛇咬十年怕井绳,他们最后用的版本仍是Postgres 9.2,原因之一是不想再去踩别的版本的坑了。

作者注:以这个作为抛弃Postgres的理由就太容易引起争议、令人质疑Uber技术团队的技术水平了。在社区的口碑中,Postgres的稳定性恰恰是高于MySQL的,如果因为害怕碰上Postgres的BUG而转用MySQL,那……我们只好祝福Uber了。

从库上的MVCC支持不好

Postgres的从库上并没有真正的MVCC,它的数据表空间、表空间文件内容和主库是完全一样的,在从库上就是依次应用WAL。可如果从库上有一个正在进行中的事务的话,它就会挡住WAL的应用,从而导致看起来主从同步延迟很大。Postgres实现了一个机制,如果某个业务程序的事务挡住同步线程太久的话,就直接将那个事务杀掉。所以如果在从库上有一些比较大的事务在运行的话,你可能就会经常看见莫名其妙的主从同步就延迟了,也会看见自己的操作运行了一段时间就不知被谁杀掉了。并不是每个程序员都很熟悉数据库的底层工作机制,所以这些现象会让大家觉得很诡异。

作者注:这一点的确是的。相比来说对于这个Postgres的复制过程,MySQL的主从复制并不会杀死从库上的事务。

Postgres数据库的升级

Postgres的数据复制是物理级的,主从数据文件完全一致,所以不能支持不同版本之间的主从复制,比如主库使用9.2从库使用9.3,或者相反,等等。Uber最初使用的是Postgres 9.1,他们成功的升级成了9.2,但升级耗费了相当长的时间,再加上后来业务爆发式增长,让他们再也没能安排下一次升级。而且Postgres直到9.4之后才有了工具 pglogical 来帮助减少升级耗时,可是pglogical又不在Postgres主分支里,让使用旧版本的人无所适从。

作者注:有消息Postgres的WAL日志也将变成逻辑型了,在这样的功能推出之后,就可以支持不同版本间的数据复制了。

MySQL的其他优点

除了上文所述的几点,MySQL还有几个其他Postgres不具备的优点:

  1. BufferPool :虽然Postgres在内部有比较小的缓存,但和现在动辄几百G的服务器内存比起来,它的缓存还是太小,对硬件利用率太低了。InnoDB则有BufferPool,可以同时用于写缓冲和读缓存,用LRU管理,大小可配,这样就把硬件资源充分合理的利用起来了。

  2. 连接管理:MySQL的连接管理是每个连接一个线程,每个线程消耗的资源都很有限,所以MySQL可以轻松支持10000个以上的连接。可是Postgres是每个连接一个进程的,进程之间通信和共享资源复杂,消耗资源严重,而且对多连接支持不好。Uber的业务已经需要极大的增加数据库连接数,Postgres已经无法满足需要。

Evan Klitzke总结说:

在初期Postgres还是工作得很好的,但业务扩展时我们就碰上了非常严重的问题。现在我们还是在用着一些Postgres数据库,但是主要的数据已经挪到了Schemaless上,有些特别的业务也用了Cassandra等NoSQL数据库。我们现在用MySQL用得很好,我们也会写更多的博客来分享更多关于MySQL在Uber的使用内容。

社区反馈

也许是Evan的文章标题起得太大( WHY UBER ENGINEERING SWITCHED FROM POSTGRES TO MYSQL ),有标题党之嫌,而且是写出来的内容并不能让大家信服Postgres到底是怎样不能满足Uber的需要。 HackerNews 上的Postgres拥护者们给了他尽情的回应:

  • 为什么用MySQL而不是Percona或MariaDB?Percona有更多的参数可调。
  • 如果Postgres的一些底层存储设计特性就可以导致Uber设计团队轻率的转向MySQL的话,那只能说明他们技术团队水平太差了,对技术钻研太肤浅了。
  • 所谓Postgres的缺点在某些用户眼里看起来恰恰是比MySQL强的优点所在。
  • 碰到了几个BUG就吓得不敢再用了?MySQL照样很多BUG,到时候可怎么办?
  • 文中提到的问题是在大量数据的情况下才出现的,MySQL照样会有问题。
  • 为什么不考虑CitusDB、RethinkDB等?
  • 让我们等着看Uber的下一篇文章吧:《当我们从Postgres转到了MySQL之后,为什么又从MySQL转向了Postgres》。

诚然,Schemaless只是将MySQL用于数据存储,存的内容是简单的“键-值”对模型,虽然也用到了第二索引,可是这些并不是放弃Postgres的充分理由。说到写放大和流量放大,这些钱能解决的问题也不应该对如日中天的Uber产生困挠。也许在确定了由单机版的Postgres换成分布式的Schemaless这样的方向之后,再选择底层存储时,反正之前使用Postgres的经历也不愉快,加上流量问题,就索性换成MySQL?让我们期待Uber技术团队后续的文章能帮我们完全理解他们的想法吧!

感谢魏星对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们。

原文  http://www.infoq.com/cn/articles/underlying-storage-of-uber-change-from-mysql-to-postgres
正文到此结束
Loading...