正文:
科技进步了,数据库主从的延迟的问题,应该被解决了,当然这里说的并不是PostgreSQL数据库本身解决主从同步的方案,因为实话说,没有方案,或者说如果可以的话,那么要求将是及其严苛的,做到PostgreSQL主从完全同步,且不影响性能的方案是99%的应用场景无法接受的。
那么到底主从的MYSQL OR PostgreSQL可以主从一致吗,我的回答是可以,当然可以,今天揭秘PostgreSQL 的数据库如何主从一致在源代码层次的如何进行设计。
下面枯燥的理论部分来了,我在开始之前先问几个问题,PG的数据库WAL中最大的问题是什么。
wal 日志带来的从库复制延迟问题
相信看完上图的人对于PostgreSQL的wal的性能问题会有一个了解,也明白基于日志的复制的主从方式必然会有延迟,尤其是大事务的情况下,或堆积一堆的数据脏页后的checkpoint导致的wal激增。
那么今天我就找到这块的内部资料,来说说这个"PostgreSQL" 数据库是怎么解决的这个问题,可以做到主从数据一致。
LOG INDEX
这里我先提出三个名词, Logindex中的HashTable, Segment,Index Order。
下面我将用一张图来说明这三者的关系
+-----------------+ +-----------------+ +-----------------+
| HashTable | | Segment | | Index Order |
+-----------------+ +-----------------+ +-----------------+
| [0] : ------> |---->| Item Head (PageA)| | [0] : (3, 0) |
| [1] : NULL | | Next Seg (LSN1) | | [1] : (3, 1) |
| [2] : ------> | | Next Seg (LSN2) | | [2] : (5, 0) |
| [3] : ------> |---->| Item Head (PageB)| | [3] : (6, 0) |
| [4] : NULL | | ... | | [4] : (3, 2) |
| [5] : ------> |---->| Item Head (PageC)| | [5] : (7, 0) |
| [6] : ------> |---->| Item Head (PageD)| | [6] : (8, 0) |
| [7] : ------> |---->| Item Head (PageE)| | [7] : (9, 0) |
| [8] : ------> |---->| Item Head (PageF)| | [8] : (10, 0) |
| [9] : ------> |---->| Item Head (PageG)| | [9] : (11, 0) |
| [10] : ------> |---->| Item Head (PageH)| | [10] : (12, 0) |
| [11] : ------> |---->| Item Head (PageI)| | [11] : (13, 0) |
| [12] : ------> |---->| Item Head (PageJ)| | [12] : (14, 0) |
| [13] : ------> |---->| Item Head (PageK)| | [13] : (15, 0) |
| [14] : ------> |---->| Item Head (PageL)| | [14] : (16, 0) |
| [15] : ------> |---->| Item Head (PageM)| | [15] : (17, 0) |
| [16] : ------> |---->| Item Head (PageN)| | [16] : (18, 0) |
| [17] : ------> |---->| Item Head (PageO)| | [17] : (19, 0) |
| [18] : ------> |---->| Item Head (PageP)| | [18] : (20, 0) |
| [19] : ------> |---->| Item Head (PageQ)| | [19] : (21, 0) |
| [20] : ------> |---->| Item Head (PageR)| | [20] : (22, 0) |
| [21] : ------> |---->| Item Head (PageS)| | [21] : (23, 0) |
| [22] : ------> |---->| Item Head (PageT)| | [22] : (24, 0) |
| [23] : ------> |---->| Item Head (PageU)| | [23] : (25, 0) |
| [24] : ------> |---->| Item Head (PageV)| | [24] : (26, 0) |
| [25] : ------> |---->| Item Head (PageW)| | [25] : (27, 0) |
| [26] : ------> |---->| Item Head (PageX)| | [26] : (28, 0) |
| [27] : ------> |---->| Item Head (PageY)| | [27] : (29, 0) |
| [28] : ------> |---->| Item Head (PageZ)| | [28] : (30, 0) |
| [29] : ------> |---->| Item Head (PageAA)| | [29] : (31, 0) |
| [30] : ------> |---->| Item Head (PageAB)| | [30] : (32, 0) |
| [31] : ------> |---->| Item Head (PageAC)| | [31] : (33, 0) |
| [32] : ------> |---->| Item Head (PageAD)| | [32] : (34, 0) |
| [33] : ------> |---->| Item Head (PageAE)| | [33] : (35, 0) |
| [34] : ------> |---->| Item Head (PageAF)| | [34] : (36, 0) |
| [35] : ------> |---->| Item Head (PageAG)| | [35] : (37, 0) |
| [36] : ------> |---->| Item Head (PageAH)| | [36] : (38, 0) |
| [37] : ------> |---->| Item Head (PageAI)| | [37] : (39, 0) |
| [38] : ------> |---->| Item Head (PageAJ)| | [38] : (40, 0) |
| [39] : ------> |---->| Item Head (PageAK)| | [39] : (41, 0) |
| [40] : ------> |---->| Item Head (PageAL)| | [40] : (42, 0) |
| [41] : ------> |---->| Item Head (PageAM)| | [41] : (43, 0) |
| [42] : ------> |---->| Item Head (PageAN)| | [42] : (44, 0) |
| [43] : ------> |---->| Item Head (PageAO)| | [43] : (45, 0) |
| ... | | ... | | ... |
+-----------------+ +-----------------+ +-----------------+
工作流程:
1 当一个 Page 被修改时,生成一个 LSN。
2 通过 PageTag 的哈希值,在 HashTable 中找到对应的 Segment 数组索引。
3 在 Segment 数组中,找到对应的 Item Head,并将 LSN 添加到其 LSN 链表中。
4 同时,在 Index Order 数组中记录下该 LSN 和对应的 Segment 数组下标。
5 通过 Index Order 数组,可以按顺序回放 LSN,并根据 HashTable 和 Segment 数组找到对应的 Page。
这些信息同意包装到一个叫 WAL Meta 数据传输中
一句话解释,在通过LOG INDEX的新设计后,PostgreSQL的从库回放的方式,会有什么改变。
1 并行回放: PostgreSQL的WAL日志的回放是顺序的,并不是并行的,如果采用了logindex的设计模式,则WAL在从库的回放将是并行的。
2 快速检索需要先回放的日志,如果在从库获得读取数据的要求,而发现这部分数据没有回放,则可以通过log index去找到需要回放的wal 来定点回放。
写到这里log index的功能如果在PostgreSQL上实现,将大大有利于PostgreSQL从库的数据与主库的同步性,当然这还不能满足文章里面提到的主库和从库的数据完全一致性,且不损耗太多的性能。
那么PostgreSQL 主从传统的架构的核心问题是:
1 在传统的PostgreSQL的架构中,shared nothing架构下,必然会产生RO 与 RW节点WAL日志回放的性能问题,导致主从在全部时间不能完全做到一致,但最终主库和从库是一致的。
2 在RO 节点接受到WAL的日志回放,这会导致CacheMiss,频繁带来Buffer pool的淘汰问题。影响从库的性能。
3 串行WAL日志的回放,导致性能不足和从库延迟的问题。
4 大量的WAL中包含了full page write 导致了大量的磁盘 和网络被无效的数据占领,从库回放更是添加了60-80%无用的数据。
image
基于这个问题:PolarDB for PostgreSQL 除了设计出log index的结构来加速wal日志回复的便利性。同时还做出了如下举措,让PostgreSQL 的主 从库之间可以做到完全同步。
1 取消full page write ,日志就是日志,无需记录数据库的数据在日志里面,大大降低了 wal的大小,将磁盘和网络的压力降低60-80%。 (这里通过硬件来完全避免页面部分写的问题)
2 通过shared storeage的方式,真正的存储只有一份,所有的从库和主库都是一个存储一个文件,所以不需要网络作为数据传输的媒介。
3 WAL的日志传输通过内存通道复制,而不是网络通道,主库和从库的内存之间是有通道,且内存中仅仅复制的是log index的数据,数据量非常小。
4 在获得了需要复制的数据后,从库直接从磁盘拿取数据,而不是通过重放的方式,将数据塞入到从库的shared buffer pool。
5 数据磁盘为原子paxos协议的一些三,数据在落盘时通过硬件落到对应的存储,不存在一个磁盘坏了数据就损失的了问题。如同你做了 raid 5 一块磁盘坏了也不会损坏数据的道理类似。
image
当然这也只是POLARDB FOR POSTGRESQL (商业版),可以做到主从POSTGRESQL 数据库在大部分时间数据完全主从完全一致的冰山一角,其中还有使用其他的技术,如 Mini Transaction锁机制,延迟回放,硬件数据硬压缩等,我们后面找机会在进行探索和解密。
image
最终结果:POSTGRESQL 数据库是否可以主从节点完全数据一致,回答是可以,当然是在POLARDB FOR POSTGRESQL 上。