Dremel-大数据上的交互式分析

Dremel-大数据上的交互式分析

 翻译自:http://research.google.com/pubs/pub36632.html

摘要:

Dremel是一套用于分析只读嵌套数据的可扩展交互式即时查询系统。通过结合多层执行树和数据的列组织,Dremel可以在秒级完成上万亿行表的聚合查询。这个系统可以扩展到几千个CPUP级别的数据,在Google有几千个用户。本文将描述Dremel的架构和实现,并解释它如何作为MapReduce计算的补充。此外我们也描述了一种新的针对嵌套记录的列存储形式,并分析了一些在几千个节点上跑出来的实验。

1.介绍

现在,互联网公司及其它各行业都已经不再仅仅满足如何用低成本的存储来收集大量的业务数据,进一步的大规模数据分析处理也开始被广泛应用了。对于数据分析师和工程师来说使数据随手可取变得越来越重要了。交互响应时间在数据的浏览、监控、在线客户支持、快速原型、数据流的debug及其它任务中有本质的区别。

通过大量的并行,交互式的数据分析可以更好地扩展。举个例子,使用现在的商用硬盘在1秒内读取1T的压缩数据需要几千块盘同时运行。同样,在几秒内完成CPU密集的查询也需要几千个核并行。在Google,大量的并行计算在共享的商用服务器集群上完成。一个集群上一般都部署了大量的分布式应用,这些应用共享服务器资源,负载情况差别也很大,也可能以不同的硬件参数运行。在分布式应用中,经常会存在一两个执行者比其它执行者花更长的时间才能完成任务,甚至可能因为故障失效或者被集群管理系统抢占导致永远无法完成。因此,为执行更快完成以及容错,处理这些长尾和故障失效是非常必要的。

web和科学计算中使用的数据经常是非关系型的。因此,一个灵活的数据模型对于这些领域来说是非常必要的。编程语言中的数据结构、分布式系统中的消息交换、结构化文档等都有嵌套的表示形式。这些数据的规范化和重建在互联网级规模上的代价是非常昂贵的。在Google大部分的结构化数据处理底层采用了一个嵌套的数据模型,其它一些主要的web公司也类似。

本文描述了一套支持交互式大数据分析的系统Dremel,运行在共享的商用服务器集群上。与传统的数据库不同,它处理的是原生的嵌套数据。原生意味着在何处访问数据的能力,如在分布式文件系统(如GFS[14])或者其它的存储层(如Bigtable[8])。Dremel可以在这些数据上执行许多查询,而以往则需要一系列的MapReduceMR[12])任务,执行时间也只是MR的一小部分。Dremel并不准备取代MR,相反经常结合MR来作MR输出的分析或者是一些大型即时的快速原型。

DremelGoogle2006年投入使用的,迄今为止已经有几千个用户了。公司里也部署了很多套Dremel,节点数从几十个到几千个不等。下面是一些使用这个系统的例子:

抓取网页的分析

         Android市场里的应用安装数据分析

         Google产品的crash报告

Google BooksOCR结果

作弊分析

Google Mapmap区块的debug

Bigtable的子表合并

Google分布式build系统里的测试结果

成千上万的磁盘IO统计

Google数据中心各种任务的资源监控

Google codebase的符号表、依赖关系

         Dremel借鉴了web搜索和并行DBMS的思想。首先,它的架构采用了分布式搜索引擎[11]中服务树的概念。就像一个web搜索请求,一个查询沿着树一层一层往下,且每层都会进行改写,查询结果则是通过底下的树一层一层往上返回汇聚最终拼接而成。其次,Dremel提供了一个高层的类SQL语言可以表达各种查询。与Pig[18]Hive[16]等不同,Dremel并不是转换为MR任务来执行的。

         最后,很重要的是,Dremel采用了列存储形式,这使得它从底层存储上读取更少的数据,同时通过更低代价的压缩来降低CPU消耗。列存储在关系数据分析领域[1]已经被采纳,但在嵌套数据模型上还没有见到相应的一些应用。我们这里所描述的列存储格式在Google已经被很多数据处理工具所采纳,包括MRSawzall[20]FlumeJava[7]等。

2.背景

