为PostgreSQL讨说法丨为什么说Uber不应该切换成MySQL?
Posted 高效运维
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为PostgreSQL讨说法丨为什么说Uber不应该切换成MySQL?相关的知识,希望对你有一定的参考价值。
编者按:本文已获得云栖社区授权,转载请注明出处。
背景
最近有一篇文档,在国外闹得沸沸扬扬,是关于UBER使用mysql替换postgres原因的文章。
英文原文:
中文翻译:
文章涉及到 PG数据库的部分,论点过度的浮于表面,没有深入的理解和分析。很容易导致用户对PostgreSQL产品的误解 。
另外还有一篇翻译的文档,则看起来已经完全变味。
隐约感觉到一股黑PG的势力袭来。
为了让用户更清楚的认识涉及技术的本质,我打算写一篇浅析的文章,深入浅出的讲讲个中道理 。
uber在文章阐述的遇到的PG问题
We encountered many Postgres limitations:
Inefficient architecture for writes
Inefficient data replication
Issues with table corruption
Poor replica MVCC support
Difficulty upgrading to newer releases
我接下来会依依介绍其原理,以及文章内容存在的问题。
1、Inefficient architecture for writes
uber文章的观点
PG的MVCC机制,更新数据为新增版本,会带来两个问题:
SSD的写放大
索引的写放大
本文观点
事实并不是MVCC的问题,所有的数据库只要支持并发读写,就需要多版本,只是版本管理的手段可能不一样。有通过回滚段管理的,也有通过多版本(MVCC)进行管理的。
原理剖析
基于回滚段管理的数据库
当更新一条记录时,有些数据库需要将整个数据块拷贝到回滚段区域(有些是基于逻辑行的拷贝,则拷贝到回滚段的是记录)。
注意写回滚段也是会产生REDO写操作的。
更新可能在当前的row进行。
这种情况,只要索引字段不变化,索引就不需要变。如果索引字段值发生变化,索引也要变化。
如果更新后的记录超过原来行的长度,可能在本页找一块空闲区域(如果能装下),也可能要到其他页找一块区域进行更新,有擦除旧记录,写入新纪录的写操作。
不管怎样,索引都要变化。
基于回滚段的数据库,如果要回滚事务,开销会很大(特别是当事务修改的数据量很大时),因为要从回滚段将整个块拷贝到数据文件(基于逻辑行拷贝的回滚则是类似重新来一遍UNDO事务的SQL操作,同时还需要擦除之前更改的行)。
代价非常高。
基于MVCC的数据库
当更新一条记录时,产生一个新的版本。
PostgreSQL 会优先使用在当前页更新(HOT),即在当前页进行更新,不管行长度是否发生变化。
这种情况,只要索引字段不变化,索引就不需要变。如果索引字段值发生变化,索引也要变化。(hot时,索引不变,通过HEAP页内旧item指向新item来做到定位到新的记录)
如果未在当前页更新,则索引才需要变化(通过配置表的fillfactor,可以大大减少这种情况的发送,尽量走HOT)如果读者还是担心这个问题,我们可以做一个压测试验,看看到底会不会更新索引,会不会对更新造成性能影响如何?
1000万数据,9个字段,8个索引,更新其中的mod_time字段。
记录下当前表的大小和8个索引的大小
全力压测30分钟,更新mod_time字段
压测结果,更新速度持续在 13万/s 以上。 这个压力应该可以覆盖很多的用户吧。
压测结束后,查看表和索引的大小,如果按UBER文中指出的,会更新索引,但实际上,结果说话,表和索引根本没有膨胀。
UBER 文章对用户的误导不攻自破。
小结
基于回滚段的数据库,在更新数据时SSD写放大>100%(因为回滚段是一定要写的,并行写回滚段的操作也需要写REDO);
而基于MVCC的数据库,SSD写放大的概率低于100%(因为可能发生HOT,发生在当前页),而且旧记录只改行的xmax标记,产生的REDO极少。
基于回滚段的数据库,在删除数据时SSD写放大是100%(因为回滚段是一定要写的,并行写回滚段的操作也需要写REDO);
而基于MVCC的数据库,SSD写放大的概率为0 (因为只需要改一下行头部的xmax的标记)。
基于回滚段或MVCC的数据库,索引的写放大,都与是否发生行迁移有关,概率差不多。
基于回滚段的数据库,如果要回滚事务,开销会很大(特别是当事务修改的数据量很大时),因为要从回滚段将整个块拷贝到数据文件(基于逻辑行拷贝的回滚则是类似重新来一遍UNDO事务的SQL操作,同时还需要擦除之前更改的行)。
基于MVCC的数据库,事务回滚非常快,因为不需要拷贝行或者数据块,也不需要修改已更新的记录,只是记录clog时将当前事务标记为ABORT即可,也就是说只需要改2个比特位。
PG的HOT技术完美的解决了索引更新的问题,根本不存在UPDATE就一定需要更新索引的问题。
彩蛋
PostgreSQL TOAST机制
PostgreSQL的TOAST机制,可以将变长类型的值,自动压缩存储到另一片区域,通过内部的POINT指向,而不影响行的其他值。
例如存储文档,或者图片的表,如果这个表上有一些字段要更新,有一些字段不要更新,那么在更新时,PostgreSQL数据库会有非常大的优势,因为行很小。
基于回滚段的数据库,需要拷贝旧的记录或数据块到回滚段,记录或块越大,这个开销越大。
存储文档、图像、非结构化数据,使用PostgreSQL很有优势。
MySQL innodb是基于B+树的存储,当PK数据随机数据写入时存在巨大写放大,因为经常要分裂,不仅影响插入速度和查询速度,同时数据存放也会变得非常无序。
即使按PK顺序扫描时,也可能出现大量的离散IO。
基于B+树结构的存储,为了提高插入速度,如果使用index cache的话,则影响并发的查询,因为查询时要先合并索引。
另一方面,B+树的存储,必须要求表需要一个PK(即使表没有PK的需求,也要硬塞一个PK列进来),secondary index则指向这个PK。
如果PK发生更新,则所有的secondary index都要更新,也就是说,为了保证secondary不更新,务必确保PK不更新。
如果要对secondary index进行范围扫描,其实物理的扫描上是离散的。
所以uber本文提出的,secondary index 不需要变更的好处,其实背后是有以上代价存在的(例如一定要加PK,插入速度更慢,插入时PK不能随机否则分裂带来的IO巨大,使用secondary index范围扫描时会造成离散的IO等弊端),把原理,代价都交代清楚,才能看得更清楚。
PostgreSQL 有几种方法来消除这种离散IO。
a、bitmap scan,获取heap tuple前,先根据ctid的blockid排序然后再从heap获取记录,以获得物理上顺序的扫描。
b、cluster by index,将表的物理存储顺序按照索引的顺序来存放,从而使用该索引扫描时,则是顺序的扫描。
PostgreSQL的表是基于HEAP存储的,不存在以上B+树存储的问题,随便怎么插入,速度都很快。
SSD的原子写,通常SSD写入时是以最小单位为4K的写入,即使修改很小的数据。
那么以directio或buffer io为主的数据库,哪个对SSD的伤害更大呢?
对于directio的数据库,因为每次都是真实的伤害,而buffer io的数据库,OS层还会合并IO,可以大幅降低SSD的真实写(os 层调整vm.dirty_background_ratio可以调整写频率,从而影响合并粒度)。
PostgreSQL的shared buffer管理是基于buffer io的管理,对SSD来说是一种很好的保护,有兴趣的童鞋可以测试验证一下。
2、Inefficient data replication
uber文章的观点
PG的复制低效,有写放大。
本文观点
PostgreSQL的流复制非常高效,延迟几乎为0,同时还支持流的压缩和加密传输,很多企业用流复制来实现异地容灾,HA,读写分离的应用场景。
同时PostgreSQL也支持逻辑复制(>=9.4支持流式逻辑复制,<9.4的版本则支持基于触发器或者基于异步消息的逻辑复制)。
原理剖析
问题反驳 1 (复制低效)
我第一次听说PG的复制低效的,要知道PG的复制是业界有名的高效,延迟极低(关键是复制延迟与事务大小无关),网络好的话,几乎是接近0的延迟。
PostgreSQL流复制原理
即时唤醒,流式复制,所以延迟极低。
问题反驳 2 (REDO写放大)
基于回滚段的数据库,在更新时,拷贝到回滚段的旧版本,是要写REDO的。
而基于MVCC的数据库,旧版本仅仅需要写修改行头bit位的REDO,所以基于MVCC的数据库,更新时写入的REDO应该是基于回滚段的数据库的一半甚至更少(比如基于物理的回滚段要拷贝整个块,产生的REDO也很大)。
同时,由于基于回滚段的数据库回滚时,要将回滚段的数据拷贝回数据文件,是会产生REDO的,这一点,基于MVCC的数据库不存在这种写放大的问题。
问题反驳 3(复制流量放大)
基于REDO的物理复制,意思就是要把REDO复制一份到备库。
所以REDO写了多少,就要复制多少到备库,网络的流量也是这样的。
另一种是基于REDO的逻辑复制,需要复制的数据不仅仅包括新的数据,还要包括旧的版本数据(PK或者full row)。
可能一条记录更新前和更新后的数据都要复制。
对更新操作来说,物理复制,不需要复制旧的记录(因为产生REDO的仅仅是XMAX的变化)过去,而逻辑复制则需要复制旧的记录过去。
另外需要注意的是,目前PG的垃圾回收也是以物理恢复的形式复制的,在实现上还有改进空间,比如通过逻辑的方式复制垃圾回收(只复制block id),可以大大减少网络传输的流量。
而 uber 文章并没有指出,事实上 MySQL 目前只支持逻辑复制,并且如果要开启逻辑复制,不仅仅要写redo,同时还要写 binlog,等于写了双份日志,这个写放大也是很大的。
MySQL redo 用于恢复数据库,binlog用于复制。
自PostgreSQL 9.4开始,PG内核层就同时支持物理复制和逻辑复制,而且仅仅写一份日志就能同时支持物理以及逻辑复制。
在9.4版本之前,则可以通过其他软件进行逻辑复制(例如Londiste3, slone-I)
逻辑复制有一个弊端,被复制的表一定要有PK。 物理复制不存在这个问题 。
逻辑复制另一个弊端,大事务导致主备的延迟非常大,因为备库一定要等主库事务结束,备库才能开始回放该事务。物理复制不存在这个问题。
小结
PG的复制是业界有名的高效,延迟极低(关键是复制延迟与事务大小无关),网络好的话,几乎是接近0的延迟。
基于MVCC的数据库,就版本仅仅需要写修改行头bit位的REDO,所以基于MVCC的数据库,更新时写入的REDO应该是基于回滚段的数据库的一半甚至更少(比如物理回滚段要拷贝整个块,产生的REDO也很大)。
对更新操作来说,基于REDO的物理复制,不需要复制旧的记录过去,而逻辑复制则需要复制旧的记录过去,物理复制产生的网络流量更小。
逻辑复制有一个弊端,一定要PK。 物理复制不存在这个问题 。
逻辑复制另一个弊端,大事务导致主备的延迟非常大,因为备库一定要等主库事务结束,备库才能开始回放该事务。 物理复制不存在这个问题 。
彩蛋
PostgreSQL可以开启协议层压缩,同时可以选择是否加密传输,压缩传输REDO。更高效,更安全。
PG的用户如果有主备环境,可以关闭FULL_PAGE_WRITE,产生的REDO更少(第一次更新的PAGE不需要写FULL PAGE)。
但是需要注意,如果关闭了FPW并且主库因主机问题或在OS问题挂了,需要从备份环境恢复。
PG用户,可以将checkpoint拉长,减少FULL PAGE的产生,从而减少REDO的产生。
PG的用户,如果需要从PG或者MYSQL复制到阿里云的rds
PG,可以使用阿里dbsync插件,目前支持全量复制,增量的逻辑复制正在开发中。
3、Issues with table corruption
uber文章的观点
用户在使用PG 9.2 时,因为主备切换,导致了一些数据问题。
本文观点
UBER在文中并没有描述清楚这个问题的始末,如何复现,同时也没有听说过PG的其他用户遇到这样的问题。
而且我在测试环境模拟TPC-B,同时不停的进行主备切换,也没有遇到类似问题。
PG的物理复制是可以保证主备完全一致的。
uber给出的观点心理暗示比较可怕。
PG一直以来就是一个以稳定性和功能强大著称的数据库,在企业市场有非常好的口碑。
国内的银行,运营商,保险,互联网公司都有在核心环境使用:
平安科技、阿里巴巴、高德、去哪儿、腾讯、用友、阳光、中移动、探探、智联、典典、华为、斯凯、通策医疗、同花顺、核电、国家电网、邮储银行、友盟、莲子。。。。。。
海外的汽车生产巨头,政府部门,医疗,物流等各个行业也都有非常多的用户 。
生物制药 {Affymetrix(基因芯片), 美国化学协会, gene(结构生物学应用案例), …}
电子商务 { CD BABY, etsy(与淘宝类似), whitepages, flightstats, Endpoint Corporation …}
学校 {加州大学伯克利分校, 哈佛大学互联网与社会中心, .LRN, 莫斯科国立大学, 悉尼大学, …}
金融 {Journyx, LLC, trusecommerce(类似支付宝), 日本证券交易交所, 邮储银行, 同花顺…}
游戏 {MobyGames, …}
政府 {美国国家气象局, 印度国家物理实验室, 联合国儿童基金, 美国疾病控制和预防中心, 美国国务院, 俄罗斯杜马…}
医疗 {calorieking, 开源电子病历项目, shannon医学中心, …}
制造业 {Exoteric Networks, 丰田, 捷豹路虎}
媒体 {IMDB.com, 美国华盛顿邮报国会投票数据库, MacWorld, 绿色和平组织, …}
零售 {ADP, CTC, Safeway, Tsutaya, Rockport, …}
科技 {Sony, MySpace, Yahoo, Afilias, APPLE, 富士通, Omniti, Red Hat, Sirius IT, SUN, 国际空间站, Instagram, Disqus, …}
通信 {Cisco, Juniper, NTT(日本电信), 德国电信, Optus, Skype, Tlestra(澳洲电讯), 中国移动…}
物流 {SF}
小结
仅凭一个没有始末的结论,似乎很难说明什么。
基于逻辑复制的数据库,主库压力大时通常会遇到备库追不上。又或者因为某些原因导致主备不一致,即使发现了,可能并没有很好的修复手段,因为你不知道该以哪个数据为准。
逻辑复制导致主备不一致的原因较多,例如 主库执行失败,备库执行成功,或者备库执行成功,主库执行失败。
又或者 主库和备库的环境不一致,例如字符集,或者其他的,都非常容易导致主和备的不一致。
对于要求主备严格一致的场景,强烈建议使用物理复制。
4. Poor replica MVCC support
uber文章的观点
PG备库的MVCC支持较差,查询会与恢复堵塞
本文观点
首先,PG的备库分两种,一种是物理备库,一种是逻辑备库。
对于逻辑备库来说,与MYSQL的恢复机制是一样的,既然是一样的就不需要讨论了。
UBER文章说的 查询会与恢复堵塞,说的是物理备库,但必须纠正一个观点,查询是否堵塞恢复,是要论看场景的,况且堵塞的情况极为少见,还有一点要注意,逻辑复制也会有堵塞。
原理剖析
物理复制,什么情况下查询会堵塞、或与恢复冲突?
当以下操作产生的REDO被复制到备库,并且备库准备拿这些REDO来恢复时。
Access Exclusive locks taken on the primary server, including both explicit LOCK commands and various DDL actions, conflict with table accesses in standby queries.
主库的访问排它锁,与备库对应的锁产生冲突。例如主库truncate a表, 备库查询a表。这种情况的冲突面很窄。
Dropping a tablespace on the primary conflicts with standby queries using that tablespace for temporary work files.
主库删除表空间,备库使用这个表空间产生临时文件。例如主库删除TBS,备库的一个大的查询需要写临时文件,并且这个临时文件是写到这个表空间的。
这种情况非常少见,也很容易规避,新建一个临时表空间不要删除即可。
Dropping a database on the primary conflicts with sessions connected to that database on the standby.
主库删除数据库,备库刚好连在这个数据库上。这种情况也非常的少见。
Application of a vacuum cleanup record from WAL conflicts with standby transactions whose snapshots can still “see” any of the rows to be removed.
主库回收dead tuple的REDO,同事备库当前的query snapshot需要看到这些记录。这种情况可以通过参数控制,恢复优先,或查询优先。可以配置时间窗口。
而且这种冲突出现的概率也非常的小,除非用户在备库使用repeatable read,同时是非常大的事务。而通常用户用的都是read committed.
Application of a vacuum cleanup record from WAL conflicts with queries accessing the target page on the standby, whether or not the data to be removed is visible.
同上,但是当query访问的页就是要清理垃圾的页时,也是有冲突的。这是物理复制与逻辑复制唯一有差别的地方,但是对现实场景来说,这种情况出现的概率也不大。
对于PG来说,主备冲突导致的备库延迟,绝对没有MySQL逻辑复制在碰到大事务时那么可怕,逻辑复制遇到大事务,导致的延迟是很严重。
在现实应用场景中,很少有用户担心PG的备库延迟,即使有短暂的冲突,因为是基于块的恢复,恢复速度是很快的,马上就能追平(只要备库的IO能力够好,通常追平是瞬间完成的)。
难道逻辑复制就不会出现查询与恢复的堵塞、冲突吗?
当然也会有冲突
逻辑复制,什么情况下查询会堵塞、与恢复冲突?
备库发起一个repeatable read的事务,由于备库不断的恢复,备库的该查询事务有可能因为snapshot too old失败。
主库发起的DDL语句,回放时会与备库的查询冲突,DDL的回放会被完全堵塞。
主库删除一个数据库,回放时如果备库正好连在这个数据库上,产生冲突。
小结
基于物理复制或逻辑复制,只要备库拿来使用,都有可能出现查询与恢复冲突的情况。
PG对于冲突的处理非常的人性化,你可以选择恢复优先 or 查询优先,设置时间窗口即可。
同时PG还支持备库的QUERY反馈机制,主库可以根据备库的QUERY,控制垃圾回收的延迟窗口,避免QUERY和垃圾回收的冲突。
5、Difficulty upgrading to newer releases
uber文章的观点
PG的跨版本升级较难,跨版本不支持复制
本文观点
看起来文章的作者或者UBER里可能没有熟悉PG的人,PG的大版本升级的途径很多,也很方便。
我这里给出两个方法:
方法1:通过迁移元数据的方式升级,这种升级方式,取决于元数据的大小(即数据结构,函数,视图等元信息)所以不管数据库多大,都能很快的完成升级。
例如以10万张表,1万个函数,1000个视图为例,这样的元数据大小可能在几十MB的水平。
自动化程度高的话,导出再导入应该可以控制在分钟级别完成。关键是它能支持原地升级,也就是说,你不需要再准备一套环境,特别是数据库非常庞大的情况下,再准备一套环境是很恐怖的开销。
当然,如果企业有环境的话,为了保险,通常的做法是,复制一个备库出来,在备库实现原地升级,然后激活备库转换为主库的角色。
备库升级结束后,再升级老的主库,由于只动到元数据,所以主备的差异很小,rsync一小部分数据给老的主库,就能让老的主库实现升级,同时将老的主库切换成备库即可。
简单的几步就完成了主备的大版本升级。
基于pg_upgrade的大版本升级可以参考我以前写的文章:
方法2:通过逻辑复制增量平滑升级,与MySQL的升级方法一样,也很便利,但是要求一定要准备一个备库环境,如果数据库已经很庞大的话,总的升级时间会比较漫长。
对于 >= 9.4的版本可以使用PG内置的逻辑复制。
小于9.4的版本则可以使用londiste3或者slony-I。
PG跨版本支持复制,而且支持的很好。
对于>=9.4的版本,可以用基于流的逻辑复制。
对于<9.4的版本,可以使用londiste3, slony-I。
小结
每种数据库都要去深入了解,才能去解决业务上面对的问题。
每种数据库存在即有存在的理由,有它适合的场景,MySQL和PostgreSQL发展这么多年,都有各自的用户群体,相互都有学习和借鉴的地方。
作为数据库内核工作者,要多学习,把数据库做好,把最终用户服务好才是王道 , 也许下一代的数据库引擎就是PostgreSQL和MySQL杂交的,看看阿里云ApsaraDB接下来会放什么招吧 。
没有深入探讨问题的根源就抛观点,是不对的,容易被拿去当枪使,对整个开源行业没什么好处 。
UBER发表的该文章对PG的论点过于表面和片面,读者要多思考,别被拿去当枪使了还不知道 。
基于线程和进程的讨论太多,PG基于进程,优势是非常强壮,所以PG的扩展能力极强,看看PG那无数的插件就知道了,是一个贴近用户的,高度可定制化的数据库。
本文末尾的推荐阅读也包含了大量通过插件方式扩展PG功能的文章。
本文仅对uber发文的PG部分作出解释,网友可以多多交流。
重要通知:
文章之外,还有很多深入讨论。原文下提问,德歌会来回答。
↓↓↓ 点击"阅读原文" ,了解更多。
以上是关于为PostgreSQL讨说法丨为什么说Uber不应该切换成MySQL?的主要内容,如果未能解决你的问题,请参考以下文章