从一到无穷大 #35 Velox Parquet Reader 能力边界

news2024/11/16 6:00:27

在这里插入图片描述本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。

文章目录

  • 引言
  • 源码分析
  • 功能描述
  • 功能展望

引言

InfluxDB IOX这样完全不使用索引,只是基于执行引擎与Arrow-rs Parquet Reader的极致工程化道路无疑是相对极端的,这样的做法在大多数低时间线场景性能基本不可能高于VictoriaMetricsInfluxdb v1.x这样的全倒排索引实现,尤其是在筛选条件较多时。

权衡之下,像GreptimeDB这样对Parquet构建稀疏倒排索引[7]的方案就成了非多引擎下的合理选择,依托于DatafusionArrow-rs活跃的社区和完备的功能,事实上可以相对低成本,低风险的在各方面指标达到良好的表现,在初创公司的角度来看这当然是一条合理,有效,且高效的道路。

但是世界并不是只由rust构成,也不是所有团队都有资源愿意像InfluxData一样对DataFusionArrow-rs这样基础库做大量的投入,并稳操社区的控制权,最后才反哺自己的产品,当然回馈了不少上层软件产品,同时孕育了一众开源的时序数据库产品。

对大多数团队来说如何在现有资源下低风险,高人效的拿成果就成了需要思考的问题。

在21世纪20年代来看,自研数据库计算引擎是一件极高投入,较低回报的事情,算子扩展,并行化,性能提升,稳定性等无不需要大量的精力投入,到最后性能,功能也不及世界顶尖的执行引擎产品,这事实上是基本可预料的,一个数据库产品团队的内核研发能有多少人力去做专用计算引擎呢?这也是

这条路Meta已经走过了[1],其设计了Velox用来替换PrestoSparkXStream等系统的执行引擎,基础语言为CPP。

我们的系统语言为Cpp,在经过技术调研后除去从DuckDBClickhouse等知名项目中抠执行引擎外,可行的技术选择只剩下了Arrow AceroVeloxArrow Acero虽然依托于Arrow-cpp社区,且愿景宏大,但是整体还处于实验阶段,且没有值得信赖的项目背书。相对之下Velox确实就成了唯一的选择。

Velox研究了一段时间后,认为Velox满足了90%以上的功能需求,但是部分性能关键点存在缺失,有比较大的修改空间,本篇文章聚集在Velox Parquet Reader,探究其功能缺失点。

源码分析

VeloxVelox Parquet Reader并不是原生的Arrow-cpp的实现,而是100%自主实现的,官方给出的解释[5]是:

Velox implements a visitor pattern suitable for most columnar file formats. This visitor pattern implements file-format agnostic features like predicate pruning, filter evaluation, etc… There are also some IO optimizations like prefetching and I/O coalescing.
The format-specific implementation (for Parquet, ORC, and DWRF) involves extending these visitor APIs. Wrapping these implementations around apache arrow parquet would involve a lot more work.
Velox implements a visitor pattern suitable for most columnar file formats. This visitor pattern implements file-format agnostic features like predicate pruning, filter evaluation, etc… There are also some IO optimizations like prefetching and I/O coalescing.

Velox代码中Parquet Reader(velox/dwio/parquet/reader)的部分只涉及了文件格式的编解码,连Decoder部分也是公有的,而类似filter pushdown这样的功能则是通过Decoder,放在visitor pattern中去实现。

这样来看,确实实现更多的优化功能会十分麻烦,因为这部分代码需要新增在common部分,且需要新增大量新的公有接口。

先给出一个我自己写的Filter pushdown的读取Parquet文件样例,example中对这部分描述比较少,而且都是很底层的接口,不研究下代码还是比较难写出来的,我认为对初学者有比较大的学习价值。

