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

在这里插入图片描述本作品采用知识共享署名-非商业性使用-相同方式共享 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:readCommonvelox/dwio/common/SelectiveFloatingPointColumnReader.h:processFiltervelox/dwio/common/SelectiveFloatingPointColumnReader.h:readHelper回调 velox/dwio/parquet/reader/FloatingPointColumnReader.h:readWithVisitor filter作为参数构造ColumnVisitorvelox/dwio/parquet/reader/ParquetData.h:readWithVisitor(Visitor visitor)  visitor中包含filtervelox/dwio/parquet/reader/PageReader.h:readWithVisitorvelox/dwio/parquet/reader/PageReader.h:callDecodervelox/dwio/parquet/reader/PageReader.cpp:seekToPage velox/dwio/parquet/reader/PageReader.cpp:prepareDataPageV2velox/dwio/parquet/reader/PageReader.cpp:makeDecoder 构造decodevelox/dwio/dwrf/common/ByteRLE.h:readWithVisitor 各种code被赋值给decodevelox/dwio/common/ColumnVisitors.h:process 最重要的逻辑,判断是否应该过滤列velox/type/Filter.h:applyFiltervelox/dwio/common/ColumnVisitors.h:filterPassedvelox/dwio/common/ColumnVisitors.h:addResult 然后把下标加入addOutputRowvelox/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.xdnf.cn/news/1542426.html

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

相关文章

JavaEE: 深入探索TCP网络编程的奇妙世界(四)

文章目录 TCP核心机制TCP核心机制四: 滑动窗口为啥要使用滑动窗口?滑动窗口介绍滑动窗口出现丢包咋办? TCP核心机制五: 流量控制 TCP核心机制 书接上文~ TCP核心机制四: 滑动窗口 为啥要使用滑动窗口? 之前我们讨论了确认应答策略,对每一个发送的数据段,都要给一个ACK确…

centos7下openssh升级方法(编译安装)

注意&#xff1a; 首先打开两个或以上的shell连接&#xff0c;因为在升级过程中如果升级失败会导致不发新建shell连接&#xff1b;升级后使用xshell6,7连接&#xff0c;openssh版本对应修改&#xff0c;下载地址&#xff1a; https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/por…

Servlet day2(概念理解)

Servlet体系结构 Servlet相关配置 HTTP协议内容

leetcode746. 使用最小花费爬楼梯,动态规划

leetcode746. 使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶…

计算机毕业设计 基于SpringBoot的小区运动中心预约管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Java笔试面试题AI答之设计模式(4)

文章目录 16. 简述什么是观察者模式&#xff1f;基本概念主要特点实现方式应用场景优缺点 17. 请列举观察者模式应用场景 &#xff1f;18. 请用Java代码实现观察者模式的案例 &#xff1f;19. 什么是装饰模式&#xff1f;定义与特点结构与角色工作原理优点应用场景示例 20. 请用…

基于大数据的电子产品需求数据分析系统的设计与实现(Python Vue Flask Mysql)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

【GlobalMapper精品教程】088:按点线面空间位置选择案例

按点线面空间位置选择的原则为:点线面的排列组合。 文章目录 一、选择线要素附近的点二、选择相交或触碰所选线的区和线三、选择包含点的区要素四、选择选定区域内的点要素一、选择线要素附近的点 启动该工具之前,首先要选择线,例如,选择某一段铁路5km范围之内的县城驻地。…

DeepSeek 2.5本地部署的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…

[Meachines] [Medium] Sniper RFI包含远程SMB+ powershell用户横向+CHM武器化权限提升

信息收集 IP AddressOpening Ports10.10.10.151TCP:80,135,139,445,49667 $ nmap -p- 10.10.10.151 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 80/tcp open http Microsoft IIS httpd 10.0 |_http-server-header: Microsoft-IIS/10.…

三阶魔方还原法 勾上回下 上右左左右

