Tenzing A SQL Implemention On The MapReduce Framework(译)

作者:Biswapesh Chattopadhyay&Weiran Liu .etc.Google Inc 2011-8

原文:http://www.vldb.org/pvldb/vol4/p1318-chattopadhyay.pdf

译者:phylips@bmy 2011-10-6

译文: http://duanple.blog.163.com/blog/static/70971767201192484915767/


虽然个人喜欢看英文原文, 但是再次推荐一下这个译者的bolg“ http://duanple.blog.163.com/”,他翻译的论文条例清晰,没有生硬的去翻译英文,绝对是真正的理解上的翻译。


摘要

Tenzing是一个建立在MapReduce之上的用于Google数据的ad hoc分析的查询引擎。Tenzing提供了一个具有如下关键特征的完整SQL实现(还具有几个扩展):异构性,高性能,可扩展性,可靠性,元数据感知,低延时,支持列式存储和结构化数据,容易扩展。Tenzing目前已被Google内部的上千个工程师所使用,提供了每天在1.5PB压缩后数据上的10000多个查询支持。在本文中,我们描述了Tenzing的架构和实现,并给出了由一些典型分析型查询任务组成的benchmark结果。


1.导引

MapReduce[9]框架目前已经广泛地流行,无论是在Google内部还是外部。在Google内部,MapReduce出来之后很快就成为用于编写大规模分布式数据处理任务框架。在Google之外,像Apache Hadoop这样的一些项目已经快速流行起来。然而,该框架很难让那些临时性的商业用户去使用,因为它需要用户了解分布式处理和C++或者是Java语言。

 

无论是在Google内部(Sawzall[19],Flume-Java[6])还是外部(PIG[18],HIVE[21],HadoopDB[1]),已经进行了很多创建基于MapReduce的更简单接口的尝试。据我们所知,这些实现具有这样的一些问题:分钟级的延迟,低效率,或者是糟糕的SQL兼容性。低效的部分原因是由于MapReduce没法使用像数据库那样的优化和执行技术[10,12,20]。与此同时,分布式DBMS厂商也已经将MapReduce执行模型集成到他们的引擎中,以提供对日益复杂的分析任务的支持。这样的一些厂商包括AsterData,Greenplum,Paraccel和Vertica。

 

本文中我们描述了Tenzing这样一个建立在MapReduce之上的SQL查询执行引擎。我们已经构建了一个低达10秒级延迟,高效的,具有完全的SQL92特性(还支持某些SQL99扩展)的系统。Tenzing也支持在行式存储,列式存储,Bigtable[7],GFS[14],文本和protocol buffers之上进行高效地数据查询。用户也可以通过SQL扩展比如用户自定义函数和对嵌入式关系型数据的本地化支持来访问底层平台。

 

我们也利用了索引和其他的传统优化技术—同时使用了一些新的技术—来达到可以与商业化并行数据库相媲美的性能。受益于MapReduce,Tenzing可以在廉价,不可靠的硬件之上扩展到数千个核和数PB数据的规模。我们与MapReduce团队紧密协作来实现MapReduce的优化并加以利用。我们针对MapReduce框架所进行的改进会在5.1节进行更详细的讨论。

 

Tenzing已经广泛应用在Google内部,尤其是被一些非工程师团队(销售,财务,市场部门)而使用,已经有上千个用户和每天上万个的分析型查询。Tenzing服务目前运行在两个数据中心,每个都具有2000个的CPU核心,提供在来自不同数据源和数据格式的1.5PB压缩数据上的查询服务。已经多个Tenzing表的行数超过了1000 billion。


2.历史及动机

在2008年末,处理Google Ads数据的数据仓库还是使用一个第三方的数据库实现的,这里我们用DBMS-X代表。虽然它当时工作地很好,但是我们也面临着下面的一些问题:

1.      扩展开销不断攀升:我们的需要是能够扩展到上PB的数据,但是在DBMS-X之上完成这件事,看起来开销之高将是难以接受的。

2.      快速增长中的加载时间:每天导入新的数据需要花费数小时,同时增加新的数据源后开销将会成比例地增加。此外,导入任务也会跟我们的用户查询竞争资源,会导致导入过程中查询性能低下。

3.      数据分析师的创造性受限于SQL以及无法访问多个数据源的限制。越来越多的分析人员不得不为更复杂的分析编写自己的代码,通常是直接针对数据源进行处理(比如使用Sawzall进行日志处理)

 

我们决定通过将我们所有的分析任务移植到Google自己的基础设施上,来对现有的平台进行一个大的重新设计。新的平台需要能够:

1.      能够扩展到数千处理器核,数百个用户以及数PB的数据

2.      运行在不可靠的硬件上,同时还要保持高可靠性

3.      不低于现有平台的性能

4.      能够直接对存储在Google系统上的数据进行处理,以最小化昂贵的ETL过程

5.      为分析人员提供所有的SQL特性以最小化学习代价,同时能够支持更高级的功能,比如复杂的用户自定义函数,预测和挖掘

 

通过在Tenzing上的努力,我们已经取得了很大的成功。经过大概18个月的开发,我们成功地将所有的DBMS-X用户迁移到了新平台中,同时为他们提供了更多的数据,更强力的分析能力,针对大多数场景下的类似性能以及好得多的可扩展性。


3.实现概览