void processParquetFile(const std::string& filePath, std::vector<RowVectorPtr>& rowBatches, memory::MemoryPool* pool) {
    dwio::common::ReaderOptions readerOpts{pool};
    readerOpts.setFileFormat(FileFormat::PARQUET);
    auto reader = getReaderFactory(FileFormat::PARQUET)
                      ->createReader(
                          std::make_unique<BufferedInput>(
                              std::make_shared<LocalReadFile>(filePath),
                              readerOpts.memoryPool()),
                          readerOpts);
    if (!reader) {
        std::cerr << "Failed to create reader for file: " << filePath << std::endl;
        return;
    }

    RowReaderOptions rowReaderOptions;
    auto rowType = ROW({"measurement", "timestamp", "arch", "datacenter", "usage_user"},
                       {VARCHAR(), TIMESTAMP(), VARCHAR(), VARCHAR(), DOUBLE()});
    rowReaderOptions.select(
      std::make_shared<facebook::velox::dwio::common::ColumnSelector>(
        rowType, rowType->names(), nullptr, false));
    auto scanSpec = std::make_shared<facebook::velox::common::ScanSpec>("");
  
    auto untyped = parse::parseExpr("usage_steal >= 3.0", parse::ParseOptions());
    auto filterExpr = core::Expressions::inferTypes(untyped, rowType, pool);
    std::shared_ptr<core::QueryCtx> queryCtx{core::QueryCtx::create()};
    exec::SimpleExpressionEvaluator evaluator{queryCtx.get(), pool};
    auto [subfield, filter] = exec::toSubfieldFilter(filterExpr, &evaluator);
    auto fieldSpec = scanSpec->getOrCreateChild(subfield);
    fieldSpec->addFilter(*filter);

    scanSpec->addAllChildFields(*rowType);
    rowReaderOptions.setScanSpec(scanSpec);
    auto rowReader = reader->createRowReader(rowReaderOptions);
    std::cout << "The type of rowReader is: " << typeid(*rowReader).name() << std::endl;

    auto rowBatch = BaseVector::create(rowType, 50000, pool);

    while (rowReader->next(50000, rowBatch)) {
        auto rowVector = std::dynamic_pointer_cast<RowVector>(rowBatch);
        if (rowVector) {
            std::lock_guard<std::mutex> lock(batchMutex);
            rowBatches.push_back(rowVector);
        } else {
            std::cerr << "Error: Batch is not a RowVector for file: " << filePath << std::endl;
        }
    }
}

以一个Filter pushdown的流程来入门,整体代码流程如下:

velox/dwio/parquet/reader/ParquetColumnReader.cpp:build
velox/dwio/parquet/reader/StructColumnReader.h:StructColumnReader 在构造函数中构造每一列的column reader
velox/dwio/common/SelectiveStructColumnReader.cpp:next
velox/dwio/common/SelectiveStructColumnReader.cpp:read 从此处处获取每一列的数据
velox/dwio/common/SelectiveStructColumnReader.cpp:getValues 分别调用child的getValues
velox/dwio/parquet/reader/FloatingPointColumnReader.h:read 
    velox/dwio/common/SelectiveFloatingPointColumnReader.h:readCommon
    velox/dwio/common/SelectiveFloatingPointColumnReader.h:processFilter
    velox/dwio/common/SelectiveFloatingPointColumnReader.h:readHelper
    回调 velox/dwio/parquet/reader/FloatingPointColumnReader.h:readWithVisitor filter作为参数构造ColumnVisitor
    velox/dwio/parquet/reader/ParquetData.h:readWithVisitor(Visitor visitor)  visitor中包含filter
    velox/dwio/parquet/reader/PageReader.h:readWithVisitor
    velox/dwio/parquet/reader/PageReader.h:callDecoder
        velox/dwio/parquet/reader/PageReader.cpp:seekToPage 
        velox/dwio/parquet/reader/PageReader.cpp:prepareDataPageV2
        velox/dwio/parquet/reader/PageReader.cpp:makeDecoder 构造decode
    velox/dwio/dwrf/common/ByteRLE.h:readWithVisitor 各种code被赋值给decode
    velox/dwio/common/ColumnVisitors.h:process 最重要的逻辑,判断是否应该过滤列
    velox/type/Filter.h:applyFilter
    velox/dwio/common/ColumnVisitors.h:filterPassed
    velox/dwio/common/ColumnVisitors.h:addResult 然后把下标加入addOutputRow
    velox/dwio/common/ColumnVisitors.h:addOutputRow 
    回调 velox/dwio/common/SelectiveColumnReader.h:addOutputRow 把row加入outputRows_

