oceanbase本质上还是基于lsm-tree(http://www.trueeyu.com/?p=138),简单的说就是将数据的更改hold内存中,当达到指定的阈值后,再批量写到磁盘与已有的数据做合并。

这个设计是基于这个假设:写入远远大于读取,insert量大,update量小,这种方法可以优化写,但没有显著降低读,因为大部分最新的数据都在内存里面,读取速度会非常快。
理论上是很简单的,但是一旦用在实际的场景中确出了很多问题,夸张的说,做为存储组的同学,1/3的时间应该都花在处理合日合并相关的问题上。

基础架构

1.0之前的oceanbase架构,都是分为4个server,chunkserver(存储静态数据),updateserver(存储动态数据),mergeserver(sql处理,并发查询等),rootserver(元数据管理),这个4个server都可以分别部署在不同的机器上,写入是一个单点。

每日合并的过程实际上就是chunkserver(简写为cs)的老版本的静态数据与updateserver(简写为ups)的动态数据进行合并,生成新版本的静态数据,写到cs。

下面就基于这个架构引发的一系列问题来描述。

ups内存不足

ob所有数据的更改都是要写到ups的,所以ups的内存一旦用完,则会阻塞应用的写入,这是不可以接受的。所以当ups内存到达一定程度后,就要触发每日合并,将动态数据与静态数据进行合并,合并完成后将相应的内存释放。

合并速度慢,内存无法及时释放

上面理论上解决了内存不足的问题,但是由于静态数据与动态数据合并,要涉及cs读写磁盘,以及cs与ups之间的网络交互,静态数据动态数据的合并逻辑,速度比较慢,这就有可能导致合并没有完成,所以内存无法及时释放,内存用光,无法写入。

解决这个问题的方法是每个ups都配置了几块ssd组成raid,当内存到达阈值时,先将动态数据dump到ssd,这样,内存不足时,可以马上释放相应的内存。每日合并时,静态数据+ssd上数据+内存中的数据进行合并。

数据dump到ssd后,读取相应数据,响应时间变长

dump到ssd的数据,实际上还是最近的比较热的数据,在每日合并完成之前,还会对它进行访问,访问频度仍然很高,读ssd的速度还是要小于读内存的数据,导致大量请求响应时间变长;

解决这个问题的一个方法是,dump完成后,并不马上释放内存,而是逐步切一部分流量到SSD上的dump数据,读取dump数据,要经过一层cache,这样就可以使cache先热起来,一段时间后再将动态数据内存释放。当然这个cache占用的内存要配置一个限制。

每日合并占用大量网络带宽的问题

上面的方法,解决的是ups的内存问题;然后,每日合并,涉及cs,ups之间的大量数据交互,主要是cs从ups拉取数据,而1.0之前的ups都是单点,大量的合并请求会导致ups网络占用过大;

  • ups一般使用万兆网卡,在一定程度上会缓解这个问题,但是不能根本上解决;
  • 合并一般在业务低峰期进行,也可以缓解这个问题,但是对于一些24小时都很忙的业务就无能为力了。

所以ups都会配置一个合并请求与正常请求带宽的百分比,对合并流量做一个限制。

但是这些方法对一些应用还是毫无办法,线上的一个OLAP的应用数据量特别大,每天写入也很多,如果做了限流,会导致每日合并非常慢,合并无法完成,又会导致dump ssd盘用完,无法及时释放内存。

针对这个应用,专门开发了小版本冻结,数据分发机制,将一个大版本拆成多个小版本,每个小版本,都会将数据推送到各个cs,从而将每日合并的过程所用时间平摊到日常时间,而且合并时,cs从本地读,也会加快合并速度,一举两得。

每日合并对单个ups压力过大的问题

对于QPS要求很高的集群,即使对每日合并的流量做了限制,也会对前台请求有影响;因为每日合并实际上读取的是一个快照数据,没有一致性的问题,所以每日合并的请求配置为尽量读备ups。

每日合并对集群的影响

ob一般有三个集群,如果三个集群都同时进行合并的话;会带来两个问题,一个是产生大量的集群网络流量,另一个一旦因为程序bug,导致合并失败,会很验证回滚;所以提出了错峰合并的概念,当合并某个集群时,先把这个集群上的前台访问的流量切走,或是切走一部分,然后进行合并,这个集群合并完成后,再合并另一个集群,这样就最大可能减少了对前台应用的影响,也可以预防因为某个bug,导致所有集群挂掉的情况;离职前的一段时间,就是因为有这个方法,通宵三个晚上,成功恢复因为一个bug导致的合并数据错乱的问题。

重写数据百分比的问题

每日合并,既然是动态数据与静态数据进行合并,那么就涉及一个更改数据百分比的问题。当然,我们无法做到仅仅读写更改的那行数据,因为这样需要大量的元数据,不可能做到。0.3,0.4这个判断更改的单位是256M(一个tablet的大小),只要这一个tablet中有一行数据发生了变化,就要重新读写整个tablet,这样对cs的读写压力会很大;但是如果将这个tablet的单位设置太小,又会导致文件太多,文件系统的管理开销很大;0.5使用了blocksstable,整个磁盘就是一个大文件,判断更改的单位是2M,这样就将更改比例优化了很多,增加了合并速度。

合并导致的大量小文件问题

既然是静态数据与动态数据合并,就要涉及一个tablet分裂的问题,在老版本的分裂算法,是只要大于256M,就要分裂,这样很可能就会产生一个大文件(256M),一个小文件(1M),一断时间后,会导致小tablet非常多,而rs管理元数据的开销非常大。(http://www.trueeyu.com/?p=198)

解决这个问题的方法是:当文件到达256M+128M时才分裂,这样就不会产生大量小文件。

表schema变化对每日合并的影响

数据不仅有数据的增删查改,还有表的结构的变化,比如增加一列,删除一列等;如果做了这种操作,这张表的数据都要重写,导致每日合并要重新读写所有数据,这会导致单次每日合并的时间过长。

解决这个问题的方法有两个,一个是只合并有数据更改的tablet,将schema变化持久化到本地,这样合并不会重写所有数据,但是影响读的速度。

另一种方法,将这次全量读写,分多次合并完,也就是最终采用的渐近合并方案。

单个cs合并速度的问题

上面都是整体上的一些问题,优化单个cs同样重要。以前,线上经常出现,只有几个盘在读写,其它盘空闲的情况,导致总体合并时间比较长;所以要对tablet的磁盘均衡算法做一些设计,综合考虑table_id,range,tablet数据,tablet大小等因素,这里不做细讲,其实这个与tablet在cs间的分布的优化原理是一样的。

合并失败的问题

上面说的都是理想情况下的问题,但是也会遇到一些比如硬件错误,网络阻塞,程序bug导致的合并单个tablet失败的问题,这样有可能导致某个cs永远无法合并完成;所以当出现重试多次还失败的情况,就会查询rs,其它cs是否已经有合并完成的副本,直接迁移过来就可以;当然迁移,一旦与渐近合并,默认值等功能结合在一起,会特别复杂,实际上在测试阶段也出现了很多问题,特别是schema这一块,这也导致了1.0的静态数据schema管理的重构。

合并对cache的影响

合并一般涉及的是一次性的读写,所以不允许他读写cache。

合并中过期数据删除的问题

一些应用的过期数据,为了性能的考虑,是在合并过程中顺便删除的;但是对于过期条件的判断,会非常耗性能,特别是时间类型的转换,开销特别大,针对这个问题,也做了很多优化,这里不详解

合并中对行迭代的要求

合并一般是整行的读取,而正常请求一般是读取几列,所以针对合并,不需要进行一些类型列索引的迭代逻辑,优化单行的迭代速度

合并中使用AIO

合并一般是2M块的读写,在AIO层也做了相应的优化

其它

合并中还出过很多大大小小的问题,记得的先有上面这些。
1.0的架构,上面的某些问题不再存在,但是还会有其它一些问题。

Comments

2015-06-04