Dremel是一个可扩展的、交互式即时查询系统,用于分析只读的嵌套数据。Dremel能够对集群上的超大数据集进行交互式分析。Pig、Hive利用MapReduce执行查询,须要在多个MR做业间传递数据,相比之下,Dremel是就地执行查询的(MapReduce的瓶颈颇有可能就是在Map和Reduce之间传递数据)。Dremel并非用来取代MapReduce的,它能够和MapReduce互相补充,好比用于分析MapReduce的输出。算法
实现Dremel有2个问题:首先是通用的存储层,好比GFS,一个高性能的存储层对于就地查询是很是关键的;其次是存储格式,按列存储对于扁平的关系数据很是合适,运用到嵌套数据更加困难,必需要保留结构信息,而且可以按任意域的子集重构记录。编程
本文的主要内容就是如何按列存储嵌套数据模型。数据结构
编程语言使用的数据结构,分布式系统交换的信息,结构化文档,等等,能够很天然地用嵌套结构表示。在Google,嵌套数据模型是大多数结构化数据处理的基础。Dremel使用的嵌套数据模型可用下面语句表示:编程语言
τ是一个原子类型或者一个记录类型。Dremel使用的原子类型包括整形、浮点数、字符串等;记录包括一个或多个域,每一个域能够是原子类型或其它记录类型。Ai是记录的第i个域,每一个域能够选择一个标签,标签*(repeated)表示重复域(可出现0或屡次),标签?(optional)表示可选域(0或1次),无标签(required)则表示必要域(只出现一次)。分布式
上图是论文里给出的例子,定义了一个记录类型Document表示网页,以及该记录的两个实例。域DocId是必要域;Links是可选域,Links内能够包含一系列的Backward和Forward,指向其它网页的DocId;每一个文档能够有多个Name,表示能够用不一样的URL访问该网页;每一个Name能够有多个Language(Code-Country对),以及可选域Url。r1和r2是知足该格式的两个例子。 域的完整路径使用’.’链接,好比Name.Language.Country。工具
整个数据模型能够当作是一棵树,只有叶子结点才有值。性能
按列存储有不少好处,好比更好压缩率,减小查询读取的数据量。按列存储关系数据很简单,可是按列存储嵌套数据很复杂,由于必须保存数据的结构信息。本小节解决记录的按列无损存储,快速编码,高效记录聚合等挑战。大数据
数据自己不能告诉咱们记录的结构。以Document的Name.Language.Code为例,按列存储后,连续两个Code的值,咱们并不知道它们是属于同一个Name下的两个Language,仍是不一样的两个Name,以下图三个Code的关系。所以为了保存记录的结构信息,Dremel引入了Repetition Level和Definition Level概念。ui
仍然以Code为例,它在r1中出现了3次。’en-us’和’en’位于第一个Name,’en-gb’位于第三个Name。换句话说,’en’是在Language发生重复,’en-gb’是在Name发生重复。为了区分这些状况,Dremel为每一个值分配一个重复级别。重复级别表明值是在哪一级发生重复。Name.Language.Code路径上有两个重复域,所以Code的重复级别能够是0、一、2,0表示开始新记录(或者理解为在记录一级重复)。根据定义,r1的三个Code的重复级别分别是0、二、1。注意r1的第二个Name里没有Code,为了区分’en-gb’是第二个Name仍是第三个Name,必须’en’和’en-gb’之间插入NULL。编码
在Code的例子里,由于Code是Language的必要域,若是Code读取了一个NULL,咱们就能够肯定这个位置其实是一个没有Language的Name。可是通常状况下,还须要额外信息。以Country为例,Country是Language的可选域,若是Country读出一个NULL,那么这个位置究竟是缺了Language的Name,仍是缺了Country的Language呢?
定义级别用于解决这个问题。定义级别表示这个值的完整路径上有几个域是能够未定义(可选域或重复域)但却已定义。对于非空的值,它的定义级别就等于完整路径上可选域和重复域的数量;对于NULL,得看它出现的位置上实际定义了几个可选域和重复域。以Country为例,Name.Language.Country的可选域或重复域有三个,因此对于非空值,它的定义域必定是3;而对于NULL,则要看缺席的缘由,好比r1的第一个Name的第二个Language没有Country,定义级别为2,而第二个Name没有Country,定义级别为1。NULL告诉咱们此处本能够有一个值,定义级别告诉咱们缺席的缘由。
r1和r2全部域的重复级别和定义级别以下图所示。论文的附录A提供了快速计算重复级别和定义级别的算法。
列的存储都以block为单位,值通过压缩后,和对应的重复级别和定义级别一块儿被编码到block。好的编码方式能够尽量去掉没必要要的数据。
性质一:特定的一列里,非空值的定义级别都是相同的,而空值的定义级别确定小于非空值的定义级别。
性质二:重复级别老是小于定义级别。
利用性质一,咱们能够不存储非空值的定义级别和NULL值。根据性质二,定义级别为0意味着重复级别也是0,所以DocId的两个级别都不用存储。
前面讨论的是如何将记录按列存储,这一小节是如何将列重组为记录,这对面向记录的处理工具(好比MapReduce)是很是关键的。给定域的一个子集,Dremel将只重组该记录被选定的域。Dremel的思想是建立一个有限状态机(FSM),负责读取每一个域的值和级别。FSM的每一个状态对应一个域,状态的跳转由重复级别决定:每读取了一个值,Dremel查看下一个重复级别决定跳转到哪一个状态。
上图显示了重组完整记录的有限状态机。开始的状态是DocId,每当读取一个DocId,FSM跳转到Links.Backward;根据重复级别的定义,1表示下一个Backward值属于同一个Links,所以重复读取,遇到0则表示Backward读完了,跳转到Links.Forward;Forward和Backward相似;跳转到Code后,状况更加复杂,由于Country和Code是兄弟,因此不论Code的重复级别为什么值,都跳转到Country;当下一个Country的重复级别是2时,代表当前Language级别发生重复,跳转到Code;不然Language级别无重复,跳转到Name.Url;下一个Url的重复级别是1代表Name发生重复,跳转到Code;不然Name结束,记录读取完毕。完整的算法在论文附录B。
FSM的构造算法在论文附录C,基本的思路比较简单。假如现处于状态f,即读取域f的值,若是下一个重复级别是l,则回溯到级别为l的祖先结点,而且选择该祖先结点下的下一个(论文里说的是first leaf,我的认为有误)叶子结点n为跳转状态。所以咱们获得了一个跳转(f, l)->n。以Name.Language.Country为例,假如读取的下一个重复级别为1,由于country的祖先中级别为1的是Name,而Name在Language后的下一个叶子结点是Url,所以获得跳转(Name.Language.Country, 1)->Name.Url(而按照原文first leaf的解释,跳转的目标应该是Name.Language.Code,显然是错误的)。
原文: Dremel: Interactive Analysis of Web-Scale Datasets. In VLDB'2010.