功能描述

Velox Parquet Reader基本能力可以表述如下:

功能Rust-rsVelox说明
RowGroup Parallel Readingvelox 支持ParquetRowReader查询指定offset limit的数据;基于此机制可以实现RowGroup并行读取,但是需要在TableScan中额外封装,较为复杂,没有底层机制自动实现RowGroup并行读取
Page Indexoffset index + column index
Split Block Bloom filters (SBBF)[11][12] 利用现代 SIMD 指令将速度提高 30%-450% 的布隆过滤器变体
Streaming decode允许一次解码一批行后push到上层Operator执行,做到解码和执行形成一个pipeline,这也是Velox的Push执行模型
I/O pushdown避免缓冲整个文件,首先获取和解码元数据,然后对相关数据块进行范围提取,并与 Parquet 数据的解码交错,并积极使用各类filter pushdown
Dictionary preservation在解码期间保留字典,可显著提高读取 Arrow 数组时的性能
Vectorized decode一次将多个值解码为列式内存格式
Projection pushdown只读选择的列
Predicate pushdown条件下推
RowGroup pruning基于条件过滤不需要读取的RowGroup
Page pruning1. velox虽然PageReader中存在skip的函数,但是并不是为了filter push down服务的,filter push down是行级别判断的; 2. velox不支持倒排索引的指定行读取,这个过程也会执行 Page pruning,datafusion就做的很好[8]
Late materialization多列执行filter后求交集再解码,在行较多,条件较多时可以减少大量的解码开销
Pre-Buffer预缓冲原始 Parquet 数据,而不是每个column chunk进行一次读取
Parquet V2列编码和页压缩算法的完全兼容实现
自定义Row Filter[8][9]此功能允许对Parquet实现自定义的索引,以大幅加速查询性能;但是需要PageIndex,这样就可以基于传入的row在跳过RowGroup的基础上跳过不需要的Page

总体来看的评价是:

  1. 基本功能完善
  2. 性能可想的不够优异
  3. 架构追求美感,包袱重,很难复用Arrow-cpp
  4. 不支持倒排索引

其次从部分测试结果看,Velox filter pushdown存在一些性能问题,还没有filter Operator快,这个具体的原因后续仔细分析以下,暂时倒不是特别急切。

场景Velox 500条件Velox 15条件
Q1filter算子3.566s0.123s
Q1filter pushdown3.220s0.117s
Q2filter算子0.951s0.163s
Q2filter pushdown3.272s0.124s
Q3filter算子1.455s0.116s
Q3filter pushdown3.851s0.142s

功能展望

我们对于Velox Parquet Reader的核心诉求是支持Parquet级别的倒排索引,这样可以无缝适配到TableScan算子的异步Push模式中去。执行引擎本身当然也需要改一些地方,但都可以逐步迭代,并不急切。

我改了一版的Velox代码,以支持Parquet ReaderRow级别的过滤,只需要在AddSplit时添加文件对应的RowFilter信息即可,接口部分模仿DataFusion+Arrow-rs,但是还有部分测试和代码优化工作需要细化;

肉眼可见的,Velox社区还有大量的核心特性的贡献机会,虽然Meta的开源社区维护一直被人诟病,但是有PrestoSpark背书近五年到不必担心项目爆雷。

参考:

  1. 从一到无穷大 #26 Velox:Meta用cpp实现的大一统模块化执行引擎
  2. https://blog.mwish.me/
  3. Velox: Meta’s Unified Execution Engine vldb2022
  4. Querying Parquet with Millisecond Latency
  5. [Design] Native Parquet Reader
  6. PARQUET-1820: [C++] pre-buffer specified columns of row group #6744
  7. GrepTimeDB index
  8. advanced_parquet_index.rs
  9. parquet/src/arrow/arrow_reader/selection.rs
  10. Faster C++ Apache Parquet performance on dictionary-encoded string data coming in Apache Arrow 0.15
  11. Parquet SBBF
  12. Split block Bloom filters

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2155881.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《沧浪之水》读后感

