2.0解析系列 | OceanBase 2.0 之 索引实时生效

OB君:本文是 “ OceanBase 2.0 技术解析系列” 的第七篇文章。今天咱们来聊聊数据的持续可用,说说2.0的索引实时生效功能。更多精彩欢迎关注OceanBase公众号持续订阅本系列内容!

引言

随着业务的快速发展,其对数据库的数据访问规则是不断变化的,在数据库中新建索引来加速业务查询是很常见的需求。算法

互联网的业务规模和发展速度对数据库的索引构建提出了更高的要求,一方面,在海量的业务规模下,非故障致使的停机是不可接受的,这意味着索引构建的同时,正常业务的读写请求不能被影响;另外一方面,业务的快速发展和迭代,对索引构建的效率也有着更高的要求,索引更快速的生效,能加速新业务的开发和迭代过程。数据库

传统单机关系数据库通过几十年的发展,逐渐实现了索引实时生效功能,这些数据库主要解决的问题是在索引构建的时候,避免长时间的锁表影响正常的业务请求。而分布式数据库因为其分布式的特性,在实现索引实时生效时,面临和单机数据库不一样的问题.网络

OceanBase 1.x中经过把索引构建放在合并流程中避免了这些问题,但并无作到索引实时生效,从用户执行建立索引表语句到索引表生效须要通过一或两次合并,OceanBase 2.0中解决了其中的问题,索引构建再也不与合并耦合,用户执行建立索引后,能当即进入索引构建流程,较大地缩短了索引构建生效时间。架构

本文首先介绍了关系型数据库索引构建的发展示状,接着描述了OceanBase 1.x中索引构建遇到的问题,最后分析了OceanBase 2.0的索引构建设计,并给出了解决这些问题的方法。并发

索引构建的现状

根据架构不一样,关系型数据库分为单机数据库和分布式数据库。不一样的架构下,索引构建的方案有所不一样,本节将分别介绍业界单机数据库和分布式数据库的索引构建方案。负载均衡

单机数据库

咱们以MySQL为例,来描述单机数据库的索引构建方案。MySQL从5.6开始支持索引实时生效,首先,执行完建立索引语句以后,新事务中新索引表的数据会写入到Row Log中,与此同时,会等待未往索引表中写入过数据的事务都结束。当全部的事务都结束后,开始索引构建流程,主要是处理两部分数据,一部分是等事务结束以后的主表快照点数据,此部分数据在索引表中是不存在的,须要经过主表数据构建出来,另外一部分数据是记录在Row Log中的数据,须要应用到最终的索引表中。构建完成以后,将索引设置成可读写状态,进而优化器能使用该索引来优化用户的查询。框架

MySQL索引构建的特色以下:运维

  1. 是为In Place Update的存储引擎设计的,为了不索引构建和用户事务对索引表更新的并发问题,索引构建过程当中的更新数据会记录到Row Log的特殊存储中,这部分数据须要从新写入到索引表中,写入的时候会有必定的加锁时间;
  2. 基于快照点的构建流程是串行的,面对大数据量场景下性能可能存在不足;
  3. 元数据只有单版本,在更新相关元数据时,会加锁。

分布式数据库

和单机数据库不一样的是,分布式数据库的数据分布和请求执行多是分布在多台机器上的,致使索引构建方案也有所不一样。本节将介绍Google研发的分布式数据库F1的索引构建方案。分布式

如上图,Google F1采用的是存储计算分离的架构,架构上总共分为三层,最底层是分布式Key-Value存储引擎,第二层是无状态的计算层F1 Server,第三层为代理层,应用程序经过代理层和F1交互。性能

在此种架构下,事务执行时,有可能出现不一样的SQL语句在不一样的F1 Server执行的状况,那么不一样的语句可能使用了不一样版本的关系型元数据(为了设计和实现简单,F1只容许系统中同时出现两种不一样版本的元数据),这会致使以下问题。

假设元数据版本S1 < S2,且S2比S1多了一张索引表,有以下执行过程。

  1. 在S2版本的F1 Server上执行INSERT语句,因为S2版本包括索引表,所以会生成索引表相关的KV记录;
  2. 在S1版本的F1 Server上执行DELETE语句,且和1中INSERT语句使用相同的主键,因为S1版本不包括索引表,所以索引表相关的KV记录不会被删除。

