可扩展的分布式RTOLAP系统 r-store,支持多版本,每个版本有时间戳。
每个OLAPquery 在他提交的时间点已经存在版本的数据
而每个OLTP创建新的版本。
使用MR框架,OLAP的mapper直接访问存储在存储系统中的实时数据
存储系统是由Hbase扩展的
A.R-store架构
4个部分: 分布式kvstore,streaming系统保存real-time data cube,MR系统处理大规模OLAP请求,MetaStore存储全局变量和配置信息。
OLTP请求直接提交给Kvstore,OLAP请求由MR处理。
最简单的方式就是MR扫描所有的real-time数据 找到OLAP提交之前的最新版本。
Kvstore需要多版本并行控制 避免OLAP和OLTP不会相互block,但是这个非常低效。
改进:将real-timetable 物化为data cube成为历史数据。当OLAP提交时,先从MetaStore获得query的时间戳,经过costmodel优化,OLAP转换成Mrjobinput由历史数据datacube 和实时数据real-timedata组成。为了能够高效的访问real-time data,kvstore支持增量扫描。扫描Real-time data使用的是IncrementalScan操作,扫描data cube使用FullScan。
接下来介绍的是如何将real-timedata合并到datacube中。我们希望能够加速这一操作。
为了能够高效的更新datacube,对于kvstore的更新会stream到流系统中,real-time data cube会保存在流系统中,real-timedata会周期的物化 来更新datacube。这样比重新算datacube快多了,吞吐量也足够处理来自kvstore的更新流。
一旦更新完成,data cube的最近时间戳就传到metaStore中,然后唤醒压缩进程来压缩real-timedata。MetaStore还存放其他全局信息包括每个OLAP的提交时间,datacube的更新频率。
B.存储设计
Kvstore必须支持multi-version concurrency control来保证OLAP和OLTP不会相互block。
另外还要考虑到许多东西,高效的文件扫描操作,compaction scheme 负载均衡。
1)Full and Incremental Scan:
Fullscan(ti)Ti作为input,返回所有key的在Ti之前的最新版本,这个数据是用来计算data cube用的。
IncrementalScan(T1,T2)拿T1T2作为输入,对于每个key返回T1之后的两个版本号。一个是T2之前的最新版本,另一个是T1之前的最新版本。这个操作用于RTOLAP请求的处理算法。
2)global and local compactions
Global compaction:这个进程在每次data cube更新之后都会执行。对于同一个插入data cube中的key的所有版本,都合并成一个版本 叫Vdc,与data cube对应,用于更新datacube。
Local compaction 本地压缩进程在每个节点上都被唤醒。首先,压缩程序会获得当前正在运行的扫描进程的提交时间。对于每一个key,Tscan提交之前的最新版本的数据都会被这个进程访问。因此本地压缩进程就之访问那些scan进程不会访问的数据。还有就是Vdc不会改变。
3)负载均衡:有一些app里面某些范围的key会被经常改动,造成节点上的负载不均。另外由于update采用的是insert新版本数据所以节点上的会有数据的倾斜(某些key的版本要比其他多)。这会影响到scan的效率。解决方法就是把经常被更新的范围分散到多个节点上。。。。。。
- Data Cube Maintenance
为了提高OLAP的执行效率,data cube保存在kvstore中。Datacube可以使full Iceberg或者closed cube。根据应用来选择最合适的datacube类型。我们只考虑fulldata cube,which consists of a lattice of cuboids。有两种更新data cube 的方式。
但是,并不是所有的cube都能增量更新。增量更新只能用于self-maintainable aggregate functions(新值可以从老的值计算出来)比如SUM,COUNT等等。
在R-store中,重新计算的方式用于第一次建立datacube,增量更新的方法用于在流处理模块中维护real-timedatacube。流系统使用从kvstore来的流来更新自己的datacube,并且周期性的将其物化到存储系统中。由于data cube的更新包含两个阶段,因此可以自然地使用MR来处理,流版本的MR用于R-store的流处理模块。
④R-store的实现
Hbase开源分布式kvstore。简单介绍一下Hbase的数据结构。
增量scan:对于store 并行的访问storefiles和memstore,key按照升序扫描,大的时间戳能早一点扫描到。扫描key的所有版本,和时间戳作比较返回需要的两个版本。如果key只有一个版本,那就只返回这个版本。
对于data cube更新扫描kv对是开销非常大的一步。所以要提升增量scan的性能非常重要,我们提出了一个合适的增量scan算法。
首先我们使用一个in-memory的结构来评估d(T),上一次datacube更新之后到现在的更新的不同key的数量。使用一个bitarray,因为每个region的key都是连续的,所以其实就可以给他们hash到0-M-1的范围中,one billion key on one node needs 128MB
为了提升性能,T1设置为Tdc,T2设置为Tq(提交时间)。先扫描memstore中的kv对。Memstore中有很多个版本,只有最新的版本放在kvMap中。然后计算Tdc之后更新的但是没有记录在memstore中的数量,然后估算随机读取这些kv对的代价。如果代价比扫描Tdc和Tq之间所有的数据的代价小,那么就使用storefile的索引来直接读取这些kv对。这样用于更新的最新版本的key值就得到了。然后只要简单的扫描Tdc之前的数据,就可以把Tdc之前的最新版本返回给客户端了。
普通方式:并行的扫描memstore和storefile 找到想要的版本
改进版:先扫描memstore 评估的d(T) 计算随机读cost 如果小于扫描 就随机读。
压缩:Hbase默认压缩进程将所有的storefile压缩到一个文件,并且每个key只保留一个版本。全局压缩类似于默认值,但是触发情况不一样;本地压缩之压缩某个时间戳之前的版本。为了保证压缩进程不会blockscan进程,压缩数据放在不同的文件中,而不是直接替换掉未压缩的数据。老版本被压缩文件替换掉 当他们不再被任何scan进程访问时。由于压缩进程与OLAP争夺CPU和IO资源,压缩的频率和整个系统的性能就存在一个tradeoff。当
(平均每个key的版本数超过阈值)超过阈值时本地压缩进程被触发。
负载均衡:Hbase的默认region大小是256MB。大于这个值就分裂,放到不同节点上。Hbase默认设置只能存放一定版本数量的key。更多之后就强制把老版本删除,这就需要用户手动分裂hot region。R-store就不要求强制移除老版本。等到size到了上界自行分裂。。。。。。你在逗我。。
- Real-Time Data Cube Maintenance
R-store使用Hstreaming来存放real-time data cube(其他流式mr系统也可以用)。Hstreaming的每个mapper负责处理一定范围内key的更新。当一个keyupdate的请求到达,这个key的旧值is retrieved 从本地存储中检索,如果存在的话。为了高效的检索老数据,需要给kv对建立聚集索引,经常更新的key会缓存在内存中。在实际情况中,更新通常发生在小范围的key中,旧值有很高的概率需要从cache中检索到。Mapper:如果key是新的,对于每个cuboid,创建一个kv对然后shuffle到reducer中。Map输出的key是
Cuboid+kv.value,value就是kv.value。
Reducer:每隔wr时间invoke一次。另一个时间间隔wcube定义data cube的物化间隔。算法3说的是reduce函数如何增量更新DC。如果是在下一次Tdc发生之前产生的updata,reducer就把local DC和mapper给的中间值merge起来作为DC,否则存放在△DC‘。当所有mapper上的update时间戳都要大于等于下一次Tdc时,data refresh进程就被唤醒,将local DC写到Hbase-R中。在refresh进程运行的过程中到来的update仍然写进△DC’中,因为他们的时间戳不可能比Tdc还小。当refresh结束,Tdc增加,DC和△DC’merge在一起。
传统的流式系统中要容错,计算的状态就要周期性的checkpoint。Checkpoint之后的数据流写进log中用于恢复。R-store中DC物化到kvstore中实际就是对real-time dc的进行checkpoint。由于上次DC更新后的kv对还存放在real-time data中 虽然可能会有一些压缩少了一些值,但是该有的还都在。所以real-time dc的恢复只需要用dc和real-timedc就可以恢复了。
- Data Flow of R-Store
图二描述了Hbase-RHsreaming和MR的数据流动。
每个regionserver 处理几个region。有一些是real-timedata的,有一些是DC的。OLTP提交到某个region server上,存放在这个region的memstore中。如果memstore超过了上界就写到storefile中存在HDFS里。一旦把更新写入Hbase-R,就把以流的形式输入Hstreaming的mapper中,每个cuboid的改变都会计算并shuffle到reducer里。Reducer这边,real-time datacube把这些更新的信息存放在本地。每隔一段时间,Hstreaming就把本地的datacube物化到Hbase-R里,然后把最新的DC时间戳通知MetaStore。压缩进程开始启动,将DC更新之前的版本压缩掉。
⑤real-timeOLAP
之前说的是R-strore的架构。本节讨论real-time OLAP是如何执行的。在R-Store中如果MR job的输入只有DC,那么在扫描阶段 map端的performance是最大化的,但是结果可能就比较老旧。为了最大化OLAPquery的新鲜度,所有在query提交之前的kv对就都应该考虑在内。因此除了DC real-timetable也必须扫描。
假设DC的创建时间是Tdc OLAP的提交时间是Tq。对于Tdc之后更新的key,如果Tq和Tdc设置的不一样的话,那么在realtime data 上执行的IncrementalScan会返回Tdc之前的版本和Tq之前的最新版本。通过使用这两个版本 和每个cuboid上的值merge一下就可以得到需要的value,并且满足OLAP对freshness的需求。下面的小节介绍了query processing algorithm 使用了IncrementalScan操作,该算法叫做IncreQuerying
- Querying incrementally-Maintained Cube
实现了MultiTableInputFormat 每个MR job都可以扫描多个table上的数据,并且扫描操作可以使full 或增量scan。这样就可以通过fullscan访问cuboid表上的数据,增量scan就可以访问real-time表上的数据。
算法4描述了map。Mapper根据filter
The mappers增加filter来区别数据来自cell还是tuple。对于用来计算聚集函数的cell 和tuple把它们分配到同一个reducer上。Cell的输出就是the selected numeric value,tuple的输出是原值,用来重新计算numeric value。Value加上tag Q+-Q表示是否是cell的值,-代表旧值,+代表新值。过程与增量update比较相似,除了增加一个filter 并且partition key和DC的维度数不一样。???
Reduce。Reduce根据每个cell的老值、cell的变化量及聚集函数计算出新值。Cell key的reduce和算法3不同。
For example, for the TPCH part table,to compute a rectangular subset of the cube
(mfgr =“Manufacturer#13”), the key of the reduce function
is the combination of the attributes (brand to container) after
removing mfgr.
图三show the data flow OLAPquery on a 二维cuboid(mfgr,brand)
计算每个由M1生产的牌子的price的和。为了保证结果的freshness,两个表都要扫描。注意到cuboid的列key是二维属性的结合。因此,如果filtercondition里面包含一些
“Manufacturer#1” and “Brand#13”这样的限制,能够作为row key的前缀,Hbase-R中的扫描就可以避免扫描整个DC。
The min key for the range scan is “Manufacturer#1,Brand#13”,
and the max key is “Manufacturer#1,Brand#14”.
我们把merge real-time数据和DC的操作 对user透明,我们封装了一些新的DC操作,并且自动的将这些操作转化成Mrjob。算法5
- Query结果的准确性
OLAP提交时 这个query从MetaStore拿一个Tq的时间戳。为了保证正确性,如果query需要多次扫描一个表,每个node上的扫描进程总会返回Tq之前的数据。然而,在分布式系统中,尽管可以在一定程度上同步时钟,不同节点上可能还是有不同。如果节点k上的当前时间戳Tk比Tq小,那么多次扫描节点k的表就会导致不一致。解决方法就是等到tq小于等于tk。在局域网时钟同步能够达到1ms的准确性,因此相比于执行时间,这个延迟可以忽略。
- Cost Model
IncreQuerying算法并不总是好的。增量扫描不仅扫描real-time table也扫描DC,导致更高的代价。另外他一个keyshuffle2个版本的数据到MR中。当只有少量的OLTP货OLTP之访问比较小范围的key时,IQ算法比较好因为IS只发送少量的数据。另一种real-time querying的实现方案类似于重新计算DC:使用fullscan,不论他是否被更新过,每个KV对只返回一个版本。当update均匀分布在所有key上时,这个实现方式更加高效。为了选择一种更为高效的方式,我们提出了costmodel。最重要的是是s(T)=d(T)/|T|DC更新后 更新过的key占的百分比。
- )基础方法:用普通的IS还是使用adaptive的IS取决于节点的状态,因此我们就都当做是普通的IS。所以开销我们就按照传输的tuple数量来计算。Mapper扫描,mapper输出,外部排序,mapper shuffle到reducer,归并,写入文件系统
把每个key传输一个版本,MR job扫描阶段的开销就是
Cscan = shHBase × |T | × f(T)
Mapper的输出是每个tuple一个kv对,因此map的输出大小就是
SMO = s(Q)× (|T|/mT) × (d(Q)+ n(Q))
外部排序map的输出开销:
Csort−map = mT × 2cL × ((SMO × logB(SMO/(B + 1)))
当mapper结束,reducer开始。Shuffle的开销:
Cshuffling = shMR × s(Q) × |T| × (d(Q)+ n(Q))
拉过来的数据放在本地文件系统,开销可以忽略,
shuffle和本地写是流水线方式的。数据用多路归并,只需读写文件一次:
Creduce−merge = 2cL × s(Q) × |T| × (d(Q) + n(Q))
最后结果表写入Hbase-R,开销:
Creduce−write = wHBase × |Q| × (d(Q) + n(Q))
- )IQ算法:Mpjob会读DC和updated 数据。Mapper扫描real-time data和DC。
把数据shuffle给mapper的开销是
Cscan−R = shHBase × 2|T| × f(T) × s(T)
扫描CD的开销时:
Cscan−C = shHBase × |C| × (d(C) + n(C))
Map的输出大小是:
SMO−R = 2 × (s(Q) × |T| × s(T)/mT ) × (d(Q) + n(Q))
SMO−C = (|C|/mC)× s(Q) × (d(Q) + n(Q))
外部排序开销:
SMO−R = 2 × (s(Q) × |T| × s(T)/mT ) × (d(Q) + n(Q))
SMO−C = (|C|/mC)× s(Q) × (d(Q) + n(Q))
Mapper到reducershuffle的cost:
Cshuffling =shMR × s(Q)×((2× |T| × s(T) + |C|) × (d(Q) + n(Q))
Reduce阶段,sortmerge的cost:
Cshuffling =shMR × s(Q)×
((2 × |T| × s(T) + |C|)× (d(Q) + n(Q))
写进HDFS的代价:
Creduce−write = wHBase × |Q| × (d(Q) + n(Q))
根据这个cost model,就能动态决定real-time query提交之后选择哪种方式。