当前位置: 首页 > 科技观察

说说PostgreSQL的复制特性发展史

时间:2023-03-19 00:08:54 科技观察

就复制功能而言,从远不能胜任到功能齐全,PG来晚了一些,但它很快就完成了这些路径,确实值得一读“功能***最大的开源数据库”。本来准备的下一个topic是PostgreSQL的Redo的讨论,但是就PG的实现来说,redo机制很少需要运维特别关注,所以单独把redotopic下的replicationtopic拉出来了。整理后,第一部分是PG抄袭这一特点的历史原因。复制曾经是PGer心中挥之不去的伤口。PG9.0之前,要想做PG数据库主从,只能手动(PG内置了归档文件的shell调度操作)不断的拷贝wal的日志(而且只能拷贝到之前的WALlogofthewalcurrentlybeingwrite)到从库(姑且这么称呼吧),这个从库需要设置为recovery模式,不能对外提供服务。从我个人的角度来看,PG在功能上确实比MySQL强。但其在互联网业务早期不被看好和选择的主要原因之一是无法拓展阅读能力。在互联网业务之外,对于关注交易和数据安全的人来说,PG和Oracle是有差距的。肉眼也是可见的。当MySQL还是一个运行在文件上的SQL执行器时(从它衍生出来的MyISAM实际上最多只是一个带有B-tree的文件访问器),它已经复制了这个关键特性,并使用了这个机制,终于等到了介绍InnoDB成为了一个“真正的数据库”。对于早年的互联网业务,MySQL快速扩容从库,扩大读能力的机制,对于天生读远多于写的互联网业务来说是天作之合。而PG,在那个时代,确实是比较被动的,但最终随着时间的推移,逐渐体会到了主从复制的各种特性。9.0异步重做复制实现时间来到2010年(第一波互联网早已过去,第二波互联网也将过去,大创业时代来临),PG发布了9.0版本,其中之一最重要的特点,就是实现了流复制机制,解决了两个问题:一是直接通过网络连接从数据库中拉取redologs(redoreplication,这也是MySQLer的心痛点),而不是通过操作系统命令从主数据库复制,避免维护复制机制本身的复杂性;二是改造HotStandby机制,即在从库申请redo的同时,也让从库提供只读select服务。这里实现的复制当然只是异步复制,这个机制我觉得很奇葩:首先在执行recovery之后,从main复制所有的wal日志(不包括正在写入***的wal文件)library通过操作系统命令,然后向master数据库发起网络连接读取***的wal记录,从recovery中读取master数据库的连接信息。时间,手动指定)。而如果从库要中断主从复制,不是执行stopslave(MySQL)命令,而是在操作系统中设置一个触发文件。实现了9.1的(半)同步复制。2011年pg9.1发布,其中引入的新复制机制是同步复制的大杀器。在MySQL中,有一种机制叫“半同步复制”,意思是主库收到客户端的commit并发送后,直到从库接受并完成本次事务的所有事件,并确认flush到relaylog,这个commit会返回给client。当客户端收到commit时,意味着即使主库宕机,数据也必须已经存在于从库中,即“没有数据丢失”。简单的说PG在9.1实现的同步复制就是一回事。把关键字event换成redo,把relaylog换成slavewallog就行了。注:下图只是相关流程的简化图,只保留复制相关的逻辑。WAL子系统就不详细展开了。随后的图片与其他图片相同。但是在实现细节上还是有很多不同。(讨论MySQL的基本版本总是MySQL8.0——好吧,我知道这有点不公平)。例如,它允许在会话或单个事务级别进行控制,以区分“重要”事务和“不重要”事务。在MySQL中,半同步在遇到从库响应超时时会自动降级整个数据库(rpl_semi_sync_master_wait_no_slave控制)。比如在设置哪些从库是半同步复制的时候,用逗号剪切指定一个或者多个(但是只支持其中一个作为同步从库,先选择第一个作为同步节点,如果**如果第一个有问题就选择第二个,以此类推),而MySQL的半同步复制保证了“只要有从库接收到的rpl_semi_sync_master_wait_for_slave_count”的边界点。比如由于回滚实现机制的问题,PG的回滚不会被同步复制阻塞,MySQL在具体情况下(可以参考https://my.oschina.net/llzx373/blog/282768本文讨论是在回滚的情况下,也会生成binlog讨论),即使是回滚,也需要等待半同步响应。比如在事务可见性方面,PG直接由事务id控制,而MySQL在5.7引入了rpl_semi_sync_master_wait_point来处理(MySQL半同步复制是5.7之前的,master库中的其他事务可以看到等待的事务数据)半同步复制返回,即使事务还没有返回commit给客户端),对了,这个版本还引入了pg_basebackup命令,主要用于从库和数据库的备份。PG的从库也可以将查询需要的最早事务点上报给主库,避免主库的数据清理(vacuum)清理从库需要的数据。当然,从库的长期查询也会导致主库文件变大,在具体的使用决策上值得考虑。在控制复制启停的方式上,也提供了sql函数调用,而不是仅仅依赖触发器文件。(pg_wal_replay_pause(),pg_wal_replay_resume())9.2级联复制级联复制,即A->B->C形式的复制,其最终意义是减轻主库的复制负载压力。复制负载问题的根源类似于互联网业务,写的少,读的多。一个主库可能要承载几十个从库(记得春节的时候我们给一个主库扩容了几十个从库)级联复制是引入日志发送行为的必要条件,不管是磁盘压力还是网络带宽。当然,级联复制的C一定是异步复制。pg_basebackup也可以在从库进行备份,可以避免备份对主库的性能影响。在同步复制方面,增加了一个remote_write级别,也就是说只要从库收到wal日志,就可以确认commit成功,但不保证一定要刷盘。9.3的性能和可用性该版本没有本质的功能变化,但在可用性和性能方面有了相当大的改进。例如,多个从库中的一个提升为主库后,其他的从库可以直接切换过来,但在此之前,必须重新同步。比如pg_basebackup可以直接生成一个recovery.conf文件。9.4逻辑日志导出和延迟备份从这个版本开始,xlog(wallog)支持逻辑复制的逻辑分析导出,或者跨库类型的复制,甚至可以支持逻辑复制和同步复制,唯一的限制是,逻辑复制只能应用于单个数据库而不是全局。这个版本也引入了replicationslot的概念。前面说过,为了防止主库清理还需要从库使用的数据,从库需要向主库上报需要的交易点信息。从9.4开始,单独提出了这种机制,称为replicationslot。主要作用是为物理复制和逻辑复制提供一个维护事务点信息的视图,避免因数据库连接断开导致的数据清理。在复制应用方面,从这个版本开始,PG增加了延迟复制的特性。对于误删操作等诸多问题,该功能可以尽可能缩短数据恢复时间。9.5性能和易用性这个版本没有大的变化,主要有以下几点:允许WAL日志以压缩形式传输到从库,使用主从库的CPU来换取更低的网络消耗。recovery.conf的主库连接信息可以写成URI(postgres://)的形式。添加了wal_retrieve_retry_interval参数以控制从站故障后的重试。9.6多同步复制从库和真同步复制说句题外话,PG在9.6开始支持并行查询,并在后续版本做了很大的增强。这就是我认为PG与MySQL的比较。具有绝对优势的功能。PG的主要slot之一的vacuumfreeze在这个版本中也被引入,这是一个重要的特性,不会重复处理已经完全冻结的数据块。上面说了,MySQL有一个参数rpl_semi_sync_master_wait_for_slave_count来控制同步从库的个数,而PG则是从setlist中选择某一个。从9.6开始,这个设计改为等到wal确认写入多个库后,返回commit。synchronous_standby_names参数不仅是一个逗号切割列表,而且以n(s1,s2,s3)的形式变成n(s1,s2,s3),让前n个数据库在满足wal条件后确认commit.在前面讨论同步复制的时候,提到了PG的同步复制,它和MySQL半同步复制的实现机制基本类似。不过从9.6开始,增加了remote_apply同步点,即直到从库申请对应的wal日志,主库才能返回commit成功,主库commit到从库的效果可以是立即看到。相信开发者问的不只我一个,说写在主库的语句在从库可以查到,为什么查不到,而在当代的大型项目中,假设上层应用可以直接调用read和write,即写入立即可读。如果下层贸然采用传统的读写分离方式,可能会造成上层应用无法立即看到数据的问题。这个问题夹杂着主从延迟的各种纠结。传统行业客户最常遇到的问题之一。10发布-订阅逻辑复制逻辑复制在这个版本中得到了极大的增强。首先,它本身就支持逻辑复制的特性。虽然从9.4开始就可以解析xlog用于逻辑复制,但是当时PG并没有内置逻辑复制相关的组件。从10版本开始,支持表级别的逻辑复制,支持跨大版本。操作系统和机器架构之间的逻辑复制比物理复制灵活得多(例如MySQL的binlog复制)。synchronous_standby_names再次更改了语法,包括first和any语义。如果是first指定的列表,会按照顺序优先级确认返回的从库响应是否达到指定个数(比如first1(s1,s2),和老实现类似,选择第一个作为同步点,如果第一个s1失败,则选择第二个s2作为同步点),并且对于任何语义,例如像任何2(s1,s2,s3),它允许三个从库,并且任何两个从库只要同步成功,库就可以确认提交。对于any,它更类似于MySQL中的半同步确认语义。恢复模式下的恢复目标点,除了时间戳和事务id,还支持LSN号,恢复更加灵活。另外,估计是因为slot维护了wal,一些为临时session维护的slot导致wal堆积(比如pg_basebackup,每次备份需要创建一个slot,备份完成后,需要将slot弃)。从这个版本开始,slotsupportisonly当前会话提供的临时slot,避免了这个问题。11Replicationfeatureofcornerrepair11大版本的主要功能变化是分区表终于可以使用了,而不必使用第三方插件,包括hash分区,分区表上的主键,外键,全局indexes,andtriggers这些终于支持了。在复制特性方面,这个版本基本没有大的变化,都是小修小补。比如做备份时,增加数据块的校验和。比如pg_stat_wal_receiver视图添加机器和端口信息。没什么好说的,但是从PG在复制方面已经做的事情来看,确实没有更强烈的需求来推动这方面的进一步增强。结论和个人想法从远不能胜任到功能齐全。PG虽然来的晚了点,但是也很快走完了这些路径。从功能上来说,确实不愧是“功能最强的开源数据库”。佛法讲修行,其中一个障碍叫做“见识障碍”。意思是对一件事知道的越多,看其他的东西就会有越多的偏见,越难有清晰的认知(注:这是知识和眼界障碍的解释,还有应该是禅宗本土化后顿宗的解释,在原始佛教中,这个词应该是类似于六不净的概念,这里不是我要表达的)。的确,无论是我自己的感觉好还是我和别人谈论数据库时的感觉如何,每个人都有用自己熟悉和认知的“数据库”去看待其他数据库的习惯。为了我自己。比如我对pg的vacuum如此纠结,因为在这个问题上,MySQL和Oracle这两个我比较熟悉的数据库,已经(相对)很好的解决了这个问题,但是PG不得不笨手笨脚,不处理它。——是的,有很多可以实现的手动策略,也有很多可以避免的参数设置,但为什么我要管它呢?可以自己悄悄处理吗?就算是极限单表的parallelvacuum也是可以的(MySQL5.6之前的单线程purge也是个大坑,后来改为多线程)。比如DB2,这是我在学校学数据库时的课本数据库。我从这个数据库中学到了很多知识。不久去看Oracle,第一反应就是,隔离级别哪来的?——Oracle的隔离级别,除了RC和Serializable,其他隔离级别都是通过workarounds实现的,而DB2有一套完整的4个隔离级别(虽然每个隔离级别的名字和SQL标准的名字不是很一致)———后来教别人隔离级别的课程时,我是用DB2来讲课的,没有再拉出另外一个数据库(MySQLInnoDB在RR中处理幻读,所以Serializable级别和RR的区别,解释下,费了好大劲才搞定up,还有PG的几个隔离级别中,readuncommittedreads不能读脏读,repeatablereads不能读phantomreads(更不用说了),不知道有多少人拿着oracle谈论隔离级别,但是到目前为止就我个人的感觉而言,即使是现在,还是觉得很别扭。如果我们要打破刻板印象,还是去了解其他的东西,了解它们的优缺点,“用在该用的地方”编”。在技??术成长方面,我相信会有更好的进步。另外,由于不同的数据库,虽然从外部来看,都是SQL语言控制的数据库,但内部实现的细节却完全不同。作为一个DBA,如果你不想让你的职业生涯被迫在某个数据库上维护(DB2有前车之鉴,我差点就这样走了),那么就敞开心扉,学习一下其他数据库的特性,这也是一个很好的防御措施。作者:云河恩墨软件开发研究院研究员刘伟;前微博DBA,主要研究方向为开源数据库、分布式数据库,擅长自动化运维和数据库内核研究。参考http://mysql.taobao.org/monthly/2015/12/05/https://www.postgresql.org/docs/11/release.htmlhttps://dev.mysql.com/doc/refman/8.0/en/replication-semisync.htmlhttps://my.oschina.net/llzx373/blog/282768事务的mysql复制处理