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

  作者注:不过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主分支里,让使用旧版本的人无所适从。