未完待续..... 未完待续.... 未完待续.... 【经典语录】 01、我一辈子的经验就是不要做瞎子&#xff0c;也不能做聋子&#xff0c;该听到的信息要听到&#xff0c;但是要做哑巴&#xff0c;看到了听到了心中有数就行了&#xff0c;可千万不要张口说什么。 02、你刚从学校毕业…

MQ入门(一):同步调用和异步调用--RabbitMQ基础入门

目录 1.初识MQ 1.1.同步调用 1.2.异步调用 1.3.技术选型 2.RabbitMQ 2.1.安装部署 2.2.RabbitMQ基本架构 2.3.收发消息 2.3.1.交换机 2.3.2.队列 2.3.3.绑定关系 2.3.4.发送消息 2.4.数据隔离 2.4.1.用户管理 2.4.2.virtual host 1.初识MQ 微服务一旦拆分&…

web前端字段大小写下划线转换工具

文章目录 前言一、如何使用&#xff1f;二、相关代码总结 前言 程序员在敲代码的过程中都要命名一些字段&#xff0c;但是Java语言对字段的命名规范和sql命名规范不一样&#xff0c;如下图所示&#xff0c;这种机械性的转换工作很劳神费力&#xff0c;为了省点劲写了一个web小…

尚品汇-Jenkins部署构建服务模块、Linux快照备份(五十七)

目录&#xff1a; &#xff08;1&#xff09;构建作业&#xff08;server-gateway&#xff09; &#xff08;2&#xff09;构建service_product模块 &#xff08;3&#xff09;演示添加新代码 &#xff08;4&#xff09;学会使用linux快照 &#xff08;1&#xff09;构建作…

在SpringCloud中实现服务间链路追踪

在微服务架构中&#xff0c;由于系统的复杂性和多样性&#xff0c;往往会涉及到多个服务之间的调用。当一个请求经过多个服务时&#xff0c;如果出现问题&#xff0c;我们希望能够快速定位问题所在。这就需要引入链路追踪机制&#xff0c;帮助我们定位问题。 Spring Cloud为我们…

【沪圈游戏公司作品井喷,游戏产业复兴近在眼前】

近期财报季中&#xff0c;腾讯、网易及B站等国内游戏巨头纷纷亮出亮眼的游戏业务表现&#xff0c;均实现了接近或超越双位数的同比增长。然而&#xff0c;审视过去一年&#xff0c;国内游戏行业仍笼罩在宏观经济“降本增效”的阴影下。 行业数据揭示&#xff0c;全国游戏公司社…

封装 wx.request 的必要性及其实现方式

目录 为什么需要封装 wx.request 1. 避免回调地狱 2. 统一管理 3. 扩展功能 小程序异步 API 的改进 封装实现方式 在小程序开发中&#xff0c;网络请求是不可或缺的功能之一。小程序提供了 wx.request API 来实现网络请求&#xff0c;但直接使用这个 API 在复杂场景下可…

关于SpringBoot项目使用maven打包由于Test引起的无法正常打包问题解决

一、问题描述 在日常工作中&#xff0c;在接手项目时&#xff0c;项目未必是“正常”的&#xff0c;一般平常搭建项目&#xff0c;都不会采用一键式生成的方式&#xff0c;现在说下旧项目&#xff0c;可能项目结构并不是那么简洁&#xff0c;通常都带有与main同层级的test&…

Cpp类和对象(中续)(5)

文章目录 前言一、赋值运算符重载运算符重载赋值运算符重载赋值运算符不可重载为全局函数前置和后置的重载 二、const修饰成员函数三、取地址及const取地址操作符重载四、日期类的实现构造函数日期 天数日期 天数日期 - 天数日期 - 天数日期类的大小比较日期类 > 日期类日…

嵌入式系统stm32cube本地安装出现的问题