系统有四个主要组件:worker池,查询服务器,客户端接口,和元数据服务器。图1展示了Tenzing的整体架构:

 分布式worker池 作为执行系统,它会选择一个查询执行计划,然后执行MapReduce。为了降低查询延迟,我们不是每次都重新生成新进程,而是让进程一直处于运行状态。这帮助我们显著降低了查询的端到端延迟。该池由master和worker节点组成,再加上一个称为master watcher的守护进程。Worker会对定义在元数据层的所有表的数据进行操作。同时Tenzing是一个异构系统,后台存储可能是多种数据存储的混合,比如ColumnIO[17],Bigtable[7],GFS[14]文件,MySQL数据库等。

 

查询服务器 作为连接客户端和worker池的中间桥梁而存在。查询服务器会解析查询,进行优化,然后将执行计划发送给master执行。Tenzing优化器会采用某些基本规则和基于开销的优化来创建一个最优的执行计划。

 

Tenzing提供了几类客户端接口,包括一个命令行客户端(CLI)和一个Web UI。CLI提供了一些更强力的功能比如复杂脚本,通常由那些高级用户使用。Web UI具有一些易用性特性,比如查询和表的浏览,字符高亮,更适合于新手和中级用户。同时还提供一个可以直接在worker池上执行查询的API,这样单独的二进制程序可以不通过服务端组件,就能够在MapReduce jobs中启动查询。

 

元数据服务器 提供了一个用于存储和获取诸如表名和schema以及指向底层数据的指针的这样一些元数据的API。元数据服务器也会负责存储ACLs和其他的与表相关的安全信息。服务器使用Bigtable作为持久性的后台存储。


3.1查询生命周期

一个典型的Tenzing查询会经历如下步骤:

1.      用户(或者另一个进程)通过Web UI,CLI或者API向查询服务器提交查询

2.      查询服务器将查询解析为一个中间语法树

3.      查询服务器从元数据服务器获取相应的元数据,然后创建一个更完整的中间形式

4.      优化器扫描该中间形式进行各种优化

5.      优化后的执行计划由一个或多个MapReduce组成。对于每个MapReduce,查询服务器通过master watcher找到一个可用的master,将该查询提交给它。在这个阶段,执行过程已经被物理地划分为多个工作单元(i.e. shards)

6.      空闲的workers从masters那里拉取已就绪的任务。Reduce workers将它们的结果写入到一个中间存储区域中

7.      查询服务器监控该存放结果的中间存储区域,当它们到达后进行收集。之后结果会被流式地返回给客户端。


4.SQL特性

Tenzing是一个完整的SQL引擎,它支持所有主要的SQL92属性,但是可能会有某些限制。Tenzing也为了能进行更高级的分析,在核心SQL之上提供了很多增强功能。这些增强功能设计,充分利用了底层的MapReduce框架,可以完全地并行化。


4.1 Projection和Filtering

Tenzing支持所有的标准SQL操作符(算术操作符,IN,LIKE,BETWEEN,CASE等)和函数。此外,执行引擎还嵌入了Sawzall语言,这样就可以使用所有的内建Sawzall函数。用户也可以使用Sawzall编写他们自己的函数并通过Tenzing调用。

 

Tenzing编译器针对filtering进行了一些基本优化,比如:

1.      如果表达式计算结果为一个常量,那么它会在编译期转换为一个常量值

2.      如果断言是一个常量或者常量边界(比如BETWEEN)并且源数据是带索引的(比如Bigtable),编译器会将该条件下推,用来确定底层源的索引的边界扫描。比如,这非常有助于在表上的基于日期边界的扫描

3.      如果断言没有涉及复杂函数(比如Sawzall函数)同时数据源是一个数据库(比如MySQL),fliter可以直接应用到执行在源数据库上的底层查询中。

4.      如果底层存储是基于某列的边界进行划分的,同时断言也是针对该列的某个边界,编译器会跳过那些边界之外的部分。对于基于日期的表来说,这也是很有用的

5.      ColumnIO文件有一些包含了元数据信息的文件头,包括每一列的最小和最大值。执行引擎在处理完头部信息后,如果基于查询中的断言信息确定文件中未包含所需要的数据,会直接忽略该文件

6.      Tenzing会只扫描那些查询执行必需的列,如果底层格式支持的话(比如ColumnIO)


4.2 Aggregation

Tenzing支持所有标准的聚合函数,比如SUM,COUNT,MIN,MAX等,以及DISTINCT修饰符(比如,COUNT DISTINCT)。此外,我们还支持很多统计性的聚合函数,比如CORR,COVER,和STDDEV。基于MapReduce框架的聚合函数的实现,在很多论文包括原始的MapReduce论文[9]中都进行过讨论;但是,Tenzing使用了一些额外的优化。其中的一个例子就是下面讨论的单纯地基于hash表的聚合。


4.2.1 HASH BASED AGGREGATION

基于hash表的聚合在RDBMS中很常见。但是,很难使用基本的MapReduce进行高效地实现,因为reducer总是会对数据根据key进行不必要的排序。我们修改了MapReduce框架来放松这个限制,使得具有相同key的所有value会落到同一个reducer片段内,但不一定是相同的Reduce()调用中。这就使得完全避免在reducer端的排序步骤以及在MapReduce之上实现一个单纯的基于hash表的聚合成为可能。这对于某些特定类型的查询性能会有显著的影响。由于受优化器的限制,用户必须显式指明他们希望采用一个基于hash的聚合。如果没有足够的内存存放该hash表时,使用基于hash的聚合的用户查询会失败。

 

考虑如下查询:

