我们知道He3DB是基于PostgreSQL开发的云原生数据库,那么它是如何实现的,相比PostgreSQL做了哪些改动呢?我们将思考如何云原生数据库He3DB是如何设计的,思考其背后的设计逻辑。
云原生数据库的由来
最早的数据库都是部署在物理机上,比如PostgreSQL、MySQL。后来,随着云计算的发展,应用上云,有了在云上部署数据库的需要,诞生了云数据库。但是云数据库有其不足之处,为了解决云数据库的不足,云原生数据库Aurora诞生了,提出了Log is database的概念,采用存算分离共享存储的架构。
设计逻辑
存算分离
云原生数据库须采用存算分离的架构,否则无法发挥其云计算相比物理机的最大优势——弹性。在存算分离的架构下,原有数据库刷页落盘持久化,WAL只作为Redo日志作为故障恢复使用,其中后台进程不断的刷页落盘将不再可行,因为存储节点与计算节点是分离的,不再是处在同一个单机实例中,刷页落盘需要通过网络进行,而网络是非常慢的,采用这种方式其相比单机PostgreSQL数据库,其还有性能劣势,明显,这种方案是不可行的,即计算节点与存储节点之间不能采用原有的刷页落盘持久化的方式。那怎么办呢?除了缓存中的页,WAL日志存储了数据库的所有修改,可将其发送到存储节点,存储节点不断的重放WAL日志,代替刷页,其相比页空间要小很多。
所以,相比PostgreSQL,He3DB计算节点要将WAL日志发送到存储节点,不再将WAL放到pg_wal本地盘中。
日志服务
PostgreSQL中高可用是通过主备方式实现的,即,主节点通过流复制将WAL日志发送到备节点,备节点重放WAL日志。而在He3DB中,高可用是通过存储节点集群实现的,即将WAL存储在分布式存储中,在分布式存储中,WAL会保存多副本,通过Raft协议保证副本一致性。这样即使1个存储节点挂了,也不会造成数据丢失。
所以,He3DB计算节点,需要将生成的WAL日志发送到Tikv中存储,将WAL元数据信息通过Wal sender进程发送到推进节点的wal receiver进程中,不在需要将完整WAL日志发送到只读节点。
当然,不同的云原生数据库,其设计是不同的,这里只分析He3DB的逻辑,其实,大的方向都是想通的。都可以参考Aurora的设计。
多版本页 + 数据一致性
多版本页的问题,不同于PostgreSQL,在PostgreSQL中,页要么在内存中,内存中的页一定是最新页,要么因为Buffer Pool满了被淘汰掉刷盘存储在磁盘中,此时磁盘中的页也一定是最新页,因为是内存中含有的最新的页刷盘刷下来的,肯定是最新的。而当需要更新某个页时,也是将页从磁盘读到Buffer中,在内存中更新页,将页标记为脏页,这时候,最新页在内存中,不存在多版本页的问题。而在He3DB中,则存在多版本页的问题,最直接的原因就是此时的最新页并不是通过从内存中刷盘刷下来的,因为计算节点仅仅将WAL日志发送给了推进节点,并没有将页发送到推进节点,推进节点是通过旧的一个版本的页加上WAL日志回放到最新页,而回放到那个位置是有限制的,因为只读节点的一致性的限制,往往推进节点不能推进到最新页,推进的位置(也就是推进到那个LSN)要满足不能超过当前只读节点的最新LSN,因为一旦超过了,那么只读节点从推进节点获取页+WAL日志的时候,就会得到“未来页”,引发不一致。可以认为页有很多版本,通过<Page+LSN>的方式,标识每个版本。
未来页的问题,直接原因就是推进节点的刷脏速度,大于备库的回放速度引起的,所以,主节点的推进节点要控制推进的速度,不能推进的太快,这里还会引发一个工程问题,要是回放的速度很慢,那么就可能会造成推进节点积累了大量的页以及WAL,可能会有内存无限膨胀的问题。具体的实现中,对应的数据结构是LogIndex,即,LogIndex内存不够用了怎么办?又不能随意扔了,唯一的办法就是内存不够时存储到磁盘中,释放部分内存,按需从磁盘读取到内存中,其思路于操作系统的swap机制是一样的。
于PostgreSQL的不同之处
故障恢复
故障恢复的不同,PostgreSQL故障恢复的逻辑是宕机重启后,从最近的一个检查点开始恢复,读取检查点之后的所有Redo日志并进行回放,出于性能的考虑,往往不会选择进行频繁的checkpoint,会通过配置,降低checkpoint触发的频率,也就是说,这个回放的过程可能会很长。而在He3DB中,计算节点不断地将WAL日志发送给推进节点,由推进节点进行回放日志,这个过程是不间断的一直在进行的,因为他不会影响到计算节点,所以,在进行故障恢复时,可以很快的进行,因为这个故障回放操作其实一直在进行。
实现要点
- WAL日志发送到tikv中,而不是落在本地盘pg_wal目录中,pg_wal相关的逻辑需要修改,日志段的命名规则,WAL日志的格式,WAL日志写入的逻辑肯定不同了,WAL日志归档等等,日志相关是一大改动。
- 主备节点之间同步WAL日志元数据,而不是完整的日志记录,因为不需要完整的日志记录了,日志记录在tikv中这部分需要改
- 因为目前需要区别是主节点还是推进节点,需要代码中加以区别,可考虑在GUC参数中加以区分
- 关于autovacuum,只有主节点会触发autovacuum,备节点,只读节点禁止触发autovacuum。
- 多版本页实现,LogIndex,以及持久化的设计
- 多版本页,推进速度的控制,并行回放
当然,代码实现要点不止这些,后续逐渐列出。




