列式存储系列(二): Vertica

简介: 本文就 Vertica 的数据模型、存储、执行引擎以及这几个方面与 C-Store 的区别进行了简单的介绍。总的来说,Vertica 是一个纯正的列式存储数据库,为此,Vertica 设计实现了 projection 这一数据模型,并围绕该模型设计实现了一套大数据分析管理引擎。

作者:辛庸,阿里巴巴计算平台事业部 EMR 技术专家。Apache Hadoop,Apache Spark contributor。对 Hadoop、Spark、Hive、Druid 等大数据组件有深入研究。目前从事大数据云化相关工作,专注于计算引擎、存储结构、数据库事务等内容。

本文是列式存储系列的第二篇。在上一篇,我们介绍了C-Store,一个列式存储数据库。在本篇,我们讲述 C-Store 的继任者——Vertica。C-Store 是一个概念原型,在这个概念原型提出并发表后,Stonebraker 着手建立了一家公司研发商用的列式分析型数据库,公司名字就叫 Vertica。2011 年 Vertica 被惠普公司收购。2012 年,Vertica 公布了它的论文:《The Vertica Analytic Database: C-Store 7 Years Later》。Vertica 没有使用 C-Store 的任何代码,仅仅是借鉴了 C-Store 的某些设计思想。同时,对于 C-Store 一些设计不合理的地方,Vertica 提出了改进措施。在这篇文章里,Vertica 详细介绍了那些与 C-Store 不同的设计及其背后的设计考量。

Projection


C-Store 的核心概念是 Projections,即一张表的若干列子集构成一个 projection,多个 projection 的并集构成整张表。每个 projection 有自己的 sort-order。Projection 的概念类似于物化视图。但是物化视图要更为复杂,它往往还包含聚合、索引、join 等内容,而 projection 仅仅包含数据。Vertica 继承了 C-Store 的这个概念。

1
上图是 projection 的一个示例。对于一张原始表,它被拆分成了两个 projection。第一个 project 包含原表的全部五个字段,并按照 date 排序。第二个 projection 包含两个字段,并按照 cust 排序。在 Vertica 中,数据被分段(segment)存储到不同的节点。每个 projection 有不同的分段依据。在第一个 projection 中,分段依据为 hash(sale_id),第二个 projection 中,分段依据为 hash(cust),即图中浅色的字段。

在 C-Store 的介绍文章中我们有提到,projection 的设计的核心难点在于,如何根据几个 partial projection 还原出原表,因为不同的 projection 中数据的排序都是不一样的。C-Store 采用了一种 Join-Index 的做法(具体原理请参考上一篇文章)。Vertica 没有采用 Join-Index,而是要求所有的表都必须要有一个 super projection,即包含所有列的 projection。之所以不采用 Join-Index 是因为维护 Join-Index 的负担过重,已经远远超出了 projection 设计带来的好处。不过这里笔者有点疑惑,根据 Join-Index,我们能够根据 partial projections 还原出原 row(或者原 row 的一部分),但是 Vertica 没有 Join-Index,要根据 partial projection 获得原 row(或者原 row 的一部分)是不可能的,我们只能从 super projection 去读。例如,假设原表有 200 个列,super projection 包含全部的列。projection 1 包含列 3,4,5,并且按照列 3 排序,projection 2 包含列 5,6,7,并且按照列 5 排序。如果要查询列 3,4,5,6,7,我们不能从 projection 1 join projection 2 得到结果,只能读 super projection。这样看来,Vertica 实际上牺牲了 C-Store projection 的灵活性。可惜的是 Vertica 论文并没有交代这些细节。

除此之外,Vertica 还保留了 C-Store 中设计的 prejoin projection,即一个 projection 可以包含多张表(如一张事实表和多个维表)的列,只要它们之间存在外键关系即可。

Partitioning 和 Segmentation


同其他数据库类似,Vertica 支持分区(Partitioning)。Vertica 的分区在物理上的表现是在一个节点内部,对存储划分不同的 logical region,这样就能达到并行处理不同分区的能力。除了分区,Vertica 还支持分段(Segmentation)。在上一小节有提到,对于一个 projection,会按照其中一个字段 hash 为一个个独立的 segments,然后将 segments 分发到不同的 nodes 存储。与分区不同的是,分段是在节点间扩展的,而分区是在节点内。

Vertica 的 projection 可以有副本(replica)。副本也按照 segment 存储。副本设计的优点是一方面增加了系统的可用性,另一方面也能够在一定程度上提高并发性。
image
对于分区和分段,我们也能够从其他的分布式系统中看到一些类似的设计。对于 Hive 这种 Hadoop based 的 NoSQL 系统,由于存储本身为分布式存储,因此上层大体上是不太管怎么存的,但是上层必须要根据 HDFS 上 block 的存储做本地性优化。这个有点类似于 Vertica 的分段,均是大文件跨节点存储。在开源搜索系统 Elasticsearch 中,一个索引被划分为 Shard,并跨节点存储,也是和这里的分段类似的。ES 的 Shard 也有 replica。可见,在分布式环境下对存储做分区和分段,是一种通常的做法。