SELECT dept id, COUNT(1)

FROM Employee

/*+ HASH */ GROUP BY 1;

下面是伪代码:

// In the mapper startup, initialize a hash table with dept id

// as key and count as value.

Mapper::Start(){

dept hash = new Hashtable()

}

// For each row, increment the count for the corresponding

// dept id.

Mapper::Map(in) {

dept hash[in.dept id] ++

}

// At the end of the mapping phase, ush the hash table to

// the reducer, without sorting.

Mapper::Finish(){

for (dept id in dept hash) {

OutputToReducerNoSort(

key = dept id, value = dept hash[dept id])

}

}

// Similarly, in the reducer, initialize a hash table

Reducer::Start(){

dept hash = new Hashtable()

}

// Each Reduce call receives a dept id and a count from

// the map output. Increment the corresponding entry in

// the hash table.

Reducer::Reduce(key, value) {

dept hash[key] += value

}

// At the end of the reduce phase, ush out the

// aggregated results.

Reducer::Finish(){

for (dept id in dept hash) {

print dept id, dept hash[dept id]

}

}


4.3 Joins

基于MapReduce的Joins也已经被很多团队研究过了[2,4,22]。因为joins是我们的系统中最重要的部分之一,因此我们对不同类型的joins的实现和优化花费了大量时间。Tenzing支持:在多个数据源上的高效joins操作,比如ColumnIO和Bigtable;内,左,右,cross,和全外连接;以及等值,半等值,非等值和基于函数的连接。cross连接只对那些可以放入内存的表提供支持,右外连接只能通过sort/merge join支持。目前还不支持非等值的关联子查询。

 

我们也引入了对嵌套循环,sort/merge和hash joins的支持。对于sort/merge和hash joins,并行度必须根据优化器限制显式说明。其中某些join技术的实现如下:


4.3.1 BROADCAST JOINS

Tenzing基于开销的优化器可以检测副表(secondary table)能否放入内存。如果查询语句中表的顺序不是最优的,编译器也会使用一些规则和基于开销的启发式方法来调整joins的表的顺序,以让更大一些的表作为主表(driving table)。如果副本够小,会直接将它放入每个mapper/reducer的内存以进行内存中查找,通常情况下这样做都是最快的joining方法。某些情况下,为了节省内存,我们会为更大的表使用一个基于磁盘的有序的序列化实现。Broadcast join可以用来支持所有的join条件(CROSS,EQUI,SEMI-EQUI,NON-EQUI),每个都对应一个具有最优性能的专门化实现。也可以进行一些额外的优化:

l  用于存储lookup数据的数据结构是运行时决定的。比如,如果副表的key是一些具有有限边界的整数,我们就可以使用整数数组。对于具有更宽边界的整数key,我们可以使用一个稀疏的整数map。否则我们会使用一个对应数据类型的hash表

l  我们会在加载时对join数据应用filters以减少内存数据结构的大小,同时也只加载那些查询必需的数据

l  对于多线程的workers来说,我们只在内存中创建join数据的一份拷贝,然后在线程间进行共享

l  一旦副表的数据拷贝到worker进程后,我们会在查询期间一直保持该拷贝,这样就不需要为每个map片段都进行拷贝。当很多的map片段被少数workers运行时,这样做很有意义

l  对于那些静态而且经常使用的表来说,我们会在worker的本地磁盘进行缓存以避免远程读取。只有当表的第一次使用会产生一次读取,后续的读取将会直接使用缓存在本地磁盘的拷贝

l  我们会缓存来自上一条记录的join结果;因为输入数据通常是按照join属性(即join条件中涉及到的那些属性)自然有序的,这可以节省一次查找


4.3.2 REMOTE LOOKUP JOINS

对于那些支持在索引上的remote lookup的数据源,Tenzing提供在该key(或者是key的前缀)上的remote lookup joins。我们使用了一种异步的批量lookup技术以及一个本地的LRU缓存来提高性能。如果需要的话,优化器能够智能地调整表的顺序以使用该方式。{!与BROADCAST JOIN的区别在于,BROADCAST JOIN会将其中相对小的那个表的数据广播给所有Mapper/Reducer。而REMOTE LOOKUP JOINS则是直接去执行查询,不会对数据进行移动}


4.3.3 DISTRIBUTED SORT-MERGE JOINS

分布式的sort-merge joins是MapReduce实现中最广泛使用的join方式。当进行join的两个表大小相等并且都不具有一个在join key上的索引时,这种方式通常是最有效的。


4.3.4 DISTRIBUTED HASH JOINS

分布式hash joins通常是Tenzing中最有效的join方法,当:

l  两个表都无法完全放入内存

l  其中一个表的大小要比另一个大一个数量级

l  都没有在join key上的索引

 

考虑如下查询的执行:

SELECT Dimension.attr, sum(Fact.measure)

FROM Fact

/*+ HASH */ JOIN Dimension USING (dimension key)

/*+ HASH */ GROUP BY 1;

 

Tenzing会按如下方式进行处理:

 

// The optimizer creates the MapReduce operations required

// for the hash join and aggregation.