当上述事务执行完成后,索引表将会有多余的中间数据,致使数据表和索引表的数据不一致。

F1为了解决这个问题,引入了中间状态和最终状态,其中中间状态包括,delete-only和write-only,delete-only表示索引表只能被delete和update,而write-only表示索引表只能被insert、delete和update。最终状态包括absent和public,分别表示索引表不存在和索引表生效。在上述出问题的场景中,添加索引表的变动通过了absent->write-only->public的过程,因为absent状态时,索引表不存在,致使没法删除索引表的数据,所以,F1将添加索引的流程变成了absent->delete-only->write-only->public,这样就能保证索引构建完成后,数据和主表保持一致。

整体来看,Google F1的索引构建方案有以下特色:

F1 Server中最多存在两个不一样版本的元数据,这意味着若是有机器在一个元数据更新租约时间内没有刷新到新版本元数据,那么F1 Server必需要自动退出以保证这个约束,使得这种方案比较适用于计算存储分离的架构,另外,为了不F1 Server频繁因网络抖动主动退出,元数据更新租约时间通常是分钟级别,所以,对于数据量较小的表格构建索引,也须要分钟级别才能生效。

OceanBase 索引构建方案

本节先简单介绍下OceanBase的总体架构、存储引擎特色以及索引表的写入流程,接着讨论OceanBase 1.x索引构建中碰到的问题,最后描述了OceanBase 2.0中的索引构建方案。

总体架构

OceanBase的一个集群一般由多个zone组成,一个zone由一个或多个ObServer组成的,每一个ObServer都具备计算和存储的功能。在ObServer中有一个较为特殊,负责总控服务的节点称为RootService,负责管理集群的元数据和路由信息,其中,元数据是按照多版本方式管理的。OceanBase按照分区的方式管理数据,一张表包含一个或多个分区,每一个分区的数据会存储在多个zone中,每一个zone都是一份完整的数据拷贝(副本)。每一个分区的副本中会有一个Leader副本,负责处理该分区的读写请求。

存储引擎

OceanBase的存储引擎是按照Log Structured Merge Tree(LSM Tree)方式组织的,分为基线数据和增量数据两部分。基线数据存储在基线SSTable中,增量数据存储在Memtable和转储SSTable中。基线SSTable按照版本递增的方式来管理,某个版本的基线SSTable一旦生成后就变成只读状态,修改的数据会存储在Memtable中,当达到必定内存阈值后会先进行minor compaction(转储)转换成转储SSTable,当转储SSTable达到必定数量时,会将基线SSTable和转储SSTable作major compaction(合并),生成更高版本的基线SSTable。

在SSTable和Memtable中数据都是按照表的主键排序的,例如,有一张数据表test,包含列c一、c二、c三、c4和c5,其中主键为c1,那么test的数据是按照列c1的升序方式存储的;若是在数据表test上建立一个索引index,假设包含列c3和c2,那么index是按照主键c三、c2和c1的升序方式存储的,其中存储c1列是为了方便回表查询。

索引表的写入流程

在介绍索引表写入流程以前,先来看看OceanBase索引表的分区管理方式。OceanBase中索引表分为局部索引和全局索引。

局部索引是指分区规则和主表相同的索引,因为分区规则相同,局部索引和主表共用相应的分区,所以,局部索引分区和主表是在同一台机器上的。

全局索引是指分区规则和主表不一样的索引,分区规则的不一样致使了全局索引和主表没法共用分区,而分区是OceanBase管理的基本单位,分区不一样意味着全局索引和主表的分区是可能不在同一台机器上的。