ROS 和 WOS


Vertica 实现了 ROS(Read Optimized Store)和 WOS(Write Optimized Store),其作用和 C-Store 中的 RS 和 WS 类似。ROS 负责管理存储在磁盘上的数据,而 WOS 则运行在内存中,用于应对频繁的 Insert/Delete/Update 等操作。当 WOS 中的数据到达一定量之后,会运行 Tuple Mover 将 WOS 中的数据移动至 ROS。

C-Store 的 RS 主要负责存储 segments 数据和相应的 Join-Index,而在 Vertica 中没有 Join-Index,因此这部分数据就不用存了。但是 ROS 还存了另外一类数据:Position-Index。Position-Index 不是存储每一条数据的 position,而是有点类似于 Parquet 文件中的 meta。它记录了磁盘上每个数据块的起始位置、数据的最大值最小值等信息。这些信息可以用来在运行时过滤数据。

ROS 有个概念叫做 ROS container。一个 container 是一个小的“容器”,容纳一部分数据。这样做的好处是可以在一定程度上提高数据处理的并发性(节点内并发)。图 3 展示了一个 projection 在一个 node 内的存储状况。

image

在这个例子中,首先数据在节点内是分区的,分区列为 MONTH/YEAR,分为 3/2012、4/2012、5/2012、6/2012 四个区。然后数据根据 hash(cid) 分段,分为三个段:Segment 1,2,3. 每个段的数据占用一定数量的 container 用于存储数据,在这里有 14 个 container。对于每个 column,Vertica 存储两个文件:数据文件和 Position-Index,这样总共有 28 个数据文件和 28 个 Position-Index 文件。

Execution Engine


Vertica 设计了一套并行化的、管道式的执行引擎。这一部分的介绍比较枯燥乏味,并且和其他的 pipline 式的 SQL 执行引擎大同小异(如 impala、presto 等)。这里介绍一下 Vertica 论文中特意提到的执行引擎的优化点。

1.Sideways Information Passing(SIP)。名字非常高大上,但是原理比较简单,就是利用一些“不是非常直观的信息”生成 predicate。所谓“不是非常直观的信息”就是从 plan 中 infer 出来的信息。比如对于 HashJoin,如果表被 hash 的表较小,那么可以提前 collect 相应的值,分发到大表端进行过滤数据。这个特性 Hive 也有,叫做 “Runtime Filter”。另外,EMR-Spark 也实现了这个特性,有兴趣的同学可以测试体验一下。Vertica 的 SIP 除了 Hash Join 还用到了 Merge Join 上,但是论文没有具体讲。

2.算法的运行时调整。比如 HashJoin 发现 Hash table 在内存中无法放下,它会转为 Merge Join。

3.Prepass 算子。它本质上是多线程的预先执行一些操作,然后这些操作的结果可以被下游的 operator 利用生成最终结果。Vertica 给了个例子,如图 4 所示。对于 GroupBy,Vertica optimizer 会生成几个 stage 来执行。其中第一个 stage 会生成一个 L1 缓存同等大小的 HashTable(从这点上看,Vertica 确实做到了硬件级别的优化),用来预先聚合数据(图 4 中倒数第二个算子),并把这些数据发送到下游。当一批数据聚合好后 clear 掉 HashTable,再聚合下一批。最终的 GroupBy 是在正数第三个算子实现的。从这个原理上看,我觉着与其叫做 Prepass,不如叫算子下推更为合适一些。
image

1.JIT(Just-in-time Compilation)。在很多计算中,计算都是类型相关的。为了避免类型判断带来的开销以及虚调用,很多计算引擎利用了 JIT 技术来提升性能,Vertica 也不例外(这几乎成了现在 SQL 执行引擎的标配了)。不过 JIT 是一个非常考验技术的技术,JIT 做的好并不容易。

其他


讲了数据模型、存储和计算引擎,Vertica 的核心就介绍完了。Vertica 的论文还介绍了 Tuple Mover、事务管理、容灾等细节,这里就不介绍了,有兴趣的读者可以参阅论文。

总结


本文就 Vertica 的数据模型、存储、执行引擎以及这几个方面与 C-Store 的区别进行了简单的介绍。总的来说,Vertica 是一个纯正的列式存储数据库,为此,Vertica 设计实现了 projection 这一数据模型,并围绕该模型设计实现了一套大数据分析管理引擎。在 NoSQL 风起云涌的时代,Vertica 能够凭借自身良好的设计,在广大 NoSQL 方案包围圈里争得一席之地,且没有开源优势,充分说明了它的实力。

参考文献
[1] Andrew Lamb et.al. The Vertica Analytic Database: C-Store 7 Years Later. In VLDB, pages 1790-1801, 2012.