Optimizer::CreateHashJoinPlan(fact table, dimension table) {

if (dimension table is not hash partitioned on dimension key)

Create MR1: Partition dimension table by dimension key

if (fact table is not hash partitioned on dimension key)

Create MR2: Partition fact table by dimension key

Create MR3: Shard-wise hash join and aggregation

 

// MR1 and MR2 are trivial repartitions and hence the

// pseudo-code is skipped. Pseudo-code for MR3 is below:

 

// At the start of the mapper, load the corresponding

// dimension shard into memory. Also initialize a hash

// table for the aggregation.

Mapper::Start(){

fact shard.Open()

shard id = fact shard.GetCurrentShard()

dimension shard[shard id].Open()

lookup hash = new HashTable()

lookup hash.LoadFrom(dimension shard[shard id])

agg hash = new Hashtable()

}

 

// For each row, increment the count for the corresponding

// dimension key.

Mapper::Map(in) {

dim rec = lookup hash(in.dimension key);

agg hash(dim rec.attr) += in.measure;

}

 

// At the end of the mapping phase, flush the hash table

// to the reducer, without sorting.

Mapper::Finish() f

for (attr in agg hash)

OutputToReducerNoSort(

key = attr, value = agg hash[attr]);

}

}

Reducer代码见4.2.1节的hash聚合伪代码。

 

Tenzing调度器可以智能地进行并行化以让hash join可以运行地更快。比如在该例子中,编译器会将MR3和MR1链接起来,同时将MR2标识为一个join的数据源,这就给后台调度器如下暗示:

l  MR1和MR2可以并行启动

l  MR3在MR2结束和MR1开始产生数据时就可以启动,而不用等待MR1结束

 

我们也为MapReduce框架添加了几个优化来提高分布式hash joins的性能,比如:避免排序,streaming,memory chaining和block shuffle。将在5.1节详细讨论。


4.4 Analytic函数

Tenzing支持所有主要的分析函数,语法类似于PostgreSQL/Oracle。这些函数对于数据分析人员来说是相当熟悉的了。最常用的一些比如:RANK,SUM,MIN,MAX,LEAD,LAG和NTITLE。

 

考虑下面的对每个部门的雇员按照薪水排序的分析函数使用实例:

SELECT

dept, emp, salary,

RANK() OVER (

PARTITION BY dept ORDER BY salary DESC)

AS salary rank

FROM Employee;

 

底层实现可以用如下伪代码说明:

Mapper::Map(in) {

// From the mapper, we output the partitioning key of

// the analytic function as the key, and the ordering

// key and other information as value.

OutputToReducerWithSort(

key = in.dept, value = fin.emp, in.salaryg)

}

 

Reducer::Reduce(key, values) {

// Reducer receives all values with the same partitioning

// key. The list is then sorted on the ordering key for

// the analytic function.

sort(values on value.salary)

// For simple analytic function such as RANK, it is

enough

// to just print out the results once sorted.

for (value in values) {

print key, value.emp, value.salary, i

}

}

 

目前,Tenzing还不支持在同一个查询中在不同的partitioning keys上使用多个分区函数。我们计划通过首先将该查询转化具有同一个partitioning keys的多个查询,然后再合并每个查询的结果来消除这个限制。需要说明的是在同一个查询中同时加入聚合函数和分析函数也是可能的—编译器简单地将它重写为多个查询。


4.5 OLAP扩展

Tenzing支持对于SQL语言的ROOLUP()和CUBE() OLAP扩展。我们的扩展基本上与Oracle的语法一致。这是通过根据所需要的聚合组合在Map()阶段输出额外的记录来实现的。

 

考虑如下的输出每个雇员薪水和计算总和的查询(要求是department-wise的):

SELECT dept, emp, SUM(salary)

FROM Employee

GROUP BY ROLLUP(1, 2);

底层实现可以用如下伪代码说明:

// For each row, emit multiple key value pairs, one

// for each combination of keys being aggregated.

Mapper::Map(in) {

OutputToReducerWithSort(key = fdept, empg, value = salary)

OutputToReducerWithSort(key = fdept, NULLg, value = salary)

OutputToReducerWithSort(key = fNULL, NULLg, value = salary)

}

 

// The reducer will do a standard pre-sorted

// aggregation on the data.

Reducer::Reduce(key, list<value>) {

sum := 0

for (i = 1 to list<value>.size()) {

sum += value[i].salary

}

print key, sum

}


4.6集合操作

Tenzing支持所有的标准SQL集合操作,比如UNION,UNION ALL,ALL,MINUS和MINUS ALL。集合操作主要是在reduce阶段实现的,同时mapper会输出以整个记录为key的记录,以保证类似的key会落到同一个reduce调用中。对于UNION ALL我们进行了特殊处理,我们为它使用了round robin partitioning,同时关闭了reducer端的排序以获得更高的性能。{!UNION ALL是指将所有结果进行不去重的连接。在传统并行数据库中有循环(round robin)划分,散列划分,范围划分这样一些划分技术。其中循环划分具体是这样的:设磁盘数位n,则会将第i条记录分配给第i%n号磁盘。非常适合于顺序扫描这样的负载,因为各磁盘具有几乎相同的元组数目,因此查询负载会很均衡。此处采用这样划分方式,主要也是为了负载均衡。}


4.7嵌套查询和子查询

Tenzing支持在任何可以是表名的地方放入一个SELECT查询,这与SQL标准是一致的。通常,每个嵌套的SQL会被转换成一个独立的MapReduce,同时产生的临时表将会在外部查询中被使用。但是,编译器也能够对某些相对简单的嵌套查询进行优化,比如那些根本不需要创建额外的MapReduce job的查询。比如,如下查询:

SELECT COUNT(*) FROM (SELECT DISTINCT emp id FROM Employee);