stm32cube在线安装很慢&#xff0c;本地安装中出现的一个bug stm32cube_fw_f4_v1281安装成功之后&#xff0c;如果想安装stm32cube_fw_f4_v1281会提示stm32cube_fw_f4_v1280未安装。 如果先安装stm32cube_fw_f4_v1280之后&#xff0c;再安装stm32cube_fw_f4_v1281还会提示这个…

Python模拟鼠标轨迹[Python]

一.鼠标轨迹模拟简介 传统的鼠标轨迹模拟依赖于简单的数学模型&#xff0c;如直线或曲线路径。然而&#xff0c;这种方法难以捕捉到人类操作的复杂性和多样性。AI大模型的出现&#xff0c;能够通过深度学习技术&#xff0c;学习并模拟更自然的鼠标移动行为。 二.鼠标轨迹算法实…

C#如何把写好的类编译成dll文件

1 新建一个类库项目 2 直接改写这个Class1.cs文件 3 记得要添加Windows.Forms引用 4 我直接把在别的项目中做好的cs文件搞到这里来&#xff0c;连文件名也改了&#xff08;FilesDirectory.cs&#xff09;&#xff0c;这里using System.Windows.Forms不会报错&#xff0c;因为前…

go项目多环境配置

1.java项目配置加载最佳实践 在 Spring Boot 项目中&#xff0c;配置文件的加载和管理是开发过程中不可或缺的一部分。Spring Boot 提供了一套灵活且强大的机制来加载配置文件&#xff0c;使得开发者能够根据不同的环境和需求轻松地管理配置。当多个位置存在相同的配置文件时&…

Python语法进阶之路

一、Python基础 1.1 注释 定义和作用 对代码解释说明&#xff0c;增强可读性 单行注释 # 多行注释 """ 这是一个多行注释 """ 1.2 变量及变量类型 定义和作用 计算机目的是计算&#xff0c;编程是为了更方便计算&#xff0c;计算对象就是…

vue循环渲染动态展示内容案例(“更多”按钮功能)

当我们在网页浏览时&#xff0c;常常会有以下情况&#xff1a;要展示的内容太多&#xff0c;但展示空间有限&#xff0c;比如我们要在页面的一部分空间中展示较多的内容放不下&#xff0c;通常会有两种解决方式&#xff1a;分页&#xff0c;“更多”按钮。 今天我们的案例用于…

mybatis 配置文件完成增删改查(二):根据条件查询一个

文章目录 参数占位符#{}:会将其替换为&#xff1f; ——为了防止sql注入${}:会将其替换为实际接收到的数据&#xff0c;拼sql ——无法防止sql注入 查询一个sql特殊字符的处理 参数占位符 #{}:会将其替换为&#xff1f; ——为了防止sql注入 ${}:会将其替换为实际接收到的数据…

Java继承教程!(o|o)

Java 继承 Java面向对象设计 - Java继承 子类可以从超类继承。超类也称为基类或父类。子类也称为派生类或子类。 从另一个类继承一个类非常简单。我们在子类的类声明中使用关键字extends&#xff0c;后跟超类名称。 Java不支持多重继承的实现。 Java中的类不能有多个超类。…

Linux-gcc/g++

系列文章目录 C语言中的编译和链接 文章目录 系列文章目录一、编译过程gcc如何完成过程在这里涉及到一个重要的概念:函数库 二、动态库、静态库2.1 函数库一般分为静态库和动态库两种。 三、gcc选项gcc选项记忆 一、编译过程 具体过程在这一片c语言文章中讲解过:C语言中的编…

shardingjdbc分库分表原理

一 Mysql的瓶颈 二 解决方案 三 hash环算法 四 雪花算法

CCF csp认证 小白必看

c支持到C17(还是更高?)&#xff1b;所以学一些封装好的函数功能是必要的---比如STL里的函数&#xff1b; 因为可携带纸质资料&#xff0c;建议打印带入&#xff0c;需要时可翻阅。 【题目概述:】 0-devc环境配置 配置好你常用的编译版本&#xff1a; 想要调试记得开启下选…