索引表的写入一般是由主表驱动的,对主表的写入操做通常分为INSERT、DELETE和UPDATE,以上面的test主表和index索引表为例,对于INSERT,假如对test主表执行INSERT INTO test values(a,b,c,d,e),会根据索引表的列生成索引行(c,b,a),写入到索引表中;对于DELETE,假如对test主表执行DELETE FROM test where c1 = 'a',会经过主表中的数据,获取索引列c3和c2,假设值为c和b,拼成完整的索引行(c,b,a)并删除索引表中对应的行;对于UPDATE,假如对test主表执行UPDATE SET c3 = c' where c1 = 'a',首先会先获取主表中的数据,获取索引列c3和c2的数据,假设值为c和b,拼成完整的行(c,b,a)并删除索引表中对应的行,而后生成新的行(c',b,a)并写入索引表。对于惟一索引,更新时会检查写入的数据是否知足惟一性约束,具体地,须要检查索引表已有的数据中是否存在将要写入的行,若是存在,则会报惟一性冲突。

OceanBase 1.x 索引构建面临的问题

在OceanBase 1.x中,用户执行建立索引语句后,会等到集群下次合并时开始构建,构建过程当中先等待主表合并到新版本后,再基于主表的最新版本的基线SSTable数据,构建出索引表的数据。OceanBase 1.x索引构建有以下问题:

  1. OceanBase 1.x缺乏指定快照点读取数据的功能,索引构建依赖合并的快照点;
  2. OceanBase 1.x中只有局部索引,能经过同台机器的主表数据构建索引表的数据,而OceanBase 2.0中新增了全局索引,构建过程变成了分布式排序,如何以较小代价实现分布式排序呢?
  3. OceanBase 1.x中主表和索引表SSTable版本号是统一管理的,二者的版本号须要统一推动,而若是索引实时构建的话,可能在索引构建的同时,主表数据已经更新了多个版本,OceanBase 1.x的SSTable版本管理方法没法知足该需求。

OceanBase 2.0 索引构建方案

本节经过描述整个索引构建的流程,来介绍索引构建的方案设计以及如何解决相关问题的。整体上,OceanBase 2.0中,索引构建分为准备、构建、拷贝和收尾共四个阶段。

1)准备阶段

在索引构建准备阶段主要作了两件事情:

  1. 生成索引表的元数据信息,其中索引表设置成只写状态,根据LSM Tree存储引擎的特色,索引表构建期间的数据直接写入到索引表的Memtable中,带来的好处是这部分数据直接成为索引表的一部分,后面无须再将这部分数据插入到索引表中,而且能够复用前面小节描述的索引表写入流程的代码;
  2. 等待以前未往索引表插入过数据的事务结束,当全部的事务都结束后,获取构建快照点,构建阶段将基于此快照点扫描主表数据,并写入到索引表基线SSTable中,而用户事务产生的数据写入到Memtable中,这样就无须处理索引表构建和用户事务同时对索引表写入致使的并发更新问题。

步骤1中涉及到元数据的变动,OceanBase中采用多版本方式管理元数据,为了不分布式环境下不一样ObServer元数据版本不一样带来的问题,采用以下方案解决。

  1. ObProxy将同一个事务的请求发送给同一个中控ObServer处理,这样能够保证事务的不一样语句在中控ObServer看到的元数据版本是递增的,从而避免语句级别的元数据版本回退致使数据不一致的问题;
  2. 同一条语句可能会涉及到多个ObServer,可能会出现多个ObServer的元数据版本不一样的状况,若是出现和中控ObServer的元数据版本不一致时,则进行语句重试。

准备阶段最重要的输出为快照点,在传统单机数据库中,获取快照点比较容易的,通常获取系统当前时间戳便可,但在分布式数据库中,因为每台机器的时间戳不多是彻底一致的,所以,不能简单的获取某个机器的时间戳做为快照点。OceanBase 2.0中实现了租户级全局时间戳,每一个租户提供一个授时服务,每一个租户的事务版本号都经过授时服务来得到,一样,索引构建准备阶段的快照点也经过授时服务来得到。

2)构建阶段

构建阶段的目标是基于主表快照点扫描出索引表所需数据,并按照索引列排序规则生成索引表基线SSTable数据。

在OceanBase 1.x中,Memtable转换成转储SSTable时,多版本的数据会被归并成单版本的,所以,数据一旦发生转储,且快照点落在转储SSTable范围内,则没法读取快照点数据。为了可以基于某个快照点扫描主表的数据,OceanBase 2.0中,Memtable转成转储SSTable时,会把Memtable中全部数据及其版本号都记录下来,从而扫描数据时,能根据快照点和数据的版本号,读取到所需版本的数据。