将会产生两个MapReduce job,一个用于内部的DISTINCT,另一个用于外部的COUNT。但是,下面的这个查询:

SELECT _ FROM (SELECT DISTINCT emp id FROM Employee);

只会产生一个MapReduce job。如果需要两个或更多的MapReduces,Tenzing会将reducer和后续的那个mapper放到同一个进程中。关于这点5.1节会详细讨论。


4.8结构化数据处理

Tenzing提供对结构化(嵌套的或者重复的)数据格式(比如复杂的protocol buffer结构)的只读支持。当前的实现还有些简单和低效。引擎本身只能处理平坦的关系型数据,而不能像Dremel那样[17]。在同一个查询中,selecting那些来自不同的repetition levels的字段会被认为是一个error。{!关于repetition level的概念需要参考Dremel论文}

message Department {

required int32 dept_id = 1;

required string dept_name = 2;

repeated message Employee {

required int32 emp_id = 3;

required string emp_name = 4;

};

repeated message Location {

required string city_name = 5;

}

};

比如,给定上面的protocol buffer定义,该查询:

SELECT emp_name, dept_name FROM Department;

是非法的,因为它引入了一个repeated level(Employee),但是下面的查询:

SELECT emp_name, city_name FROM Department;

就是合法的,因为Employee和Location是独立的重复性组。


4.9视图

Tenzing支持在数据之上的逻辑视图。Tenzing中的视图主要用于安全性目的:可以让用户访问视图而不授权给他们访问底层表的权限,这样就可以做到行和列级别的安全性控制。

 

考虑一个具有emp_id, ldap_user,name, dept_id, 和salary字段的Employee表。我们可以在它之上创建如下视图:

CREATE VIEW Employee V AS

SELECT emp id, ldapn user, name, dept id

FROM Employee

WHERE dept id IN (

SELECT e.dept id FROM Employee e

WHERE e.ldapn user=USERNAME());

该视图允许用户可以看到其他雇员的部门信息,但是看不到其他雇员的薪水。


4.10 DML

Tenzing提供了基本的DML操作支持:INSERT,UPDATE和DELETE。这种支持是批处理模式的。特别的,Tenzing并不满足ACID,系统满足了原子性,一致性和持久性,但是不支持隔离性。

 

INSERT实现是通过创建新的数据集,然后将新数据添加到现有数据集上。Tenzing允许用户声明主键和外键,但是并不要求用户必须提供。对于UDATE和DELETE的支持,是通过将更新应用到数据集上然后产生一个新的数据集。然后用新数据集的一个引用替换掉元数据中的老的引用。


4.11 DDL

我们提供了大量的DDL操作支持,包括CREATE [OR REPLACE] [EXTERNAL] [TEMPORARY] TABLE, DROP TABLE [IF EXISTS], RENAME TABLE, GENERATE STATISTICS, GRANT 和 REVOKE。它们与标准SQL一致,同时是控制元数据和访问的主要方式。此外,Tenzing具有一个内建的元数据目录机制来简化数据的导入。比如,我们提供了了一些工具和命令来自动确定MySQL数据库的结构,使得它可以从Tenzing进行访问。我们也能够从protocol buffer定义中找到protocol buffers的结构信息,然后将它作为元数据导入Tenzing。这对于Bigtable,ColumnIO,以及使用了protocol buffer格式的RecordIO和SSTable文件都是非常有用的。


4.12 Table Valued Functions

Tenzing的执行引擎支持scalar和table-valued两种用户自定义函数。目前用户可以使用Sawzall进行编写,此外框架设计地能够很容易集成其他语言。目前Lua和R语言的支持已经提出,并且正在进行中。目前Tenzing已经支持使用Sawzall编写以表作为输入和输出的函数。这对于那些进行数据的标准化,以及涉及到多个行的复杂计算很有帮助。


4.13数据格式

Tenzing支持很多数据格式的加载和输出。有很多配置项可以用来描述输入输出的格式。比如,对于包含分隔符的文本格式,用户可以描述分隔符,编码方式,注释,转义字符,信息头等等。下面的语句将会从一个以分隔符分割的文本文件中创建Employee表,同时会验证加载的数据是否满足表的定义和所有限制:

CREATE TABLE

Employee(emp id int32, emp name string)

WITH DATAFILE:CSV[delim="j"]:"employee.txt"

WITH VALIDATION;

 

Tenzing支持的其他格式包括:

l  ColumnIO,一个由Dremel团队开发的列式存储系统

l  Bigtable,一个高度分布式的key-value存储

l  Protocol buffers,RecordIO和SSTables会用它来进行记录存储

l  MySQL数据库

l  嵌入在元数据中的数据(用于测试的,或者是很小的静态数据集)


5.性能

Tenzing的一个关键设计目标就是要达到与传统MPP数据库系统比如Teradata,Netezza和Vertica相媲美的性能。为了实现这个目标,有很多工作需要去做。


5.1 MapReduce改进

Tenzing是与Google的MapReduce实现紧密集成的,目前我们已经对MapReduce框架进行了一些改进来提高吞吐率,降低延迟,来让SQL操作更加高效。

 

Workerpool 我们面对的关键挑战之一就是需要将延迟从分钟级降到秒级。很快我们就明白:为了实现这个目标,必须要实现一个不需要为每个新的Tenzing查询的二进制程序创建新进程的解决方案。MapReduce和Tenzing团队一块合作完成了该pool的实现。一个典型的pool由三个进程组组成:

1.      master watcher。Watcher负责接受一个工作请求,然后将该任务分配给一个空闲的master。Watcher 也会监控pool的整体健康状况,比如空闲资源,正在运行的查询数,等。通常一个pool的实例会对应一个watcher。

2.      master pool。它由相对较少的进程组成(通常是几十个)。Master的任务是协调某个查询的执行。Master会从watcher那里接受任务,然后监控它们的进度。需要注意,一旦一个master收到了一个任务,那么就由它来负责该任务,watcher进程挂掉了也不会影响该任务的执行。

3.      worker pool。它有一系列workers组成(通常有数千个),worker负责进行实际的数据处理。每个worker既可以作为mapper也可以作为一个reducer。每个worker会一直监控某个固定位置以获取新任务,随着新任务到达,通常会以FIFO的模式进行任务的选取。我们计划实现一个基于优先队列的,这样查询就可以根据优先级进行处理。

 

通过使用这种方法,我们将Tenzing查询的执行延迟降低到了大概7秒。但是系统中还有其他的一些瓶颈,比如map splits的计算,元数据更新服务,提交/回滚结果(通常会引入文件的重命名)等。这意味着当前延迟通常都会在10-20秒之间。我们还在进行其他的一些改进,同时我们相信可以将这种端到端的延迟降到5秒,对于分析人员来说这已经是很不错的了。

 

Streaming&In-memory Chaining 最初的Tenzing实现会把所有的中间数据序列化到GFS。对于包含多个MapReduce的查询(比如hash joins和嵌套的子查询),性能会很低。我们通过在MapReduce之间实现了streaming显著地提高了这些查询的性能,比如上游和下游的MRs会通过网络进行通信,GFS只是用来进行备份。之后,我们又通过使用memory chaining进一步提高了性能,我们会将上游MR的reducer和下游MR的mapper放到用一个进程中。

 

Sort Avoidance 某些操作比如hash joins和hash aggregation只需要shuffling,并不需要sorting。我们修改了MapReduce的API使得它可以对这些操作自动关闭sorting。在sorting关闭时,mapper会将数据直接喂给reducer,reducer会直接将数据传给Reduce()函数,从而绕过了中间的sorting步骤。这使得很多SQL操作性能大幅提升。

 

Block Shuffle 通常,MapReduce会在shuffle过程中采用基于行的编解码。为了对数据进行排序所有的行都必须单独处理,因此这通常都是必要的。但是,在不需要进行排序的情况下,性能就会很低。我们在当前MapReduce的基于行的shuffle之上实现了一个基于块的shuffle机制,它会将很多小行合并成1MB大小的压缩的blocks。通过将整个block作为一行及避免reducer端的排序,我们就避免了底层MapReduce框架代码在进行行的序列化和反序列化时产生的开销。与基于行的带排序的shuffle相比,性能提高了3倍。

 

Local Execution我们进行的另一个简单MapReduce优化是本地化执行。后端系统会检测要处理的底层数据的大小。如果大小在某个阈值(通常是128MB)下,查询就不用发送给pool,而是直接在客户端进程中执行。这大概可以将延迟降为2秒。


5.2可扩展性

因为构建于MapReduce框架之上,因此Tenzing天生具有出色的可扩展性。当前产品已经部署在两个数据中心,每个都具有2000多个核。每个核具有6GB内存和24GB本地磁盘,主要用于sort buffers和本地caches(数据主要存储在GFS和Bigtable)。我们使用一个简单查询对系统进行了测试:

SELECT a, SUM(b)

FROM T

WHERE c = k

GROUP BY a

数据以ColumnIO的格式存储在GFS文件上(见表1)。吞吐率(以单worker每秒处理行数进行度量)随着worker数目的上升一直保持平稳。

 
5.3系统benchmarks

为了对比Tenzing和商业并行数据库的性能,我们采用4个最常用的分析型查询进行了基准测试。与DBMS-X(一个主流的行式存储的MPP数据库)进行了对比。Tenzing数据是以ColumnIO格式存储在GFS上的。Tenzing系统使用了1000个进程,每个具有1CPU,2GB内存和8GB本地磁盘。结果如表2所示。

 

Query#3性能差的原因是查询执行时间主要由启动时间所占据。我们相信如果采用了下一节的LLVM引擎,Tenzing的性能表现会更好。


5.4实验性的LLVM查询引擎

为了达到商业DBMS的单节点效率,我们的执行引擎已经历经多次迭代。它的第一个实现是将SQL表达式翻译为Sawzall代码。之后再将代码使用Sawzall的JIT(just-in-time)编译器进行编译。然而,实际情况表明由于需要在Sawzall本地化类型系统之间转换带来的序列化和反序列化开销,导致效率不高。第二个也是当前的实现,使用了Dremel的SQL表达式执行引擎,它直接基于SQL表达式解析树的解析结果。尽管比原始的Sawzall实现要快,但是由于它本质上还是类解释器的,及基于行的处理方式,还是有些慢。

 

在第三次迭代中,我们对两种主要的执行类型进行了进一步的实验:使用基于LLVM{!Low Level Virtual Machine--低级虚拟机的简称,LLVM 是 Illinois 大学发起的一个开源项目,和之前为大家所熟知的JVM 以及 .net Runtime这样的虚拟机不同,这个虚拟系统提供了一套中立的中间代码和编译基础设施,并围绕这些设施提供了一套全新的编译策略(使得优化能够在编译、连接、运行环境执行过程中,以及安装之后以有效的方式进行)和其他一些非常有意思的功能。}本地代码生成的row major block中间数据处理vs. 基于column major vector的列式中间数据处理。Benchmark结果如表3.

 

