[Paper Reading] Presto: SQL on Everything

阅读时间:2021.7

目录

===== 1. Introduction

===== 2. Use Cases

===== 3. Architecture Overview

===== 4. System Design

===== A. SQL Dialect

===== B. Client Interfaces, Parsing, and Planning

===== C. Query Optimization【优化器】

===== Data Layouts(数据分布)

===== Predicate Pushdown(谓词下推)

===== Inter-node Parallelism(节点间并发)

===== Intra-node Parallelism(节点内并发)

===== D. Scheduling

===== E. Query Execution

===== F. Resource Management

===== G. Fault Tolerance

===== 5. Query Processing Optimization

===== A. Working with the JVM

===== B. Code Generation

===== C. File Format Features

===== D. Lazy Data Loading

===== E. Operating on Compressed Data

===== 6. Performance

===== A. Adaptivity

===== B. Flexibility

===== C. Resource Management

===== 8. Related Work

===== 9. Reference


===== 1. Introduction

能快速并且简单的从大量数据中提取分析变得越来越重要。

Presto是一个开源的分布式SQL查询引擎,旨在具有适应性(adaptive)、灵活性(flexible)、可扩展性(extensible)。Presto提供 ANSI SQL接口来从存储中查询数据,暴露HTTP接口,支持JDBC,并兼容很多BI工具。