_

相关文章
|
存储 SQL 关系型数据库
ClickHouse(02)ClickHouse架构设计介绍概述与ClickHouse数据分片设计
ClickHouse的核心架构包括执行过程和数据存储两部分。执行过程涉及Parser与Interpreter解析SQL,通过Column、DataType、Block、Functions和Storage模块处理数据。Column是内存中列的表示,Field处理单个值,DataType负责序列化和反序列化,Block是内存中表的子集,Block Streams处理数据流。Storage代表表,使用不同的引擎如StorageMergeTree。数据存储基于分片和副本,1个分片由多个副本组成,每个节点只能拥有1个分片。
1382 0
ClickHouse(02)ClickHouse架构设计介绍概述与ClickHouse数据分片设计
|
8月前
|
人工智能 自然语言处理 JavaScript
搞定 OpenClaw 调用阿里云百炼 API:从密钥获取到配置验证全流程
本教程详解OpenClaw(原Moltbot/Clawdbot)接入阿里云百炼API全流程:从Node.js环境准备、一键安装,到获取百炼API Key、配置环境变量及修改JSON配置文件,支持qwen3-max等Qwen3系列模型。新客首月仅0.3元/天,含通义千问万亿参数思考大模型。
|
11月前
|
人工智能 自然语言处理 安全
程序员都在偷偷用的AI编程神器!2025高效自动写代码工具全解析
AI编程革命正悄然兴起。李响团队调研显示,AI助手大幅提升开发效率,GitHub Copilot、CodeWhisperer、Tabnine各具优势,而新兴的Lynx AI更以自然语言生成全栈应用,零门槛、多端适配,助力开发者从重复劳动中解放,专注创新。
程序员都在偷偷用的AI编程神器!2025高效自动写代码工具全解析
|
资源调度 监控 物联网
《深入探秘:分布式软总线自发现、自组网技术原理》
分布式软总线是实现设备高效互联的关键技术,其自发现与自组网功能为多设备协同奠定了基础。通过融合Wi-Fi、蓝牙、NFC等通信技术,设计针对性发现协议,并采用统一接口封装,简化开发复杂度。自组网技术解决异构网络互联互通问题,支持混合拓扑结构,优化通信资源调度,引入软时钟确保时间同步。这些特性使分布式软总线成为构建万物互联智能时代的核心支撑,推动智能家居、智能办公等领域创新发展,提升生活与工作效率。
901 8
|
JavaScript 中间件 测试技术
FastAPI全面指南:从入门到企业级应用实战
FastAPI正迅速成为Python Web开发领域的明星框架。它以高性能、高效率和现代化特性著称,性能媲美Go/Node.js,支持异步编程并内置自动化文档系统。本文全面解析FastAPI核心功能,包括类型安全路由、Pydantic数据验证、异步支持等,并通过实战案例展示其在RESTful API开发、微服务架构、实时数据处理及机器学习模型部署中的应用。同时,文章提供数据库集成、中间件配置和测试策略等最佳实践,解决常见问题并展望未来技术发展方向。掌握FastAPI,助你构建高效现代化Web应用。
2400 1
|
编解码 监控 安全
JT1078和GB28181差别在哪里?
JT1078和GB28181分别是针对车载监控和公共安全监控设计的标准协议。JT1078专注于车载视频监控,适用于物流与交通场景,强调实时传输、编解码支持及无线环境下的数据安全性;而GB28181侧重于大规模公共安全监控,覆盖城市安防等领域,支持多协议交互与级联方案。两者在技术上有交集,需通过中间件实现互联互通,各有独特优势以满足不同需求。
998 8
|
数据可视化 图形学 UED
只需四步,轻松开发三维模型Web应用
为了让用户更方便地应用三维模型,阿里云DataV提供了一套完整的三维模型Web模型开发方案,包括三维模型托管、应用开发、交互开发、应用分发等完整功能。只需69.3元/年,就能体验三维模型Web应用开发功能!
1413 10
只需四步,轻松开发三维模型Web应用
|
SQL HIVE
【Hive SQL】字符串操作函数你真的会用吗?
本文介绍了SQL中判断字符串是否包含子串的几种方法。`IN`函数判断元素是否完全等于给定元素组中的某项,而非包含关系。`INSTR`和`LOCATE`函数返回子串在字符串中首次出现的位置,用于检测是否存在子串。`SUBSTR`则用于提取字符串的子串。`LIKE`用于模糊匹配,常与通配符配合使用。注意`IN`并非用于判断子串包含。
2095 3
|
缓存 Java Apache
常见的 HTTP 状态码分类及说明
这篇文章介绍了常见的HTTP状态码分类及其说明,包括1xx信息响应、2xx成功、3xx重定向、4xx客户端错误和5xx服务器错误,并提供了一个使用Apache HttpClient进行HTTP POST请求的Java代码示例。