我们从一个场景开始,来说明交互式查询处理如何融入更广泛的数据管理领域。假想一名Google的工程师Alice,有了一个新的想法想从大量网页中提取新的信息。她运行MR任务从输入数据中跑出数十亿条包括所要信息的记录,存到分布式文件系统中。为分析实验结果,她通过Dremel执行了几条交互式命令:

         DEFINETABLE t AS /path/to/data/*

         SELECTTOP(signal1,100), COUNT(*) FROM t

         这些命令在几秒内就执行完了,之后她又做了其它一些查询来验证她的算法是否工作正常。她发现了signal1中有一个不规则的地方,为更深入的发掘其中的问题,她写了个FlumeJava程序在之前输出的数据集上进行一些更复杂的分析计算。这一步解决后,她又创建了一个管道来不停地处理新进的数据。她又写了些SQL查询来聚合管道各个维度的结果输出,并把它加到了交互式的操作界面上。最后,她在一个目录里声明了她的新数据集,这样其他工程师可以找到并快速查询。

         上面的场景需要查询处理和其它数据管理工具之间的协作。第一个要素是它们需要一个公共的存储层。GFS[14]就是这样一个分布式存储层,并已在公司广泛使用。GFS使用多副本技术来保护数据在硬件故障情况下不丢失,同时也可以加快一些长尾的响应。一个高性能的存储层对于原生数据管理来说是非常必要的。它允许访问数据不会有长时间的载入过程,而这恰恰是数据库应用于数据分析处理[13]的一个主要障碍,在数据库载入这些数据并执行查询前往往需要运行几十个MR分析。另外一个好处是,数据存在文件系统中就可以很方便地使用一些标准工具,如传到另外一个集群上,修改访问权限,或者基于文件名标记出需要分析的数据集合。

         第二个构建可协作的数据管理组件的要素是需要一个共享的存储格式。列存储已经被证明可成功应用于扁平的关系数据,但对于Google的需求还需要使得它支持嵌套数据模型。图1描述了主要思想:一个嵌套域里的所有值如A.B.C是连续存储的,因此,A.B.C被访问时无需读取A.E或者A.B.D等。这里的挑战是如何保留这些结构信息使得从所有域的任意一个子集可以重建记录。接下来,我们将讨论相应的数据模型及对应的算法和查询处理过程。

3.数据模型

本节我们将描述Dremel的数据模型以及后续需要使用的一些术语。分布式系统环境里的数据模型(即Protocol Buffers[21])在Google已经被广泛使用,且已经开源。它基于强类型的嵌套记录。下面是它抽象的语法描述:

         T = dom | < A1 : T[*|?] , … , An :T[*|?] >

其中,T是一个原子类型或者是记录类型。Dom中的原子类型包括整数、浮点数、字符串等。记录包括一个或多个域。记录中的域i有一个名字Ai和一个可选的多重域标签。可重复域(*)在记录中会出现多次。它们会被解析为值列表,故域在记录中出现的顺序也是至关重要的。可选域(?)表示在记录中存在零个或一个。除此之外,必选域则是只能出现一次。

考虑图2,它描述了一个记录类型Documentschema,来表示一张网页。Schema定义使用[21]中具体的语法。每个Document有一个整型的DocId,一个可选域LinksLinks有包含多条Forward和多条Backward记录其它网页的DocId。每个Document有多个Name,代表该网页所被引用的不同URL。每个Name包括一组CodeCountry对,其中Country又不是必须的。图2另外也包含两个记录的例子,r1r2,满足上述schema。记录的结构通过缩进体现出来。下一节我们将使用这些记录的例子来解释相应的算法。Schema中定义的域形成了一个树的层次。一个嵌套域的完整路径通过点号来表示,如Name.Language.Code

Google,该嵌套数据模型可以支持与平台无关的、机制可扩展的结构化数据的序列化。代码生成工具生成具体编程语言(如C++Java)的代码。跨语言的兼容性通过对记录的标准二进制化表示来保证,记录中的域及相应的值序列化后传输。通过这种方式,Java写的MR程序可以处理另外一个C++生成的数据源中的记录。因此,如果记录采用列存储,快速的重建对于MR和其它数据处理工具都非常重要。

4.数据模型

正如图1所述,我们的目标是对于一个给定的域连续存储所有的值以提高查询效率。本节我们将介绍这里的挑战,采用列存储格式无损地表示记录结构(4.1节),快速编码(4.2节),高效的记录重建(4.3节)。

4.1      重复级别和定义级别

         值本身不能传达出一个记录的结构。给定一个重复域的两个值,我们并不知道这两个值是在哪一级别上重复的(比如,这两个值是两个不同的记录里的,还是同一个记录里的两个重复值)。同样,给定一个丢失的可选域,我们也不知道是哪个记录里明确定义了。因此,我们引入了重复级别和定义级别两个概念,图3可以看到我们记录例子里所有的原子域对应的重复级别和定义级别。

重复级别考虑图2中的域Code,它在r1中出现了3次。’en-us’’en’出现在第一个Name里,’en-gb’出现在第三个Name里。为区分出这三者,我们给每个值关联一个重复级别,它用来表示这个值是在域路径上哪一个重复域重复的。域路径Name.Language.Code包含2个重复域,NameLanguage,因此Code的重复级别范围是02,级别0表示新记录的开始。现在假设我们从上往下扫描记录r1,当我们扫描到’en-us’时,我们遇到重复的域,因此它的重复级别是0。当扫描到’en’时,域Language被重复了,因此它的重复级别是2。最后,当扫描到’en-gb’时,最近被重复的是NameLanguage在这个Name后面只出现了一次),故重复级别是1。所以,Code出现的三次值其重复级别分别是021

         注意到r1中的第二个Name并不包含Code的值。为了确定’en-gb’是在第三个Name中出现的而不是在第二个Name中出现,我们需要在’en’’en-gb’之间添加一个NULL值(如图3)。Code域在Language域中时必须的,因此第二个Name中缺失Code的原因是Language没有定义。一般来说,决定哪一个嵌套记录存在的级别需要额外信息。

定义级别每个路径p对应域的值(包括NULL)另外用一个定义级别来表示p中有多少个域可以是无定义的(即该域是可选的或者是可重复的)但实际在记录中是存在的。我们看记录r1没有Backward links,但域Links是被定义了的,为保留这个信息,我们在Links.Backward列增加了一个定义级别为1NULL值。同样,r2中未出现Name.Language.Country,它的定义级别是1,相应在r1中未出现的两处定义级别分别是2Name.Language中)和1Name)中。

         我们使用一个整型的定义级别而不是空的字节使得叶子域(如Name.Language.Country)的数据包括它父域的出现情况。4.3节给了这些信息如何使用的例子。

         上面描述的编码过程对于记录结构是无损的。受篇幅限制,我们省略了对它的证明。

编码     每列存储为一组块。每个块包括重复级别和定义级别(后面简单称作级别)以及压缩的域值。NULL是有定义级别来决定的,所以它不会显式地存储。域路径对应的定义级别小于路径上可选和可重复域个数总和的即可认为是NULL。如果值总是被定义的,那么它的定义级别也不会被存储。类似,重复级别只在它需要的时候存储。比如,定义级别0意味着repetition 0级,所以后者可以被省略。事实上,在图3中,没有哪个级别是为DocId存储的。级别以字节流的形式打包。我们只用最小数量的bit。比如,如果最大定义级别是3,我们就为每个定义级别使用两个bit

4.2      将记录分解到列中

         上面我们描述了针对记录型结构按列存储的编码方法。接下来我们将描述如何更有效地生成包括repetition leveldefinition level的列stripe

         下面给了如何计算repetitionleveldefinition level的基本算法。算法递归地进入记录的结构并计算每个域的level。前面提到对于未出现的域也需要计算repetitiondefinition level。在google的应用中很多数据集是稀疏的,经常会见到有上千个域的schema,但实际记录中只包含上百个域。因此,我们需要尽可能降低未出现域的处理代价。为生成列stripe,我们创建了一颗用于域持久化的树,其结构符合域在schema中的层次。其基本思想是只更新有自己数据的域输出,除非觉得必要否则不会将父节点的状态往子节点传导。为达到此目的,子writer从他们的父节点继承level值。一有新值添加,子write就会同步到他的父节点的level

         上述算法中,RecordDecoder用来遍历记录,FieldWriter形成了与schema对应的树结构。算法运行到每个新记录时会传入根FiledWriter,此时的重复级别是0DissectRecord过程的主要工作是管理当前的重复级别。当前的定义级别则是由当前writer在树中的位置唯一决定,即域路径中可选和可重复域的总和。

         算法第5行的while循环遍历一个给定记录里所有的原子类型和记录值类型的域。集合seenFields用来跟踪一个域是否在记录中已出现过,它用来确定什么域最近被重复。子重复级别chRepetitionLevel被设置成最近重复域的重复级别或者默认是父域的重复级别(行9-13)。整个过程在嵌套记录上递归地被调用(行18)。

         4.2节我们大致描述了FieldWriter是怎样累计级别并把它们传给下一层的writer的。过程如下:每个非叶writer保存一个重复级别和定义级别的序列,每个writer还会有个版本号。Writer的版本号在有一个级别加入时会增加1。子节点会记录上一个父节点的版本号。当子writer上有非空值时,它会从父节点上获取最新的级别过来,然后再把新数据加进去。

         因为输入的数据有上千个域及百万条记录,把所有的级别存到内存是不现实的。有些级别可能还会零时存到磁盘文件里。对于空记录(包括空子记录)的无损编码,非原子域(如图2中的Name.Language)可能需要只包含级别不包含非空域值的列存储。

4.3      记录拼接

         利用面向行记录的数据处理工具(如MR)想要快速地从列存储数据中提取记录是非常困难的。给定一组域,我们的目标是重建原来的记录,就像他们只包含这些选择的域一有,其它域都已经被剥离了。这里的思想核心是:我们通过一个FSM(有限状态机)来读取各域的值和level,并顺序输出各域值形成完整记录。一个FSM的状态相当于一个所选域的解析器。状态的转换用repetition level标记。当解析器取到一个值,我们通过它的下一个repetition level来决定下一个解析器用哪个。每条记录即是FSM从开始状态到结束状态的过程。

         4给出了前面例子用于重建完整记录的FSM。起始状态时DocIdDocId读完后,FSM转到Links.Backward。在所有重复的Backward解析后,FSM转到Links.Forward,等等。

         下面描述完整的记录重建算法。

 

         在上述描述中,记录可以看做是一组由<域标记:域值>组成的对。嵌套记录可以通过起始标记和结束标记表示,类似XML

         AssembleRecord过程以一组FieldReaderFiledReader之间的FSM作为输入。变量reader在主流程中记录当前的FieldReader(行4)。变量lastReader记录上一个有值输出的reader,则在图17的三个过程里都有效。行5开始主while循环,我们取当前reader的下一个值,如果值非空,则看它的定义级别,方法MoveToLevel把记录同步拼接到当前reader的记录结构里,同时把域值也放进去。否则,我们只是调整记录结构,但不会添加任何值,这是在碰到空记录时需要做的。行12中,我们使用完全定义级别,回想一下,定义级别表示出了所需要的域(只有重复域和可选域才被计数),完全定义级别则计算所有的域在内。

         过程MoveToLevel将记录从lastReader状态转换到nextReader(行22)。比如,当lastReaderLinks.Backward(图2)时,nextReaderName.Language.Code。该方法结束了嵌套记录Links,然后开始新记录NameLanguage。过程ReturnToLevel(行30)与MoveToLevel正好相反,只结束当前记录而不会开始新的。

 

为描述FSM转换是如何构建的,定义L为当前域F的解析器所返回的下一个repetitionlevel。从schema树的F位置开始,我们找到它的祖先在L层重复,并选择该祖先的第一个叶节点域N。此即代表一个FSM转换(FL->N。举例来说,令L=1F=Name.Language.Country,它的repetition level1的祖先是Name,此时N=Name.Url

         下面描述FSM的构建算法。(附录C

         18描述了用来完成记录重建的有限状态机的构建算法。算法以记录中需要填的域为输入,并保持与它们在schema中的顺序一致。算法使用了一个概念,两个域的公共重复级别,表示这两个域的最低公共祖先。举个例子,Links.BackwardLinks.Forward的公共重复级别是1.第二个概念是barrier,表示当前域的下一个域,目的是我们挨个处理各个域直到碰到barrier并跳转到前面看到的域。

         算法包括三步。第一步(行6-10),我们从后往前遍历公共重复级别,这样保证是非增的。对每一个重复级别,我们选择序列中最左的域,当FieldReader返回重复级别时即转换到它。第二步,我们要开始填空了(行11-14)。这些空白存在时因为并不是所有的重复级别都出现在行8中计算的公共重复级别中。第三步(行15-17),我们设置所有级别等于或小于barrier级别的跳转到barrier域。如果FieldReader生成了这样一个级别,我们需要继续构建嵌套记录,且仍在该barrier之内。

 

         如果只有一部分域需要提取,我们可以构建一个更简单的FSM以便降低执行代价。图5描述了一个只读DocIdName.Language.CountryFSMS1S2是自动输出的两条记录。

         注意我们的编码和重建算法保存了域Country的完整结构,这对应用来说非常重要,比如应用要访问第二个Name下第一个Language里的country,类似XPath中的表达式/Name[2]/Language[1]/Country

 

5.查询语言

         Dremel的查询语言是基于SQL的,并为高效执行嵌套列存储而设计。本文并不打算给出该语言的完整定义,相反只是描述其重点思想。每条SQL语句(以及转换后的代数操作)以一个或多个嵌套表和它们的schema为输入,输出一个嵌套表及它的schema。图6描述了一个执行投影(projection)、选取(selection)及记录内聚合(within-record aggregation)的查询例子。该查询在图2的表T={r1,42}上执行。这些域通过路径表达式来表达。尽管没有记录构建的表述,但该查询输出了一个嵌套的结果。

         为了解释这条查询究竟做了什么,我们考虑selection操作(WHERE语句)。一个嵌套的记录可以看做一棵标记树,每个标记代表一个域。Selection操作会把不满足条件的树的分支裁剪掉。因此只有那些Name.Url有定义的且以http开头的嵌套记录才会被取出来。接下来考虑projection,每个SELECT语句中的数值表达式会在与嵌套中重复最多输入域的同层产生一个值。因此,字符串连接表达式在输入schemaName.Language.Code层生成Str对应的值。Count表达式说明了记录内的聚合操作。聚合操作在每个Name的子记录里完成,并产生一个64位的非负整形值表示Name.Language.Code在每个Name出现的次数。

         Dremel查询语言还支持嵌套子查询、记录内/跨记录的聚合计算、top-kjoins、用户自定义函数,等等。有些功能在实验章节里会有例子提到。

6.查询的执行

为了问题的简化,我们只讨论在只读系统里的相关实现。许多Dremel查询是只需要一次遍历的聚合计算,因此,我们主要解释这些查询,下一节会给出具体的实验。对于joinsindexingupdates等问题我们推迟到后续相关工作中讨论。

树型架构  Dremel实验多层服务树来执行query(见图7)。根服务器接收进来的查询,从表中读取元数据,然后将请求路由到服务树里的下一层服务器。叶子服务器直接和存储层通信,或者是访问本地磁盘的数据。

考虑下面这条查询语句:

SELECT A, COUNT(B) FROM T GROUP BY A

当根服务器收到这条查询语句时,会计算得到组成表T的所有tablets,如该表的所有水平切分,然后重写查询语句为:

SELECT A,SUM(c) FROM (R11 UNION ALL…R1n) GROUP BY A

R11,…,R1n是发给服务树第一层节点1n的查询结果:

R1i=SELECT A, COUNT(B) AS c FROM T1i GROUP BY A

TIi是表T在第一层服务器i所处理的一个切分。每层服务器执行类似的重写。最后,查询到达叶服务器,这些叶服务器并行地扫描表T的各个子表。查询完成后结果往上返回,中间服务器又会对部分结果做并行的聚合计算。这种执行模型对于返回中少量结果的聚合计算非常适合,也是交互式查询中非常普适的一种类型。大的聚合计算以及其他类型的查询可能需要依赖于并行数据库或者是MR平台。

查询分发器  Dremel是一个多用户系统,一般情况下会有多个查询同时在被执行。查询分发器基于查询优先级及负载均衡角度进行查询的调度。另外它也提供容错支持,比如当某服务器变得很慢或者某个子表的副本不可访问。

每个查询里处理的数据量经常比执行时可用的处理单元(一般称之为slot)数要多。一个slot对应叶服务器上的一个执行现场。比如,系统中有3000个叶服务器,每个使用8个现场,则总共有24000slot。因此,一个被分为100000个子表的大表需要平均大概每个slot处理5个子表才能处理完。在查询执行过程中,查询分发器计算子表处理时间的趋势图,如果某个子表花了远超比例的长时间处理,则它会被调度到另一个服务器上。某些子表可能需要被多次分发。

叶服务器按列形式读取嵌套数据。每个列条中的块会被异步预取,预读cache一般能有95%的命中率。子表一般采用3副本。当叶服务器不能服务某个子表副本时,会自动切到另一个副本上访问。

查询分发器提供了一个参数用于指定返回结果前最少扫描百分之多少的子表。设置该参数为一个稍小的值(如98%而不是100%)经常可以有效提升执行速度,特别是使用小的副本因子的时候。

如图7描述,每个服务器有一个内部执行树,对应一个物理的查询执行计划,包括数值表达式的计算。大部分数值函数的强类型代码已生成。一个project-select-aggregate查询的执行计划包括一组标有正确repetitionleveldefinition level的迭代子用于扫描输入的列,生成聚合及数值函数计算的结果,旁路掉整个记录的重建。

具体算法如下:

         19描述了Dremel中执行select-project-aggregate查询的算法。算法介绍了一个比较一般的例子,其中查询引用到多个重复域,一个简单的优化版本被用做扁平的关系查询,如这些引用只涉及到必选和可选域。算法有两个外在输入:一组FieldReader,查询中出现的每个域都会有一个,一组查询中出现的scalar表达式,包括aggregate表达式。Scalar表达式的重复级别(行8使用)被定为表达式中使用的所有域的最大重复级别。

         本质上,算法推进reader进入下一组值中,如果selection条件满足,则输出project后的值。Selectionprojection由两个变量控制,fetchLevelselectLevel。在执行过程中,下一个重复级别不比fetchLevel小的reader才能进入。同样,只有当前重复级别不小于selectLevel的表达式才需要计算。算法保证在每次深入嵌套表达式时高层嵌套的表达式(即有小的重复级别)只会被计算1次。

         一些dremel查询,如top-kcount-distinct,采用已知的one-pass算法来完成计算。

7.实验

         本段我们描述dremel的性能,使用Google的一些数据集,同时也检查嵌套数据面向列存储的效率如何。图8给出了我们所使用数据的一些特征。

         未压缩状态,上表的数据大概占了1P的空间(没有副本冗余)。除了有一个表是2副本外,其它表都是3副本,包括10万到80万不同大小的子表。我们通过单机从检查数据的基本访问模式开始,然后说明列存储相比MR执行的优势,最后看dremel的性能。这些实验是在两个数据中心的系统实例上跑的,同时也跑着很多其它应用在做正常的业务操作。除非特殊说明,执行时间都是取5次跑完后的评价时间。

         本地磁盘         在第一次实验里,我们扫描了表T11GB的数据段,大概包括30万行,以此来检查列存储和行存储的性能。采用压缩列存储,这些数据在本地磁盘上大概占用了375MB。采用行存储深度压缩后磁盘空间占用也查不到。实验环境是一个dual-coreintel机器,一块读取速度在70MB/s的磁盘。所有报告中的时间都是冷的,OS缓存在每次扫描前会先被清掉。

         下图显示了5条曲线,说明只针对部分域读取/解压数据的时间,重建、解析记录的时间。曲线a-c描述列存储的结果。曲线上的每个数据点都是通过跑了30次后取平均,每次随机取个数相同个列。曲线a显示了读取和解压缩的时间,曲线b增加了从列重建记录的时间,曲线c显示用C++解析记录所花的时间。曲线d-e说明了行存储的数据访问时间。曲线d显示了读和解压缩的时间,大量时间花在了解压缩上,事实上,读取压缩数据的时间不超过一半。曲线e在读和解压缩时间基础上又增加了50%的时间用于解析。这些开销花在了包括那些不需要访问到的所有域上。

         上述实验可以得到如下结论:当只有少量的列需要读取时,列存储方式有一个数量级的收益。按列存储的嵌套数据其检索时间随域个数线性增长。记录重建和解析的代价较高,每个都可能使执行时间翻倍。我们在其它数据集上看到了同样的趋势。一个问题是最下的曲线和最上的曲线在何处相交,即行存储什么时候比列存储性能要好。在我们的实验中,这个交叉点一般在很多域之后,但不同的数据集不太一样,并且也依赖于是否需要记录重建。

         MR和Dremel        接下来我们比较MRDremel分别在列存储和行存储上的执行。我们考虑只访问一个域的例子,这种条件下性能差异最有说服力。多列的执行时间可以通过图9的结果大致推算出来。

在这个实验中,我们计算表T1中域txtField包含的term个数平均。MR计算过程如下Sawzall程序描述:

         numRecs:                   table sum of int;

         numWords:      table sum of int;

         emitnumRecs <- 1;

         emitnumWords <- CountWords(input.txtField);

 

         变量numRecs保存记录的个数。对每一个记录,numWords会加上input.txtField中的term个数(通过CountWords计算)。平均term频度则可以通过numWords/numRecs计算得到。在SQL中可以用如下语句表示:

         Q1         SELECTSUM(CountWords(txtField))/COUNT(*) FROM T1

 

         10以对数形式显示了两个MR任务和Dremel的执行时间。两个MR任务都运行在3000worker上。同样,一个3000个节点dremel实例用来执行查询Q1Dremel和基于列的MR读了大概0.5T的压缩列数据,而基于行的MR读了87T数据。图中说明,基于列存储的MR比基于行存储的MR有明显优势(从小时级到分钟级)。而使用Dremel则相比有了更明显的提升(从分钟到秒)。

         服务树拓扑    接下来的实验,我们来评估下服务树深度对查询执行时间的影响。我们考虑表T2上的两个GROUP BY查询执行,每个执行会有一次数据的遍历。表T2包括240亿的嵌套记录。每个记录有一个重复的域item包括一个数字amount。本数据集里域item.amount重复了400亿次。第一个查询求每个国家的amount和:

         Q2:SELECTcountry, SUM(item.amount) FROM T2 GROUP BY country

         该查询返回上百条记录,大概从磁盘上读了60G的压缩数据。第二个查询在文本型的域domain上执行一个GROUP BY,并满足一定选择条件。它大概读了180G并产生了110万不同的domain

         Q3:SELECTdomain,SUM(item.amount) FROM T2 WHERE domain CONTAINS ‘.net’ GROUP BY domain

         11显示了每个查询在服务拓扑上的执行时间。在每个拓扑上,叶服务器保证在2900个,这样我们可以假设同样的扫描速度。在二层拓扑上(1:2900),单个根服务器直接和叶服务器通信。在三层拓扑上,我们使用1:100:2900配比,及有100个中间服务器,4层拓扑我们采用1:10:100:2900.

         查询Q23层拓扑上运行了3秒,再加一层则没有太多收效。相反,查询Q3的执行时间则随着加一层服务器折半折半的降。在2层拓扑上,Q3执行时间都超出了图表,因为根服务器需要聚合从上千服务器上返回的大致顺序的结果。这个实验显示了聚合后组数很多的操作利用多层服务树收益较大。

         每个子表的直方图    我们进一步深入研究查询执行过程发生了什么。图12显示了叶服务器处理子表上的Q2Q3查询有多快。这个时间以子表被调度到一个可用的slot上开始,刨去在任务队列中等待的时间。这种衡量方法排除了其它查询同时执行的影响。每个直方图底下的面积是100%。图中可用看出,99%的子表处理Q2(或者Q3)的时间在1秒(或2秒)以下。

记录内的聚合       我们另外做了一个实验分析基于表T3跑查询Q4的性能。这个查询说明了记录内的聚合,它计算所有的记录,满足记录里a.b.c.d的和大于a.b.p.q.r的和。这些域在不同的嵌套层。采用列存储的方式只有13G数据从磁盘读取(实际数据超过70T),查询在15秒内完成。如果没有嵌套的支持,T3上跑此查询将无比昂贵。

         Q4:SELECT COUNT(c1>c2) FROM (SELECT SUM(a.b.c.d) WITHIN RECORD AS c1,

SUM(a.b.p.q.r) WITHIN RECORD AS c2 FROM T3)

扩展性       下面实验说明了系统在万亿级别记录上的扩展性。查询Q5从表T4中选取top-20aid及他们出现的次数。

         Q5:SELECT TOP(aid,20),COUNT(*) FROM T4 WHERE bid={value1} AND cid={value2}

         这个查询扫描了4.2T的压缩数据,使用了4份系统配置,从10004000个节点。执行时间见图13.

每次执行,整个CPU时间几乎相同,大概在30万秒,随着机器数增加用户感知的时间也线性下降。这个结果说明大系统的资源利用率和小系统一样有效,但可以执行的更快。

长尾     最后一个实验显示了长尾的影响。查询Q6在表T5上运行,表T5有上T行。和其它数据集不同,表T5是两副本的,这使得任务重新得到调度的机会较少,任务出现长时间执行的长尾可能性大大增大。

Q6SELECTCOUNT(DISTINCT a) FROM T5

查询Q6读了大概1T的压缩数据,所读这些域的压缩率大概是10。图14可以看到99%的子表处理时间低于5秒。但在2500个节点上跑,仍有一小片子表处理了较长时间,使得查询响应时间从一分钟内延长到几分钟。下一节概述了我们通过实验所发现的结论与教训。

8.评述

         Dremel每月扫描了千万亿条记录。图15显示了一套Dremel集群一个月里实际的查询时间分布。图中可见,大部分查询处理时间低于10秒,还处于可接受的交互范围内。有些查询能达到将近1千亿记录每秒的扫描吞吐在一个共享集群上,在专用集群上可以更大。

         通过实验数据可以看出以下一些结论:

A、 在交互所能接收的时间内完成上T行磁盘上记录数据遍历式的查询。

B、 随列数目和节点数线性扩展是可以达到的,即使在几千台机器的环境里。

C、 MR也可以通过列存储得到提升,就像DBMS一样。

D、 记录的重建与解析代价是非常大的。在查询处理层之上的软件层需要为列存储进行优化。

E、  MR和查询处理可以以互补的方式处理,一层的输出作为另一层的输入。

F、  在一个多用户的环境里,一个更大的系统可以从规模经济中受益,且可以提供更好的用户体验。

G、 如果愿意牺牲一些准确性来换取速度,一个查询可以在看到大部分数据后更早被终止。

H、 大部分网络规模的数据集可以扫描的很快。但对于最后一部分限定一个时间范围是很困难的。

Dremel的代码库还是比较精简的,大概有不到10万行的C++JavaPython代码。

9.相关工作

MapReduce框架[12]被设计通过长时间的批处理任务来解决大规模的计算。同MRDremel也提供了容错能力、一个更灵活的数据模型、以及实时的数据处理能力。MR的成功促进了大量第三方的实现(尤其是开源的hadoop[15]),另外出现了一些结合并行DMBSMR的混合系统实现,提供商如AsterClouderaGreenplumVerticaHadoopDB[3]是一个在混合实现领域的研究性项目。最近的一些文章[13,22]也对比了MR和并行DBMS。我们的工作也强调两种模式的互补性。

Dremel是针对大规模扩展设计的。尽管可以想象并行DBMS也可以扩展到上千个节点,我们还不知道有什么公开的工作或者产业届的报告尝试做这个,也不是我们所熟悉的以前研究MR在列存储上的文献。

我们设计的嵌套数据采用列表示的方法主要思想还是基于几十年前就提的结构、内容、and transposed representation.最近一些关于列存储的工作包括压缩和查询处理,可以在[1]中找到。许多商业的DBMS使用XML支持嵌套数据的存储[19]XML存储schema尝试分离结构和内容,但由于XML数据模型的灵活性,将面临更多的挑战。有一个采用列XML表示的系统较XMill[17]XMill是一个压缩工具。它把所有域的结构存在一起,不适合对列的选择性检索。

Dremel使用的数据模型是复杂值模型和嵌套关系模型的变异[2]Dremel的查询语言基于文献[9]设计,该文献介绍了一种在访问嵌套数据时可避免重建的语言。相反,重建在XQuery和对象型查询语言中是必须的,如使用嵌套的for循环或者构造函数。我们不知道文献[9]的实际实现。最近可实现在嵌套数据上操作的类SQL语言是Pig[18]。其它并行处理系统如Scope[6]DryadLINQ[23]在文献[7]中有更详细的讨论。

10.结论

我们描述了Dremel,一种在大数据集上进行交互分析的分布式系统。Dremel是一个通过简单组件搭建起来的自定义的、可扩展的数据管理解决方案。它是对MR模式的补充。我们讨论了它在上T行记录、数T大小数据集上的性能。另外也描述了Dremel的关键功能,包括存储格式、查询语言和执行过程。后续我们也在计划让Dremel支持更多的功能,如正式的代数规范、join、扩展机制等。

11. ACKNOWLEDGEMENTS

Dremel has benefited greatly from theinput of many engineers and interns at Google, in particular Craig Chambers,Ori Gershoni, Rajeev Byrisetti, Leon Wong, Erik Hendriks, Erika Rice Scherpelz,Charlie Garrett, Idan Avraham, Rajesh Rao, Andy Kreling, Li Yin, MadhusudanHosaagrahara, Dan Belov, Brian Bershad, Lawrence You, Rongrong Zhong, MeelapShah, and Nathan Bales.

 

12. REFERENCES

[1] D. J. Abadi, P. A. Boncz, and S. Harizopoulos.Column-Oriented DatabaseSystems. VLDB, 2(2), 2009.

[2] S. Abiteboul, R. Hull, and V. Vianu. Foundations of  Databases. Addison Wesley, 1995.

[3] A. Abouzeid, K. Bajda-Pawlikowski, D. J. Abadi, A. Rasin,and A.Silberschatz. HadoopDB: An Architectural Hybrid ofMapReduce and DBMSTechnologies for Analytical Workloads. VLDB, 2(1), 2009.

[4] Z. Bar-Yossef, T. S. Jayram, R. Kumar, D. Sivakumar, and L. Trevisan.Counting Distinct Elements in a Data Stream. In RANDOM, pages 1–10, 2002.

[5] L. A. Barroso and U. H¨olzle. The Datacenter as a Computer:AnIntroduction to the Design of arehouse-Scale Machines.Morgan & Claypool Publishers,2009.

[6] R. Chaiken, B. Jenkins, P.-A. Larson, B. Ramsey, D. Shakib,S. Weaver,and J. Zhou. SCOPE: Easy and Efficient Parallel Processing of Massive DataSets. VLDB, 1(2), 2008.

[7] C. Chambers, A. Raniwala, F. Perry, S. Adams, R. Henry,R. Bradshaw,and N. Weizenbaum. FlumeJava: Easy, Efficient Data-Parallel Pipelines. In PLDI, 2010.

[8] F. Chang, J. Dean, S. Ghemawat, W. C. Hsieh, D. A. Wallach, M.Burrows, T. Chandra, A. Fikes, and R. Gruber. Bigtable: A Distributed StorageSystem for Structured Data.In OSDI, 2006.

[9] L. S. Colby. A Recursive Algebra and Query Optimization for NestedRelations. SIGMOD Rec., 18(2), 1989.

[10] G. Czajkowski. Sorting 1PB with MapReduce. Official Google Blog, Nov.2008. At http://googleblog.blogspot.com/2008/11/sorting-1pb-with-mapreduce.html.

[11] J. Dean. Challenges in Building Large-Scale Information RetrievalSystems: Invited Talk. In WSDM, 2009.

[12] J. Dean and S. Ghemawat. MapReduce: Simplified Data Processing onLarge Clusters. In OSDI, 2004.

[13] J. Dean and S. Ghemawat. MapReduce: a Flexible Data Processing Tool. Commun. ACM, 53(1), 2010.

[14] S. Ghemawat, H. Gobioff, and S.-T. Leung. The Google File System. In SOSP, 2003.

[15] Hadoop Apache Project. http://hadoop.apache.org.

[16] Hive. http://wiki.apache.org/hadoop/Hive, 2009.

[17] H. Liefke and D. Suciu. XMill: An Efficient Compressor for XML Data.In SIGMOD, 2000.

[18] C. Olston, B. Reed, U. Srivastava, R. Kumar, and A. Tomkins. PigLatin: a Not-so-Foreign Language for Data

Processing. In SIGMOD, 2008.

[19] P. E. O’Neil, E. J. O’Neil, S. Pal, I. Cseri, G. Schaller, and N.Westbury. ORDPATHs: Insert-Friendly XML Node Labels. In SIGMOD, 2004.

[20] R. Pike, S. Dorward, R. Griesemer, and S. Quinlan.Interpreting theData: Parallel Analysis with Sawzall. Scientific Programming, 13(4), 2005.

[21] Protocol Buffers: Developer Guide. Available at http://code.google.com/apis/protocolbuffers/docs/overview.html.

[22] M. Stonebraker, D. Abadi, D. J. DeWitt, S. Madden, E. Paulson, A.Pavlo, and A. Rasin. MapReduce and Parallel DBMSs: Friends or Foes? Commun. ACM, 53(1), 2010.

[23] Y. Yu, M. Isard, D. Fetterly, M. Budiu, U´ . Erlingsson, P. K. Gunda,and J. Currey. DryadLINQ: A System for General-Purpose DistributedData-Parallel Computing

 

你可能感兴趣的:(google)