所有的实验都是在同一个具有4GB内存的双核Intel机器上完成的,输入数据是存放在内存中的列式数据。需要注意的是,LLVM引擎目前还未集成到Tenzing。表的大小是以百万行为单位的,吞吐率是以每秒单worker处理的million行数进行度量的。数据表明,where语句的选中率越高,LLVM引擎的性能越好。我们发现LLVM策略对于实际查询具有更好的整体性能。与生产系统的执行引擎相比,vector引擎的单worker吞吐率要高三倍,LLVM引擎要高6到12倍。

 

我们的基于LLVM的查询引擎会将中间数据按行存储,即使是在输入和输出都是列式的情况下。与列式的vector processing相比,具有如下优缺点:

l  对于基于hash表的操作比如aggregation和join,使用行会更自然:复合型key的比较可以具有缓存局部性,同时使用指针来解决冲突。我们的引擎会在输入行上进行迭代,然后使用生成的代码进行完成这两件事情。同时据我们了解,在使用hash表的情况下,没有一种简单快速的列式解决方案。在hash表中的搜索也会打破列的缓存局部性,产生更多的随机内存访问。

l  Vector processing引擎总是会将结果保存在主存中。与之相比,我们生成的本地代码可以将结果存储在栈上(更好的数据局部性)甚至是寄存器中,取决于JIT编译器进行的优化

l  如果源数据的选取率比较低,vector processing可以将更少的数据加载到内存,因此扫描速度会更快。由于LLVM引擎是按行进行数据存储的,进行扫描就需要读取更多的数据,也就更慢了

l  尽管LLVM还提供了对JITed代码的debug支持,但是对于大多数vector processing引擎使用的本地C/C++过程,存在更多的分析和debug工具

 

在我们的工作负载类型中,大多数的查询都是包含了aggregation和join的。与其他操作符比如selection和projection相比,它们更容易产生性能瓶颈。我们相信本地化的代码生成引擎可以更好地解决这些性能瓶颈。


6.相关工作

最近在大规模分布式数据处理领域已经存在大量相关工作。可以划分为如下几类:

l  分布式数据处理的核心框架 我们一直关注于MapReduce,但是也有几个其他类型的系统比如Nephele/PACT[3]和Dryad[15]。Hadoop[8]是一个MapReduce框架的开源实现。这些强有力而复杂的框架是设计用于让软件工程师实现复杂的并行算法的。

l  构建于这些框架之上的更简单的过程性语言 最流行的当属Sawzall[19]和Pig[18]。它们具有比较有限的内置优化,因此比较适合于让那些更喜欢过程性编程风格的人们进行一些实验性的分析工作,但是需要提高在大规模数据上的快速迭代的能力。

l  语言扩展 建立在核心框架之上,通常具有专门的优化器。目前有建立在MapReduce之上的FlumeJava[6],建立在Dryad之上的DryadLINQ[23]。这些看起来非常适合于那些希望使用相对简单的操作符来快速构建大规模数据处理流程的程序员。

l  声明式语言 建立在核心框架之上具有一些中级或高级优化的查询语言。主要面向于乐于使用SQL的报告和分析人员。Tenzing就属此列,此外还有HIVE[21],SCOPE[5],和HadoopDB[1](最近已经商业化为Hadapt)。

l  将MapReduce和相关概念嵌入到传统并行DBMS。很多厂商已经提供这样的产品,包括Greenplum,AsterData,Paraccel和Vertica。


7.总结

本文中我们描述了Tenzing,一个建立在MapReduce之上的SQL查询执行引擎。可以得出如下结论:

l  基于MapReduce框架构建一个具有完整功能的SQL引擎是可能的,同时还可以提供一些可以让SQL进行更深入的分析的扩展

l  通过对MapReduce进行一些相对小的改进,是可以实现当前商业数据库系统中采用的大量优化的,同时可以创建一个可以与商业MPP DBMS在吞吐率和延迟上相媲美的系统

l  MapReduce框架基于廉价的不可靠硬件,提供了高性能,高可靠性和高度可扩展性。这使得它成为一个构建在大规模数据集上进行复杂操作的分布式应用程序的出色平台

l  通过设计一个可以感知异构数据源的各种特性的引擎和优化器,使得创建一种可以充分利用底层数据源特点的智能系统成为可能


8.致谢

Tenzing is built on top of a large number of existing Google technologies. While it is not possible to list all individuals who have contributed either directly or indirectly towards the implementation, we would like to highlight the contribution of the following:

l  The data warehouse management, specically Hemant Maheshwari, for providing business context, management support and overall guidance.

l  The MapReduce team, specically Jerry Zhao, Marian Dvorsky and Derek Thomson, for working closely with  us in implementing various enhancements to MapReduce.

l  The Dremel team, specically Sergey Melnik and Matt Tolton, for ColumnIO storage and the SQL expression evaluation engine.

l  The Sawzall team, specically Polina Sokolova and Robert Griesemer, for helping us embed the Sawzall language which we use as the primary scripting language for supporting complex userdefined functions.

l  Various other infrastructure teams at Google, including but not limited to the Bigtable, GFS, Machines and SRE teams.

l  Our loyal and long-su ering user base, especially the people in Sales & Finance


参考文献