排序根据索引类型不一样,执行过程也不相同,对于局部索引,索引表分区和主表相同,所以,构建阶段的数据流动仅仅是在本机,而对于全局索引,索引表分区和主表不一样,一个索引表分区的数据一般来自主表的多个分区,而这些分区多是在不一样ObServer上的,是一个分布式排序过程,整个构建过程描述成了一个SQL的plan。

和面向OLTP的分布式执行不一样,索引构建的分布式排序更关注容灾,在机器故障等状况下,能以较小的代价快速恢复,OceanBase 2.0的SQL执行框架中支持构建过程的中间结果持久化,构建过程出现机器宕机时,只须要选择其余机器从新执行故障机器相关的任务。同时,为了加速构建过程,将数据扫描和排序都作了并行化,充分利用磁盘和CPU的并行能力。

构建阶段可能会遇到合并,此时主表的基线SSTable的版本会增长,在索引表构建好以后,其版本号是落后于主表的。为了能描述这种场景,咱们对基线SSTable管理功能作了重构,每一个SSTable单独管理版本号,可以保证索引构建时,即便索引表落后主表多个版本,也能构建成功。

在生产环境,主表数据量一般比较大,索引构建每每是比较耗时的操做,为了不索引构建对正经常使用户请求产生影响,OceanBase 2.0中会基于用户IO请求来限速,当用户请求的IO响应时间超过必定阈值时,会自动限制构建过程当中IOPS。

3)拷贝阶段

鉴于索引构建比较耗时,OceanBase 2.0中只在单副本上构建索引表,其余副本从构建好的副本拷贝数据。在单副本构建期间写入索引表的数据会经过一致性算法同步到索引表多个副本上,所以,拷贝阶段仅仅须要将构建好的基线SSTable拷贝到其余的副本上。和构建同样,咱们对拷贝过程也作了并行化,每一个分区会按照数据量切分红多个任务,由多个线程批量地执行任务。为了不并行拷贝过分的消耗资源,会对拷贝阶段总体网络带宽做限制,同时也提供了拷贝的并行度控制,方便运维。

4)收尾阶段

收尾阶段分为两步,分别是数据校验和索引生效。数据校验根据索引类型的不一样,会作不一样的操做,对于普通索引,会根据构建阶段计算的主表的列校验和索引表的列校验和进行比对,保证构建出来的索引表的数据和主表的数据是一致的;对于惟一索引,由于构建过程当中也有可能写入数据,而构建过程当中索引表的基线数据还未构建完成,惟一性校验可能不完整,所以,须要在索引表基线数据构建和拷贝完成后,对惟一索引作惟一性校验,同时为了保证索引构建的数据正确性,咱们采用将主表和索引表数据进行校验,既验证了数据正确性,也验证了索引是否知足惟一性。

当数据校验经过时,将索引表设置成可读写状态,进而SQL优化器能使用新的索引来加速查询,当数据校验不经过时(通常是惟一索引惟一性约束不知足),会将索引设置成不可用状态。

总结

OceanBase的索引实时生效包括两层含义,第一是索引构建过程从合并中解耦,用户触发后能当即进行构建流程,第二是经过专门为LSM Tree存储引擎优化的构建流程,构建流程的并行化,单副本构建,副本拷贝的并行化,以及容灾时快速恢复等技术手段,有效地加快了索引生效的速度。

OceanBase的索引构建时能对占用的资源作控制,减小对正经常使用户请求的影响。经过完备的数据校验,保证了构建完成的索引表的数据正确性。另外,当前的索引构建方案对计算存储一体化以及计算存储分离的架构都是适用的。

参考文献

1. Innodb Online DDL Operations

2. Online, Asynchronous Schema Change in F1

OceanBase技术交流群

— 想了解更多OceanBase 2.0新特性?

— 想与蚂蚁金服OceanBase的一线技术专家深刻交流?

扫描下方二维码联系小编,快速加入OceanBase技术交流群!

2.0解析系列文章

相关文章
相关标签/搜索