导读 尽管数据计算引擎在初始阶段可能表现各异,但它们基本上都由一系列相仿的逻辑组件组成:前端语言、中间表示(IR)、优化器、执行运行环境及执行引擎。Velox 专注在执行引擎层,提供诸如类型系统,列式内存数据格式,函数计算,表达式求值、算子计算,Pipeline 执行引擎,内存管理等组件。因此,Velox 期望一个最终的物理执行计划作为输入,并利用本地主机中现有的可用资源高效地执行它。
本次分享题目为 Velox Memory Management,主要内容包括以下几大部分:1. Velox 介绍
2. Velox 内存管理
3. Spilling 技术
4. 未来规划
5. Q&A
6. 参考资料
分享嘉宾|段蒙 腾讯 高级工程师
编辑整理|张进东
内容校对|李瑶
出品社区|DataFun
Velox 介绍
1. Velox 的愿景
如图所示,我们的数据系统包括多种类型,例如事务型、分析型、流式以及时序型(例如监控)。这些类型均有各自的实现,由不同团队负责开发。Velox 的愿景是在执行引擎层面进行整合,以降低工程碎片化及重复劳动。同时,为用户提供一致的体验,因为本质上我们都在进行表达式求值、算子计算等操作,不同团队的实现可能导致用户体验的差异,因此执行引擎的整合将极大提升用户体验。此外,腾讯大数据的批处理采用 Spark,流式处理采用 Flink,交互式处理采用 Doris 和 Presto,如果能对这些引擎进行改造,将有助于提升性能,并通过基础设施的整合实现降本增效。愿景宏大,最终仍需落地。目前看来,降本增效是最直接的收益。2. Velox 是什么
Velox 是 Meta 公司的开源项目之一,是采用 C++ 开发的一种高性能的查询执行引擎库[1]。该引擎采用向量化处理,同时采用推送式(push
base)管道模型(pipeline model)。它与 Databricks 的 Photon,即闭源版本的引擎[2]在概念上相似,但也存在差异。Photon 采用拉取式(pull base)火山模型(Volcano model),实现简便。而采用推导式的 Velox 则在多线程执行方面有着更出色的表现。此外,Velox 专注于单机本地执行的性能优化。它并未包含任何控制面的元素,例如 optimizer 或 parser 等,因此主要致力于向量化和自适应优化。然而,值得注意的是,尽管它是开源的,并且具有通用 API,但其 API 的扩展性和通用性都非常优秀。这也是为何我们在之前提到,希望将腾讯大数据作为我们的查询执行引擎库的原因。
Velox 涵盖完备的数据类型体系,支持各种标量和复杂类型,如广告场景中极其常见的复杂类型的数据,即大量的多层次嵌套结构数据。其处理的数据采用列式内存布局,即 Vector,可以支持各种编码方式,除常见的平面(flat)编码外,还支持字典(dictionary)和常量(constant)编码,以及延迟物化(lazy materialization)和乱序写入等。其表达式计算框架是一个完全的向量化框架,除了常见的子表达式消除(subexpression elimination)、常量折叠(constant
folding)、空值传播(null propagation)等优化措施外,还结合了向量的字典编码和常量编码进行了剥离(peeling)优化。例如,对于处理国家名称这类字符串的情况,尽管可能涉及到 10 万行数据,但实际上字典中的常量仅为几十或几百个,因此计算量可以从 100K 降至 0.1K,使性能大幅提升。此外,它还能进行自适应性优化,由于每次处理的是一批数据,因此可以统计过滤器的效果。在执行多个过滤器的联合操作时,可以将过滤效果最佳的过滤器提前执行,并且其算子和函数也都实现了向量化。除了向量化带来的优势外,它还具备一些适应性优化,例如函数的计算可以动态地将其转移到快速执行路径上,这是一种常见的字符串处理方式。例如,如果在运行过程中发现是 ASCII 编码,那么就无需传递之前的 UTF-8 处理,而是使用 ASCII 编码的处理方法,从而显著提高性能。此外,其算子的实现不仅是向量化的,而且常量折叠和比较友好。同时,它还非常好地支持 Spilling,这对于批量处理场景具有重要意义。资源管理方面,包括内存管理、Spilling 和 Pipeline 模型的多线程执行框架。本次分享的重点主要集中在内存和 Spilling 这两个方面。3. Velox 执行
在 Presto 社区内颇受好评的一篇博客[3],深入浅出地介绍了 Velox 的执行过程,因此笔者从中引用下图。
如图,Velox 任务在创建之时,若具有计划片段,则此计划片段实质上为一个计划节点树。该计划节点树将依据其构建多个管道。每条管道(pipeline)的逻辑为算子的串行操作,例如,从表扫描至投影,再至哈希连接(HashJoin),直至最终输出,这就是物理上的一条管道。值得注意的是,由于其为推式基础管道,故能便捷地进行多线程执行。因此,每条管道可能会由多个线程执行,物理上则由驱动器(driver)构成的多个算子对象作为线程执行实体,在线程池中进行调度执行。其优势在于,可以进行精细的调度,对于 IO 较为密集的算子,可以让出线程。如此便能实现更公平的分配,不会因某个耗时的操作,比如按 batch 读取和处理 spill 数据的循环操作,一直占用 CPU,而每个算子本身仅处理列式数据,即列式输入,列式输出(Columnar in Columnar out)。这种方式是将算子的运算应用于列式数据之上,这样的密集循环对于当今的 CPU 架构来说是极其友好的,能够有效地触发自动循环展开和向量化指令的执行。当然,对于哈希表以及过滤等关键性能环节,在 Velox 内部都以显式方式进行循环展开、预取操作以及 SIMD intrinsic 改写。此外,在每个算子执行的过程中,都会有一个内存池用于追踪其内存使用情况。因为在执行过程中,可能存在一些容器如行、哈希表等,其中包含了内存状态数据。因此,在执行过程中,可能需要进行内存的申请与释放,这些操作均由内存管理模块负责管理。Velox 内存管理
1. 内存管理器及其设计原则
Velox 的内存管理功能源自 Presto,主要设计理念为:在有限的内存资源内,以公平公正的方式支持各种类型的查询。例如,我们可以在一个 Presto 集群内运行秒级别、分钟级别乃至小时级别的查询,但需确保内存资源的合理分配与共享。
它的主要优势体现在以下几个方面:首先,在内存分配策略上,Velox 针对小内存块做了优化,采用了 SizeClass[4]、Quantized 等技术;其次,在内存共享方面,Velox 内置了精细化的内存使用追踪功能,通过加速、仲裁等手段,实现内存资源的公平分配与仲裁。Presto 的每个 worker 都是一个长运行的进程,为了保证总体容量不会因 worker 进程占用过大导致 OOM 而被 kill,Velox 实施了严格的总体容量控制措施。
内存管理器主要由三个部分组成:首先是内存池(Memory Pool),主要用于精细地跟踪内存使用情况;其次是仲裁器(Arbitrator),负责协调各个内存单元之间的共享与仲裁;最后是分配器(Allocator),负责实际的物理内存分配工作。此设计基于严格的限制,正如先前所述,其总的物理内存占用不应持续保持在极高的水平上,以防止出现 OOM 的问题。因此,每一个查询都会有与其相关联的内存池,该内存池用于记录 RowContainers、HashTable 等运行过程中的中间数据。在每个查询的执行过程中,可能会通过内存池申请内存,这可能导致其他内存池的容量不足,从而触发其他查询的内存拉取操作进行溢出处理,即通过仲裁器将内存划分为不同容量,并分配给相应的内存池。2. 内存池
内存池采用了典型的查询结构,即从根节点开始即是一个查询任务,可能会包含多个子任务。每个子任务都具有计划节点,该计划节点将创建管道。根据并行度的要求,管道将由特定的驱动器来执行。整体从上至下均为树状结构,与之相对应的则是内存池的组织。例如,根池对应着一个查询任务,中间的任务节点分别对应一个聚合池,而底层真正执行运算的则是叶子池。
内存池可分为两大类:负责实际内存使用和分配的叶子池(Leaf Pool),以及负责聚合操作的聚合池(Aggregate Pool)(根池 Root Pool 也可以看作是一种聚合池)。内存用量从底部开始逐步聚合,向上传递,最后在根池完成最终的聚合。根池还具备一些特别的功能,如控制容量,因为实际申请内存时都会从根池发起。通过这种方式,我们可以实现对每个查询任务的内存实际使用量的精细追踪,精确到运算符级别。这样,对于全局的内存使用和控制便能达到最佳效果。
由于申请内存时需要向上层传递该内存的使用情况,这可能会引起他人进行加速操作,因此这一过程确实存在一定的消耗。借助于量化机制,可以有效减少申请次数。例如,若申请的内存少于 16M,则每次申请将向上取整至1M,而对于超过 64M 的,则向上取整至 8 M。在此基础上,每个内存池实际上是一种基于容量的管理机制。它们并非直接参与物理内存的管理,而是作为内存跟踪器(tracker),在每个查询根池中,都有一个容量(capacity)的概念,即每个池的最大容量是多少,当前已申请的容量是多少,以及当前所有的叶子节点在根池中获取的保留容量是多少。例如,某个 leaf pool 需要申请新的内存,它将从绿色部分的容量中获取。若此容量不足,则需增长其容量以向右移,此时便可能触发其他查询或自身查询中的其他 leaf pool 进行 spill 操作,故需要一个仲裁技术,即内存仲裁器。3. 内存仲裁器
内存仲裁器(arbitrator)的主要职责是管理所有可用容量,并在查询间进行仲裁,决定将哪个查询,或查询中的哪个算子闲置的容量归还于该查询或算子,又或是通过spill技术,将操作符中使用的容量,使用的状态数据进行溢出处理后,将溢出的容量还回给查询,因此,为了避免此仲裁过程,每种查询在创建时将获得一个初始容量。此初始容量即刚才提及的绿色部分,在过程中,它可从每个查询的内存池,仲裁器获取增长的容量。具体实现为,若查询自行回收,则将从该查询中某些物理算子,即那些 leaf pool 算子的 leaf pool 中,回收其中的闲置容量,或者触发 spill 操作,或者进行全局仲裁。也就是说,可以从查询 b,查询 c 中回收闲置容量和进行 spill 操作,以拉回容量。
仲裁过程与树状结构紧密相扣,即先将请求池设置为一定状态以确保其串行性,避免因某些极端情况导致风险条件降低。之后,仲裁器在获取所有可用候选池时,由于所有根池均已注册至内存管理器中,因此仲裁器可获取此信息。仲裁器观察自身情况,如要求的容量加上已存在的容量是否超过最大限制,若超过则应先行收缩自己。接着,仲裁器进行局部调整,即查看候选池中是否有空闲资源,能否回收先前提到的绿色部分,若所有容量均已回收且内存仍不足,则需继续进行全局调整,即回收其他查询占用的资源以及通过磁盘溢出方式实现回收。若最终仍无法获取充足容量,则仲裁器将放弃一个查询,优先处理占用内存最大的查询。若仲裁成功,仲裁器将增加申请者的根池容量,使得绿色部分向右扩展,继而使整个池脱离当前仲裁状态。
这一流程需采用回收机制,因此每一 memory pool 均有对应的回收器。回收器在各类 pool 中的功能并不完全相同,例如,Query pool 内的回收器为树形结构,因此可进行自顶向下的回收操作,即回收所有的 task。然而,由于回收工作较为繁杂,该过程需进行同步操作,将所有的 task 暂停。这是由于其推式管道机制(push base pipeline)的应用,使得驱动能够脱离线程,独立运行任务。当所有驱动脱离线程(Driver Off The Thread)后,任务便会暂停执行,此时便可安全地进行回收。同时,Task Pool 会负责回收对应的 Node Pool,因为 Node Pool 本身便是一个聚合池,因此直接便会引发实际的操作者进行磁盘写入,完成后会将被回收的查询的容量减记回给仲裁器,待整个回收操作结束后,将任务恢复,从而确保整个查询任务的执行得以顺利恢复。
一个端对端的流程如上图所示,假设我们有一项任务,涉及到两个查询,分别是 query A 和 query B,它们各自都有其特定的搜索树。而对于查询 A 而言,假如它的 operator 1 需要分配内存,那么它会向上传递其使用量。然而,如果在根结点发现容量不足,无法满足请求,将会通知内存管理器去扩大现有容量。内存管理器会找到仲裁者,仲裁者会寻找合适的存储池,例如 Query Pool B。接下来,Query Pool B 会按照前面所述的方式,从上往下逐步进行回收。这时,在 Query Pool B 中,会暂停这个查询的所有任务,并在节点池中找到其所有操作符,然后将所有操作符进行磁盘溢出处理(disk spilling)。4. 内存分配器
前面讲解了内存池以及仲裁者的相关知识。接下来,我们要讨论的是内存管理中的另一个关键部分——分配器(allocator)。
分配器对于大规模内存的分配,例如 HashTable,其主要优点在于其对小型内存块的出色管理。这里我们借鉴了 Umbra[4]这篇论文中的大小类(SizeClass)的概念,即在常规思维下,通过固定缓冲区(fix- size buffer)方式,可以最大程度地防止碎片化。然而,管理过程将变得异常复杂,因为可能需要在大型内存块中不断切割出各种小型内存块,并将它们通过字典(Dictionary)进行管理。编写这种 Arena 代码的复杂性极高,因此有人提出了使用可变长度的缓冲池,例如,我们可以将内存分为几类,每类具有不同的大小,如 4K、8K 等。然而,这种内存池管理方式存在一个问题,即由于需要为每个大小预先分配空间,因此无法实现提前规划,这将导致碎片化问题。而 SizeClass 通过虚拟地址与物理地址的映射,即 mmap 的方式,既解决了管理困难的问题,又解决了碎片化的问题。具体做法是,对于每个大小,都有一个内存池,但它是懒惰的(lazy)。假设为每个大小分配的容量都是整个的,例如,整体内存容量为 100G,我们可以假设每个 SizeClass 都可以分配 100G 的 buffer,并使用匿名 mmap 来做虚拟内存和物理内存的映射。此时,物理内存中没有任何占用。这个既减少了不同 size 的 buffer 管理的负担,还可以根据不同的大小进行高效的分配,从而节省实际空间。以上图的分配公式为例,当需要 150 个 page 的时,实际分配了 152 个 page,只多申请了两个 2 个 page。在使用过程中是通过位图(bitmap)来追踪 page 是否被实际实用,因为它最终的物理内存占用不能超过之前提到的 100G。尽管每个 SizeClass 的 buffer 都假设自己有 100G,但实际的物理内存是不能超过的。因此,当使用完毕后,需要将其释放。这个释放的过程是懒惰的,也就是说,会将这个位设置标志位,表示它不再需要使用,但它的映射关系仍然存在。当发现内存在分配时,需要清理一些,会通过 madvice 通知操作系统,应该将这块物理内存驱逐(Evict)掉。Spilling 技术
在进行仲裁计算时,需要进行算子的溢出操作(spilling),下面详细解析这种 spill 的机制。即,在每个算子执行过程中,其内存中会积累若干数据。其中最为常见者便是 RowContainer,如 HashTable 这类算子,其分割后(pivot)数据便存储于 RowContainer 之中;而在进行聚合操作时,accumulator 以及 group keys 等数据也均以 RowContainer 的形式呈现,原因在于无论在何种情况下,数据都将以 RowContainer 的形式进行组织。1. Spilling 框架
在此,我们需要强调的一点是,此处为何使用 ROW?实际上,无论是 Databricks 的 Photon[5] 还是 Meta 的 Velox[6],它们的 HashTable 都是采用了向量化的计算方式,但其有效负载都需要转换为 ROW,因为在进行 JOIN 操作、行的拼接以及更新计数器等操作时,如果使用 Columnar 的存储方式,将会导致大量的随机内存访问,因此,Photon 和 Velox 都选择将其转换为 ROW,具体内容可参考相关的 DATABASE 论文[4,5]。因此,这些数据实际上是算子的中间状态数据,因为在进行聚合操作时,累加器或计数器等实际上都是一种状态数据。因此,它们会通过 API 进行溢出,例如在某个特定的阶段,然后在恢复阶段再次读取并恢复内存状态。如图所示,实际上是按照分区(partition)进行排序并写入。在某些情况下,可能并不需要排序,因为不需要维护有序的状态。此外,还有一些算子并不需要进行分区,以下是两个较为常见的算子的详细介绍。2. HashAggregation 算子
首先关注的是 HashAggregation,这是 Velox 最为关键的算子之一,由于其规模庞大,我们在线上进行测试发现,如果有大规模的汇总操作,Velox 的性能将得到大幅提升,因其底层的 HashTable 具有极佳的效率,且支持在其之上进行密集型运算。因此,使用该算子进行查询,并通过向量来实现汇总是该库的有效策略。例如,在进行汇总时,需要保证顺序性,因为输入阶段每个分组依据列 a、b 和 c 进行,每组都会索引出一个累加器(accumulator),例如平均值,最大值或 arrAgg 等。由于可能会在执行过程中触发溢出,因此在溢出时需保证顺序性,直至该算子执行完毕。当前的实践表明,如果算子发生过溢出,则在恢复正常输入后会进行最终的溢出处理,因此存储中会产生多个分区文件。然而,溢出过程是分区的,可以进行并行化写入,从而提高溢出吞吐量。在恢复过程中,也是按照分区进行,逐个恢复数据(partition by partition)。由于是排序写入,最终需要进行排序合并,然后按照分区进行恢复,即将 RowContainer 中的数据重新恢复,累加器亦进行更新。通过这种方式,可以大大节约内存资源。因为在溢出时,如果我将 4 个分区的数据一次性恢复,那么内存可能会出现明显的膨胀。3. RowNumber 算子
接着是 RowContainer,其 row number 亦是一个常见的运算符。其作用在于以上述三个列(column)为基础进行窗口计算,并在其中填充相应的序号。然而,此运算符并非直接使用 HashTable 实现,而是通过计数器(counter)作为其 payload。与其他运算符不同,若发生溢出(spill),其接续输入也将溢出。因此,其在存储空间中会存在两份数据,一份是已经计算过的结果数据,另一份则是后续输入。在此过程中,无需进行排序操作。在恢复阶段,将按照分区进行恢复,重新构建 HashTable,并将溢出的数据重新插入容器。容器重新探测后,将更新 counter。由于其为流式处理,例如,当 probe 出现时,counter 达到一定大小后,将输出给上游。因此,它会直接更新 counter,后续输入可以继续探测和更新 counter,然后再次输出给上游。因此,此过程无需保持顺序,只需进行分区操作即可。
4. Recursive Spill
随后在执行过程中我们发现,RowNumber 在溢出时可能仅针对个别分区,例如当将数据恢复至内存时,实际占用的空间仍然较大。并且先前的执行规范表明,一旦发生溢出,便不可再次溢出,如若处于恢复阶段,则更是不得再次溢出。然而,假设当前分区确实过大,仍需实施溢出操作,此时我们采用了一种本机递归式溢出策略(Recursive Spill),即在层次 0,拥有如此数量的分区,例如当分区 2 (partition 2)恢复至内存后,若再次发生溢出,会将其再次划分为两部分,然后分别进行溢出。其次,处理完 partition 2-1 后,再把 partition 2-2 恢复至内层继续处理,一旦需要溢出,便再次将其恢复并溢出至第三层。这种方法适用于极为极端的情况,避免查询因内存不足而失败。
未来规划
目前 Velox 已成功与公司内部的天穹大数据平台实现了整合,内部的诸如 Spark 版本、Presto 版本,以及 SQL Gateway 等各个部分,以及内部的资源管理调度机制,都已经得到了集中统一。然而,在实施过程中,我们也遇到了诸多挑战。例如,业务部门可能会编写大量自定义 UDF、UDAF,为此我们专门为其提供了相关技术支持。同时,我们还开发了一种开放性框架,以便企业业务能够自主添加该功能模块。此外,我们还对一些内部方案,如基于 ORC 的文件格式进行了改造,同时对 text file 这类文件类型也提供了一定程度的支持。最后,我们还需要完成 HDFS 客户端的本地化工作,如接入腾讯的认证系统,而非使用 Kerberos 等传统方式。
从效果上看,Velox 可以实现 1.4-5 倍的性能提升,对互联网行业中的复杂型数据有着极高的处理效率。例如,通过将字段(subfield)向下推移,可以在扫描过程中进行行级别过滤,扫描完成后,还可以进一步进行行级别的过滤,从而有效地降低输出。此外,我们还进行了功能覆盖的增强,这主要表现在算子的完备程度上,我们与社区合作,提升了 Spark Presto 的构建函数覆盖,并改进了一些算子,如刚才提及的 RowNumber 算子的升级。此外,我们还在读写性能方面进行了诸多提升,例如,读操作方面,提供了面向 Shuffle Reader 的读写性能提升方案。同时,我们也计划将这些运营能力的提升贡献给社区,例如,Velox 的父分支很多都由我们开发,以及将原来使用 DuckDB 的 Presto golden source 改为使用 Presto Runner。关于未来的规划,重点之一是持续提升 Velox 在批处理场景中的性能表现。因为在 Spark 中有很多 ETL 任务和 IO 密集型的工作负载,如大批量的 scan 和 shuffle 操作。如果利用好 Velox
Columnar 的特性进行 Columnar shuffle,那么 RPC 数据量将会大幅下降,改善集群健康状况。另外,我们正在优化湖上查询,因为在未来,数据必然将入驻数据湖,例如, Parquet 已经成为了数据湖上的事实标准。对于跑开场景的优化,我们正在研究如何更好地利用 Iceberg 这种批处理场景。在 Iceberg 中,有大量的转换函数,以及用于湖上治理的 CO 等功能,这些都需要借助 Velox 才能实现。最后,我们正在优化写功能,例如,目前 Parquet 设计的 writer 需要转换为 Avro 的 ParquetWriter 进行写入,存在一个拷贝的过程。我们计划在最终的 writer 部分实现纯的 Velox Vector 的 ParquetWriter。此外,我们还计划将这些技术扩展到更多的场景,例如,Doris 也可以将其执行部分替换为 Velox。业内也正在进行相关的研究和探索。譬如,目前大模型已具规模,大家对大量 GPU 产生了需求,而 GPU 上的 CPU 算力则会产出过剩,因此面对结构化数据的处理,可以考虑将其下放至 CPU 以进一步发挥其算力,Velox 也为此提供了解决方案。
Q&A
Q1:段蒙老师,您好!我了解到您这边在内存管理、资源隔离领域开展了丰富的研究与实践。那么,关于 query 提交环节,特别是在提交前预先评估其资源利用,能否这样理解:在同时提交多个查询时,最初的分配过程就能根据实际需求进行更为合理的规划。A1:这个是好问题。事实上,Velox 主要致力于单个节点执行层面的性能优化,对于 query 提交这一层操作,并非由 Velox 直接处理。以我们在腾讯的经验为例,我们拥有海量的运营数据,每一个 query 的历史资源消耗我们都会进行详细的统计。因此,在提交查询时,我们会根据这些数据为其提供一些建议,以帮助其更好地进行字段计算配比。这样,我们便能通过平台侧的方式完成整个提交过程。Q2:在您之前的分享中注意到,存在一个 local 仲裁器和一个 global 仲裁器。请问这两者之间的差异或各自的职责是什么?
A2:关于这个问题,我可以解释一下。当每个 memory pool 请求扩大容量时,采用的是 Quantize 机制。例如,虽然应用程序实际申请的容量可能小于预期,但实际取得的容量可能会高于申请值。例如,当应用程序申请 1.5MB 的容量时,系统可能会给它分配 4MB 的容量,从而使应用程序实际拥有 2.5MB 的额外容量。这个机制的优势在于能够避免后续频繁申请,但也带来了新的问题。如果许多内存 pool 都有未使用的额外容量,那么我们可以直接将这些容量回收,避免发生溢出。Local
arbitration 和 Global arbitration 都会优先回收这些所谓的额外容量,如果容量还是不够,则需要回收实际占用的内存,不同的是 Local arbitration 只回收申请内存的 Query 自身的实际占用的内存,Global arbitration 会回收其它 Query 的实际占用的内存。回收实际占用的内存需要进行数据溢出处理,这部分开销较大,因为溢出处理需要进行磁盘操作。参考资料
[1] facebookincubator/velox. (Jul. 19,
2024). C++. Meta Incubator. Accessed: Jul. 21, 2024. [Online]. Available:
https://github.com/facebookincubator/velox[2] mssaperla, “什么是Photon?- Azure Databricks.” Accessed: Jul. 21, 2024.
[Online]. Available: https://learn.microsoft.com/zh-cn/azure/databricks/compute/photon[3] “Diving into the
Presto Native C++ Query Engine (Presto 2.0),” PrestoDB.
Accessed: Jul. 18, 2024. [Online]. Available:
https://prestodb.io/blog/2024/06/24/diving-into-the-presto-native-c-query-engine-presto-2-0/[4] T. Neumann and M.
Freitag, “Umbra: A Disk-Based System with In-Memory Performance,” presented at
the Conference on Innovative Data Systems Research, 2020. Accessed: Jul. 21,
2024.[5] A. Behm et al.,
“Photon: A Fast Query Engine for Lakehouse Systems,” in SIGMOD ’22:
International Conference on Management of Data, Philadelphia, PA, USA, June 12
- 17, 2022, Z. G. Ives, A. Bonifati, and A. E. Abbadi, Eds., ACM, 2022, pp.
2326–2339.[6] P. Pedreira et
al., “Velox: meta’s unified execution engine,” Proc. VLDB Endow.,
vol. 15, no. 12, pp. 3372–3384, Aug. 2022
分享嘉宾
INTRODUCTION
段蒙
腾讯
高级工程师
腾讯数据平台部高级工程师,腾讯大数据 Native Engine 项目负责人,Spark 项目负责人,Apache/Uniffle PPMC,Meta/Velox 项目 top 贡献者。
点个在看你最好看
SPRING HAS ARRIVED