三阶魔方还原法&#xff1a; 1小白花 &#xff08;转3换1&#xff09; 2白十字架 (侧与中心同色 下下) 3第一层 &#xff08;找位置角块放顶点 勾上回下&#xff09; 4 第二层 &#xff08;颜色边 勾上回下 再单白边 勾上回下&#xff09; 5 黄十字架 &#xff08;无黄边 压 勾…

0.设计模式总览——设计模式入门系列

在现代软件开发中&#xff0c;设计模式为我们提供了优秀的解决方案&#xff0c;帮助我们更好地组织代码和架构。本系列专栏将对设计模式的基本思想、原则&#xff0c;以及常用的分类、实现方式&#xff0c;案例对比、以及使用建议&#xff0c;旨在提高开发者对设计模式的理解和…

【算法】BFS系列之 拓扑排序

【ps】本篇有 3 道 leetcode OJ。 目录 一、算法简介 二、相关例题 1&#xff09;课程表 .1- 题目解析 .2- 代码编写 2&#xff09;课程表 II .1- 题目解析 .2- 代码编写 3&#xff09;火星词典 .1- 题目解析 .2- 代码编写 一、算法简介 【补】图的基本概念 &#…

HTML翻牌器:用CSS和HTML元素创造动态数字展示

HTML翻牌器&#xff1a;用CSS和HTML元素创造动态数字展示 前言 翻牌器是一种数字动态展示形式&#xff0c;在生活中常见的例如翻牌计分、翻牌时钟等。 之所以以翻牌的形式是因为其物理设计的原因使其只能滚动翻牌展示数字&#xff0c;在电子显示设备不普及时&#xff0c;使用…

Leetcode - 139双周赛

目录 一&#xff0c;3285. 找到稳定山的下标 二&#xff0c;3286. 穿越网格图的安全路径 三&#xff0c;3287. 求出数组中最大序列值 四&#xff0c;3288. 最长上升路径的长度 一&#xff0c;3285. 找到稳定山的下标 本题就是找[0&#xff0c; n-2]中&#xff0c;height[i]…

C++入门12——详解多态2

上篇文章&#xff08;C入门12——详解多态1&#xff09;中&#xff0c;我们介绍了C多态的概念和用法&#xff0c;但是只知其然而不知其所以然是万万不行的&#xff0c;所以本篇文章将从探案的角度详细介绍多态的原理。 1. 虚函数表 想要弄懂多态的原理&#xff0c;首先要了解一…

数据结构与算法学习day22-回溯算法-分割回文串、复原IP地址、子集

一、分割回文串 1.题目 131. 分割回文串 - 力扣&#xff08;LeetCode&#xff09; 2.思路 分割回文串可以抽象为一棵树形结构。 递归用来纵向遍历&#xff0c;for循环用来横向遍历&#xff0c;切割线&#xff08;就是图中的红线&#xff09;切割到字符串的结尾位置&#xf…

STM32F407单片机编程入门(十三) 单片机IAP(在应用编程)详解及实战源码

文章目录 一.概要二.STM32F407VET6单片机IAP介绍1.STM32F407VET6单片机IAP基本原理2.STM32F407VET6单片机IAP基本流程 三.配置一个BOOT工程四.配置一个APP工程五.工程源代码下载六.小结 一.概要 STM32单片机程序升级方法有很多种&#xff0c;主要有以下几种&#xff1a; 1.将…

【LeetCode】146. LRU缓存

1.题目 2.思想 3.代码 3.1 代码1 下面这是一版错误的代码。错误的原因在于逻辑不正确导致最后的代码也是不正确的。 class LRUCache:def __init__(self, capacity: int):self.time 0 # 用于全局记录访问的时间self.num2time {} # 数字到时间的映射self.key2val {} # 数字…

如何理解MVCC

MVCC是什么&#xff1f; MVCC&#xff0c;是MultiVersion Concurrency Control的缩写&#xff0c;翻译成中文就是多版本并发控制&#xff0c;多个事务同时访问同一数据时&#xff0c;调控每一个事务获取到数据的具体版本。和数据库锁一样&#xff0c;它也是一种并发控制的解决…