Presto每天在 Facebook 处理千万亿的行,数百PB的数据。具备很多优秀的特性:

  • 多租户(multi-tenant)能力。可以并发的运行数以百计的内存、IO、CPU敏感型查询,并且可以扩展到1000+的worker节点
  • 联邦查询(federated design。可以在一个query中查询来自不同数据源的数据
  • 极度灵活(flexible)。可以被应用在各种用例场景,不同的限制及性能特性
  • 为高性能而设计(high performance。涉及多个关键特性&优化,包括代码生成、JVM共享等

本文章主要有2个contribution:

  • 主要是为了介绍Presto的设计,以及为了实现上面特性而进行的优化及tradeoff
  • 其次是介绍一些关键性决定&优化带来的性能结果,以及开发和维护Presto的经验教训

===== 2. Use Cases

Presto在 Facebook 的 Use Case 如下:

  • A. Interactive Analytics。Facebook 的 engineer&data scientist 例行的进行小规模数据验证、检验假设等。可能会进行一些试探性查询,只关注第一批返回的数据
    • 期望支持 50~100 并发的查询,并能在秒级 or 分钟级返回结果
  • B. Batch ETL。批量的ETL查询,支持用户从旧的批处理系统迁移到Presto上。从CPU的角度上看,ETL查询构成了Presto的主要负载
    • 资源使用率&集群吞吐比查询延迟更重要
  • C. A/B Testing期望能在小时级别返回完成且精确的结果。也期望能在5-30s内返回小部分数据的查询结果
  • D. Developer/Advertiser Anslytics。一些暴露给客户的报表工具在Presto之上构建,此类查询的数据总量大,但是查询的选择性高(只访问自己的数据),大多数的查询包含join、aggregation、window。
    • 期望延迟在50ms-5s,支持百级别并发,并支持高可用

===== 3. Architecture Overview

【基本架构及组件功能Presto集群由一个 Coordinator & 多个 Worker节点组成。Coordinator节点负责接纳、解析、计划查询以及查询优化;Worker节点负责查询执行

【处理SQL主要流程】STEP1Client 发送HTTP请求包含SQL查询给 Coordinator;STEP2Coordinator 对SQL进行解析、优化,并产生分布式执行计划;STEP3Coordinator 将执行计划分发给 Worker,然后开始执行 tasks ,并开始列举 splits;STEP4Worker 运行这些任务,处理对应的splits( 从外部系统拉取数据或者处理其他Worker生成的中间结果)

这里 task & split 的拆分都是由 Coordinator 来完成的。task是指代执行逻辑,split是待处理数据。即先将逻辑(task)分发,随后给逻辑(task) assign 数据(split)

Worker 使用协作式、多任务处理来同时处理来自多个查询的任务。执行尽可能流水线化,并且任务一旦可用,数据就会在任务之间流动。中间数据&状态都存储在内存中。

【Plugin】Presto被设计为可扩展的,并提供多功能的插件接口。插件可以提供数据类型、函数、访问控制实现、时间消费者、排队策略和配置属性。

【Connector】更重要的,插件还提供了 Connector,Connector使得 Presto可以通过Connector API连接外部数据源,Connector API由4部分组成:Metadata API、Data Location API、Data Source API、Data Sink API

===== 4. System Design

介绍4部分内容:Presto支持的SQL Dialect(A)、查询生命周期的全路径(B、C、D、E)、资源管理机制(F)、异常容忍(G

===== A. SQL Dialect

Presto 严格遵循 ANSI SQL 规范 [2],没有实现规范中的每一个feature,实现的feature都遵循规范声明。

此外,Presto还实现了一些扩展能力,以提升可用性。比如支持 lambda表达式,以及一些内置高级函数

 

===== B. Client Interfaces, Parsing, and Planning

【客户端接口】Presto Coordinator暴露 RESTful HTTP接口,并附带命令行接口。同时还提供 JDBC client以便于BI工具连接

【请求解析使用 ANTLR-based 解析器,将SQL解析成一棵语法树

【逻辑计划】logical planner基于语法树,生成一个 plan nodes组成的中间代表(intermediate representation,IR)树结构。每个节点表示一个物理 或者 逻辑的operation,其子节点是当前节点的输入。该树结构是完全逻辑的,不包括任何的执行逻辑

 

===== C. Query Optimization【优化器

优化器【主要基于RBO完成 将逻辑计划转化为更物理的结构,代表查询的高效执行策略。该过程的工作原理贪婪的评估一系列转换逻辑,直到达到一个固定点。每个 Rule 都有一个模式,可以匹配查询计划的子树并确定是否应用转换。结果是替换匹配目标的逻辑等效子计划。Rule比如:谓词&limit下推(predicate and limit pushdown)、列修剪(column pruning)、去相关(decorrelation)

 

Presto正在通过引入 CBO(cost-based optimization)来增强优化器能力,CBO基于 Cascades framework [13]完成。

目前Presto已经支持了2个 CBO 优化逻辑(将列统计信息考虑在内) —— join策略选择 & join 重排序。

 

这里讨论一小部分优化器特性。

 

===== Data Layouts(数据分布

优化器会利用从 connector的Data Layout API获取的数据物理分布及其他属性(partition、sort、group、index)。

Connector 对于单个 table 会返回多个不同属性的layout,优化器会从中选择最有效的一个来使用 [15][19]

 

===== Predicate Pushdown(谓词下推

优化器和Connector协同决定是否将 range&equality 通过Connector下推以提高 filter 效率

如果有多个layout,引擎会选择在谓词列上有索引的layout

 

===== Inter-node Parallelism(节点间并发

【stage & task优化器会识别 执行计划 是否有某些部分可以并行执行,这些部分被称为 stage。每个stage被拆分为一到多个 task,每个 task 都是针对不同的输入执行相同的计算逻辑

 

 

引擎在stage之间进行数据传递(shuffle)的数据会插入到内存buffershuffle会增加延迟、耗费内存、增加CPU负载,因此Optimizer会关注shuffle在plan中的次数。

  • Data Layout 属性:数据分布情况决定了 optimizer 的策略,以降低数据shuffle的次数
  • Node 属性:除了数据分布外,connector还会暴露一些节点属性(Partition、sort、bucket、group [24],optimizer也会将其考虑进来作为shuffle的依据。
    • Presto 贪婪的选择满足尽可能多的所需属性的分区,以减少 shuffle。当然这也可能会导致分片倾斜(partition skew

 

===== Intra-node Parallelism(节点内并发)

Optimizer 使用相同的机制来识别是否可以进行 节点内并发 并受益节点内并发往往比节点间并发更高效,尤其是对那些对节点有严格并发吞吐控制的场景(增大节点吞吐量)

paper中最后举了2个例子,都是因为数据本身内在原因而导致的单节点大数据量处理的场景,进行 Intra-node Parallelism 可以有效的提高并发。

 

下图4中展示了节点内部并发进行计算(ScanHash & HashBuild)的逻辑。

 

===== D. Scheduling

【Coordinator管理调度task&stageCoordinator 将 Plan stage 拆分成 task,并调度给 Worker 执行,每个 task 可以被理解为一个可执行的单元。随后 Coordinator 将 task 在 stage 之间通过 shuffle 链接并构成一个树状结构

 

【pipeline&operator一个 task 有多个 pipeline 组成,每个 pipeline一系列的 operator 构成operator 对数据执行独立的定义完整的计算操作。pipeline的数据暂存在本地内存中并完成join

上图4展示了一个task内部pipeline&operator的组织关系。

 

为了完成一个query,引擎需要做出2个决策stage如何被调度 & task如何被调度

 

【Stage调度 -- Stage Scheduling

Presto支持2种 Stage 调度策略all-at-once & phased

  • All-at-once最小化 stage 之间的数据调度延迟,在数据准备好时立即执行。适用于对延迟敏感的场景
  • Phased:识别必须同时启动以避免死锁的强连通组件,按照拓扑顺序来执行这些组件

 

【Task调度 -- Task Scheduling

Task调度将 Stage 分为2种情况leaf & intermediateLeaf stage 从 connector 读取数据Intermediate stage 仅处理从其他收到的中间结果

Leaf Stage 的 task 调度给 Worker 的时候会考虑 network&connector 施加的一些约束

  • 比如:share-nothing架构,worker会尽量的和数据调度在一起
  • 分析显示,生产集群的大部分CPU都消耗在从 connector中读取数据(decompress、decode、filter、apply transformation。这种工作负载可以高度并行化,所以如果没有其他约束(比如share-everything架构),worker 节点都会尽可能参与到计算中。
  • 对于网络拓扑的考虑,比如会考虑到机架因素

Intermediate Stage 的 task 可以在所有的worker节点上执行,但是仍然要决定task的数量(基于connector配置、plan属性、数据分布等等)。

部分情况下,引擎会在运行时动态调整 task数量,比如后面 Sector IV-E 中介绍的自适应策略。

 

【Split调度 -- Split Scheduling

Leaf Stage 必须被分配 split 后才可以运行 Intermediate Stage 是一直运行且只有当任务中断或者完成时才会终止

不同的 connector 携带的 split 信息是不同的。比如对于 DFS,split由 file path & offset 组成;对于 Redis ,split由 kv 等信息组成。

 

【Split分配】 task 被调度到 worker节点后,Coordinator 开始为 task 分配 split。Presto 要求 Connector 列举小批量的 split,并 lazy 地将他们分配给 task。这会有一些受益:

  • 【加速查询解耦了大量 split 的枚举时间查询时间
  • 【快速返回query 不需要 处理所有的数据就可以产生结果,并且可以在随后快速的被 cancel 或者 达到 limit 而终止。比如内部分析场景
  • 【适配CPU能力】Worker 维护一个 split 的队列,Coordinator 可以简单的通过 保证队列最短的逻辑 来分配 split就可以适配 CPU 处理能力不同的场景
  • 【降低内存使用】所有的 query 查询都不需要加载所有的 metadata 到内存

 lazy 的split枚举策略,会导致无法精准的预估和汇报查询进度

 

===== E. Query Execution

【本地数据流转 -- Local Data Flow

【Driver Loop一旦 split 被分配到 thread上,就会被 driver loop 来处理。driver loop 比 Volcano模式 更适合多任务协同执行,因为 operator 可以在线程 yield之前快速的进入一个确定的状态。此外,driver 可以最大化在每一次quota期间运转,在无须输入的情况下就可以让数据在operator之间流转

 

【Page】driver loop operator的数据运转单元称为 pagepage 是一个以列编码的多行信息。当 Connector的 Data Source API 收到一个split时返回page,随后 operator 以 page 为单位对数据进行读写&处理。并且 driver loop 继续在 operator 之间流转 page

 

【Shuffle】 -- 利用内存减低延迟,保护内存使用

Presto旨在最大程度减小端到端的延迟,最大化提高资源利用率。Presto通过HTTP协议,使用内存缓冲的shuffle来交换中间数据。Producer保留数据,直到 Consumer拉取完成。该机制相对于将shuffle数据持久化到磁盘的其他产品,提供更低的延迟 [4][21]

 

引擎 基于输入&输出缓冲区利用率来调整并发度

  • 如果 outbut buffer 持续较高时,会降低并发处理split的数量(背压?)。有2个优点1. 网络使用更加平均2. 处理那些赶不上生产速度的客户端
  • 对于 接收端(input),引擎监控每个请求平均数据传输量,以计算HTTP请求的并发度,来保证input buffer 大小

 

【Write

大部分ETL job的场景都需要写入其他表,写入性能的一个关键点就是写入并发度,即通过 Connector Data Sink API 写入数据的线程数。

Presto使用自适应的策略,当产生数据超过buffer threshold时,增加worker数量

 

===== F. Resource Management

【CPU调度 -- CPU Scheduling

Presto主要对集群的整体吞吐优化,此外,本地(节点)还优化了计算成本低的查询的低周转时间

节点统计了每个 split 的 CPU 执行时间,并在本地以task级别进行调度决策

 

【split执行调度】任何一个split在线程上执行最多运行1s,随后必须放弃线程并返回队列中。当outbut buffer满、input buffer空、系统内存使用超限等场景也会进行任务切换调度。

 

【选择下一个待执行任务】Presto使用一个 5级的多级反馈队列 [8],任务积累更多的CPU时间,就回去更高级别。每个级别都会配置自己运行时间的比例。

 

系统提供了一个 low-cost yield signal,所以运行时间长的计算任务可以在operator中停止。如果operator运行超时,那么会收取(charge)对应的时间,并降低其随后的执行概率。这个策略可以给低资源消耗的任务提供更高的优先级。【用户期望小查询快速完成,而并不关系数据量大,计算多的查询的运行时间】

 

【Memory管理 -- Memory Management

内存被定义为2类user内存 & system内存

  • user内存 指对输入、计算推理使用的内存
  • system内存 指协助做决定的副产品(比如 shuffle buffer),可能与查询类型&输入数据无关

引擎对user & total(user + system)施加了不同的限制。query超过全局或者单节点内存限制会被kill。total内存的限制通常比user内存限制高很多,只有少量query可以超出 total 内存

查询在每个节点&全局的user内存限制是不同的,这最大程度地允许使用倾斜

不太可能所有节点同一时间都用到最大内存,所以 overcommit memory是ok的,只要我们保证节点内存不足时,集群状态仍是健康的。这里有2个策略spilling & reserved pool

【Spilling】

当节点内存快要用尽时,引擎会调用memory revocation流程 来释放内存(直到可用内存满足最后一个request运行)。Revocation是将状态 spill 到磁盘,Presto spilling支持join & aggregate。而Facebook线上并未配置 spilling,因为全内存操作 延迟更低

【Reserved Pool

节点内存不足,且没有配置Spill,也没有可回收的内存时Reserved Pool机制将会被使用

每个节点的查询内存被进一步详细分为general pool& reserved pool。当general pool被耗尽时,使用内存最大的query将被提升为reserved pool运行

为了防止deal lock,整个cluster同一时间只能有一个query在reserved pool运行。如果general pool内存耗尽,而reserved pool被占用时,所有任务都会暂停,只保留reserved pool中的任务执行。也可以配置成kill query来取消大多数节点的查询阻塞

 

===== G. Fault Tolerance

Presto支持通过低级别retry的方式使系统从短暂的异常中恢复,然而并没有很好的内部机制使系统可以应对 Coordinator 或者 Worker 的crash。Coordinator 使整个集群不可用,Worker节点异常使运行在该节点的query均失败。目前只能依赖客户端来自动的重试

有很多技术来应对异常,比如checkpoint及部分recover技术、基于复制的异常容忍 [6]等。但鉴于成本考虑,这些技术的预计价值尚不明显。尤其是考虑到 MTTF & 1000节点的集群,其中大多数查询都是在几个小时内完成的场景[17]

===== 5. Query Processing Optimization

===== A. Working with the JVM

Presto 是 Java 实现的,运行在 Hotspot JVM上,性能的优化一定程度局限于平台。没有应用级别的机制来控制 JVM JIT编译器如何生成机器码,可以对代码进行结构化,以便利用 JIT 编译器提供的优化(inlining、loop unrolling、intrinsics等),我们也在探索在 JVM 无法生成最佳机器代码的场景中使用 Graal [22]

Java 的 GC 容易造成突发的影响,Presto 使用 G1 collector [10],该collector处理大对象的能力比较弱。Presto 避免分配大对象和buffer,以适配G1

===== B. Code Generation

引擎的主要特性之一是 代码生成,针对 JVM 字节码,有2种形式

【表达式评估 - Expression Evaluation】

Presto内置一个表达式解释器,可以评估任意的表达式,但是对于评估 billion级别的数据太慢了。为了解决这个问题,Presto生成字节码来解决 constants、function call、refrence to variables、lazy or short-circuiting options

【针对JIT的启发式优化 -- Targeting JIT Optimizer Heuristics

Presto 对几个关键的 operator 和 operator组合 生成 字节码,该字节码比传统的处理流程更适合 JIT 优化。

这里有3个主要的行为,略。

===== C. File Format Features

【Page & Block叶子节点的 Scan operator 会调用 Connector API,并收到以列组织的数据 -- Page。Page 由多个Block组成,Block是一个列组织的内存结构

Connector会尽可能的利用文件格式的特性 [20],比如通过文件统计信息(header或者footer记录的range信息)来跳过文件。Reader可以将某些格式的压缩数据直接转换为 block,引擎也可以有效的对这些数据进行处理

上图展示了一些page的压缩编码场景:Directionary-encoded block 对基数较小的数据比较有效。RLE(run-length encoded block压缩重复数据。同时通过节点内共享字典的方式来提高内存利用率

===== D. Lazy Data Loading

【Lazy materializationPresto支持惰性物化数据(lazy materialization data)。这个功能可以利用像ORC、Parquet、RCFile这样已经以列组织的压缩数据。

Connector只有在真正访问它的时候才会生成block(read、decompress、decode)。大量的CPU耗费在解压和解码上,filter通常具有高度的选择性,所以当列不常被访问时,这个优化非常有效

===== E. Operating on Compressed Data

Presto会尽可能在压缩数据上运行。

  • 当 Page processor评估要对一个dictionary block进行转换或者过滤时,会处理dictionary中的所有数据,这使得引擎在快速无条件循环中处理整个字典。
  • Presto也会利用 dictionary block结构来构建hash table
  • Presto 还会对中间结果生成压缩数据

===== 6. Performance

===== A. Adaptivity

Presto支持 TPC-DS 所有的查询,本次实验中选择一个内存使用量较低的查询子集。

对比三个场景:

  • Raptor:元数据存储在MySQL,实际数据以ORC的方式存储在本地。实验中使用随机的分布策略
  • 数据存储在Hive/HDFS 中,没有使用 统计信息(statistics)
  • 数据存储在Hive/HDFS 中,使用 统计信息(statistics)

实验证明Presto可以很好的利用存储特性。过程略

===== B. Flexibility

这里截取了生产环境的query统计,证明presto可以适应低延迟(ms级别)的查询 到 ETL 运行好几个小时的job

===== C. Resource Management

这里采集了一些生产环境数据,证明查询并发度不断变化的过程中,CPU一直可以保持大约90%的利用率

===== 8. Related Work

和其他支持SQL产品对比:

  • Hive [21] 在 HDFS存储之上提供 SQL 接口,并将 query 编译成 MapReduce [9] 或者 Tez [18] job 来运行
  • Spark SQL [4] 构建在 Spark引擎 [23] 之上,解决了很多 MapReduce 系统上的问题。

然而这些系统不支持端到端的 pipeline,也通常会将中间数据持久化到磁盘。这尽管提升了异常容忍的能力,但是增加了延迟,不适用于交互式或低延迟的用户场景

  • Vertica [15] ,Teradata,Redshift等系统可以从外部系统读取数据。但是都是围绕内部系统构建的
  • Apache Impala [14] 提供交互式响应需求,但是需要在 Hadoop 系统上运行

Presto 可以构建在未知的数据源之上,可以和其他的垂直集成存储(比如Raptor)结合使用。更加开放

Presto构架在很多系统的革新技术之上:

  • 编译 query plan 来加速query 执行,借鉴了Neumann [16] & Diaconu et al. [12]
  • 尽可能的压缩数据,借鉴了 Abadi et al. [3],并生成压缩的中间结果
  • 从 Vertica & C-Store [19] 中选择最佳布局?
  • 使用类似 Zhou et al. [24] 的策略,结合计划属性来最小化 shuffle

===== 9. Reference

Theory

  • ANSI SQL  [2]

Algorithm / Data struct

  • G1 collector [10]

Research

  • Integrating Compression and Execution in Column-Oriented Database Systems. [3] #563#
  • Efficient Processing of Data Warehousing Queries in a Split Execution Environment [5] #85#
  • Fault-tolerance in the borealis distributed stream processing system. [6] #212#
  • An Experimental Time-Sharing System. [8] #244#
  • Cascades framework [13]
  • Efficiently Compiling Efficient Query Plans for Modern Hardware. [16] #427#
  • Incorporating partitioning and parallel plans into the scope optimizer. [24] #86#

Product

  • Volcano [1]、MapReduce [9]Apache Impala [14]、Vertica [15]、Tez [18]、C-Store [19]、Hive [21]、Graal [22]
  • Tenzing A SQL Implementation On The MapReduce Framework [7].
  • Microsoft SQL Server Polybase [11]
    • Hekaton: SQL Server’s Memory-Optimized OLTP Engine. [12]
  • Themis: An I/O-Efficient MapReduce [17]
  • Even faster: Data at the speed of Presto ORC [20]
  • Spark [23]
    • Spark SQL [4]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值