[1] A. Abouzeid, K. Bajda-Pawlikowski, D. Abadi,A. Silberschatz, and A. Rasin. HadoopDB: an architectural hybrid of MapReduce and DBMS technologies for analytical workloads. Proceedings of the VLDB Endowment, 2:922{933, August 2009.

[2] F.N. Afrati and J.D. Ullman. Optimizing joins in a Map-Reduce environment. In Proceedings of the 13th International Conference on Extending Database Technology, pages 99{110. ACM, 2010.

[3] D. Battr_e, S. Ewen, F. Hueske, O. Kao, V. Markl, and D. Warneke. Nephele/pacts: a programming model and execution framework for web-scale analytical processing. In Proceedings of the 1st ACM symposium on Cloud computing, SoCC '10, pages 119{130, New York, NY, USA, 2010. ACM.

[4] S. Blanas, J.M. Patel, V. Ercegovac, J. Rao, E.J.Shekita, and Y. Tian. A comparison of join algorithms for log processing in MapReduce. In Proceedings of the 2010 international conference on Management of data,pages 975{986. ACM, 2010.

[5] R. Chaiken, B. Jenkins, P. Larson, B. Ramsey,D. Shakib, S. Weaver, and J. Zhou. Scope: easy and e_cient parallel processing of massive data sets. Proc. VLDB Endow., 1:1265{1276, August 2008.

[6] C. Chambers, A. Raniwala, F. Perry, S. Adams, R.R.Henry, R. Bradshaw, and N. Weizenbaum. FlumeJava:easy, e_cient data-parallel pipelines. In Proceedings of the 2010 ACM SIGPLAN conference on Programming language design and implementation, pages 363{375.ACM, 2010.

[7] F. Chang, J. Dean, S. Ghemawat, W.C. Hsieh, D.A.Wallach, M. Burrows, T. Chandra, A. Fikes, and R.E.Gruber. Bigtable: A distributed storage system for structured data. ACM Transactions on Computer Systems (TOCS), 26(2):1{26, 2008.

[8] D. Cutting et al. Apache Hadoop Project.http://hadoop.apache.org/.

[9] J. Dean and S. Ghemawat. MapReduce: Simplied data processing on large clusters. Communications ofthe ACM, 51(1):107{113, 2008.

[10] J. Dean and S. Ghemawat. MapReduce: a fexible data processing tool. Communications of the ACM,53(1):72{77, 2010.

[11] J. Dean, S. Ghemawat, K. Varda, et al. Protocol Buffers: Google's Data Interchange Format.Documentation and open source release at

http://code.google.com/p/protobuf/.

[12] D.J. DeWitt and M. Stonebraker. MapReduce: Amajor step backwards. The Database Column, 1, 2008.

[13] E. Friedman, P. Pawlowski, and J. Cieslewicz.SQL/MapReduce: A practical approach to self-describing, polymorphic, and parallelizable user-defined functions. Proceedings of the VLDB Endowment, 2(2):1402{1413, 2009.

[14] S. Ghemawat, H. Gobio_, and S. Leung. The Google file system. SIGOPS Oper. Syst. Rev., 37:29{43,October 2003.

[15] M. Isard, M. Budiu, Y. Yu, A. Birrell, and D. Fetterly.Dryad: distributed data-parallel programs from sequential building blocks. SIGOPS Oper. Syst. Rev.,

41:59{72, March 2007.

[16] R. Kleckner. LLVM: Debugging JITed Code With GDB. Retrieved June 27, 2011, from http://llvm.org/docs/DebuggingJITedCode.html.

[17] S. Melnik, A. Gubarev, J.J. Long, G. Romer,S. Shivakumar, M. Tolton, and T. Vassilakis. Dremel:Interactive Analysis of Web-Scale Datasets.Proceedings of the VLDB Endowment, 3(1), 2010.

[18] C. Olston, B. Reed, U. Srivastava, R. Kumar, and A. Tomkins. Pig Latin: a not-so-foreign language for data processing. In Proceedings of the 2008 ACM SIGMOD international conference on Management of data, pages 1099{1110. ACM, 2008.

[19] R. Pike, S. Dorward, R. Griesemer, and S. Quinlan.Interpreting the data: Parallel analysis with Sawzall.Scienti_c Programming, 13(4):277{298, 2005.

[20] M. Stonebraker, D. Abadi, D.J. DeWitt, S. Madden,E. Paulson, A. Pavlo, and A. Rasin. MapReduce and parallel DBMSs: friends or foes? Communications of the ACM, 53(1):64{71, 2010.

[21] A. Thusoo, J.S. Sarma, N. Jain, Z. Shao, P. Chakka,S. Anthony, H. Liu, P. Wycko_, and R. Murthy. Hive:a warehousing solution over a Map-Reduce framework.

Proceedings of the VLDB Endowment, 2(2):1626{1629,2009.

[22] H. Yang, A. Dasdan, R.L. Hsiao, and D.S. Parker.Map-Reduce-Merge: simpli_ed relational data processing on large clusters. In Proceedings of the 2007 ACM SIGMOD international conference on Management of data, pages 1029{1040. ACM, 2007.

[23] Y. Yu, M. Isard, D. Fetterly, M. Budiu, _U. Erlingsson,P. K. Gunda, and J. Currey. DryadLINQ: a system for general-purpose distributed data-parallel computing using a high-level language. In Proceedings of the 8th USENIX conference on Operating systems design and implementation, OSDI'08, pages 1{14, Berkeley, CA

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值