数据STUDIO

Parquet文件格式,已成为大数据存储的默认选择

Image

Image
 高效存储与快速查询的完美结合

在数据量爆炸式增长的今天,如何高效地存储和处理海量数据成为了每个数据团队面临的挑战。面对这些挑战,Apache Parquet自2013年问世以来,迅速崛起为大数据存储领域的事实标准。但究竟是什么让Parquet格式如此受欢迎?它背后有哪些精妙的设计?今天,我们就来深入解析Parquet文件格式。

行存储 vs 列存储:根本性的差异

要理解Parquet的优势,我们首先需要了解行存储和列存储的根本区别。

在传统的行存储格式(如CSV、Avro)中,数据按行序列进行存储。这意味着每一行的所有字段值都连续存储在一起。这种存储方式适合事务处理(OLTP),其中需要频繁地访问整行记录。

Image

与此相反,列存储格式(如Parquet)将每一列的数据单独存储。这种存储方式特别适合分析型查询(OLAP),因为大多数分析查询通常只涉及表中的部分列,而不需要访问所有列。

Image

直观比喻:想象一下,行存储就像把一件件完整的衣服(整行记录)挂在衣柜里,而列存储则是将所有的袖子、衣身和纽扣(列)分别存放在不同的抽屉中。当只需要修改袖口设计时,后者显然更高效。

Parquet的核心设计:不只是简单的列存储

Parquet最精妙的地方在于,它并不仅仅是简单的列存储,而是引入了一个多层次的结构,将行和列的优势结合起来。

三层核心结构

一个Parquet文件包含三个相互关联的层次结构:

  1. 行组:每个文件被划分为多个行组,每个行组包含一定数量的行(默认为100万行)。行组是数据垂直和水平分区的单元。
  2. 列块:在每个行组中,每列都有一个对应的列块,包含该列在行组内的所有值。
  3. 页:每个列块又被划分为页(默认最大为1MB)。页是Parquet文件中最小的读写单元,有两种类型:数据页和字典页。
Image
Parquet 是一种列式格式,它将数据以行为单位存储!
Image
上图是 Parquet 文件的结构。列仍然作为单独的单元存储,但 Parquet 引入了一种称为行组 (Row Group) 的额外结构。

这种设计使得Parquet能够实现投影下推和谓词下推,这是其高性能的关键。

  • 投影下推:查询只需读取查询中涉及的列,而不是整个行,从而大大减少I/O操作。
  • 谓词下推:Parquet文件包含行组级别的统计信息(如每列的最小值和最大值),查询引擎可以利用这些信息跳过不满足条件的行组。

投影指的是SQL 语言中的SELECT语句——查询需要哪些列。回到之前的例子,我们只需要 Product 和 Country 列,因此引擎可以跳过扫描其余列。

谓词指的是SQL 语言中的WHERE子句——它用于确定哪些行满足查询中定义的条件。在本例中,我们只对 T 恤感兴趣,因此引擎可以完全跳过扫描第 2 行组,因为其中 Product 列的所有值都等于“socks”!

Image

举例说明:假设你有一个包含1亿行数据的销售表,你需要查询"2023年来自北京的客户数量"。使用Parquet格式,查询引擎可以:

  • 只读取"客户地区"和"销售日期"列(投影下推)
  • 跳过所有"销售日期"不在2023年范围内的行组(谓词下推)

结果可能是,它只需要读取总数据量的10%,查询速度自然大幅提升。

高效的压缩与编码技术

Parquet的另一个优势是其出色的压缩能力,这得益于列存储的特性和多种编码技术的应用。

列存储的自然优势

由于同一列中的数据具有相同的数据类型和相似的值,它们比行存储中的混合数据更易压缩。想象一下尝试压缩一本电话簿:按列压缩(所有姓氏在一起)会比按行压缩(姓氏、名字、电话号码交替出现)高效得多。

Parquet使用的编码技术

  1. 字典编码:Parquet为列中的不同值构建字典,然后用字典索引替换实际值。这对于低基数列(如"国家"、"产品类型")特别有效。
  2. 行程长度编码:通过将连续的重复值替换为单个值和计数,进一步压缩数据。
  3. 位打包:对于小范围整数值,使用尽可能少的位数来存储每个值。
字典编码中Parquet 会创建一个包含列中所有不同值的字典,然后将“实际”值替换为字典中的索引值。回到我们之前的例子,这个过程大致如下:
Image

支持的压缩算法

Parquet支持多种压缩算法,让你在压缩比和速度间做出权衡:

  • SNAPPY:提供均衡的压缩比和速度,是常用选择
  • GZIP:提供更高的压缩比,但速度较慢
  • ZSTD:较新的算法,提供良好的压缩比和速度
  • LZ4:注重速度,压缩比相对较低
  • BROTLI:提供高压缩比

这些压缩技术结合使用,通常可以使Parquet文件比等效的CSV文件小 75-90%,不仅节省存储空间,还减少I/O操作,提高查询性能。

Parquet实践:如何读写Parquet文件

了解了理论,让我们看看如何在实践中使用Parquet文件。

使用Python读写Parquet

Python中的Pandas库通过pyarrow或fastparquet后端提供了对Parquet文件的便捷支持。

环境准备:

pip install pandas pyarrow

写入Parquet文件:

import pandas as pd

# 创建示例DataFrame
data = {
'name': ['Alice', 'Bob', 'Charlie'],
'age': [25, 30, 35],
'salary': [70000, 80000, 90000]
}
df = pd.DataFrame(data)

# 将DataFrame写入Parquet文件
df.to_parquet('example.parquet', engine='pyarrow', compression='snappy')

读取Parquet文件:

# 从Parquet文件读取数据
df_parquet = pd.read_parquet('example.parquet', engine='pyarrow')
print(df_parquet)

使用ClickHouse查询Parquet文件

如果你使用ClickHouse,可以直接查询Parquet文件,无需先导入数据:

-- 识别文件架构
DESCRIBETABLEfile('house_prices.parquet')

-- 查询特定数据
SELECT
    toYear(toDate(date)) ASyear,
round(avg(price)) AS price
FROMfile('house_prices.parquet')
WHERE town = 'LONDON'
GROUPBYyear
ORDERBYyearASC

性能优化技巧

为了从Parquet文件中获得最佳性能,考虑以下优化技巧:

  1. 合理设置行组大小:行组大小影响内存使用和读取性能。较大的行组(100万行)可以提高压缩率和读取效率,但需要更多内存。
  2. 选择适当的压缩算法:根据你的需求权衡压缩比和速度。Snappy通常是不错的默认选择,而GZIP适合存储成本是关键因素的场景。
  3. 合并小文件:避免大量小Parquet文件,因为每个文件的元数据都需要单独读取。理想情况下,每个Parquet文件应为几百MB。
  4. 按常用查询列排序:在写入前按常用过滤列对数据排序,可以提高谓词下推的效率。
  5. 明智使用字典编码:对于低基数列,字典编码非常有效,但对于高基数列可能会适得其反。

Parquet的生态系统和未来

Parquet已成为大数据生态系统中的基石技术。它与Apache Spark、Apache Hadoop、Apache Drill等主流数据处理框架无缝集成。此外,更现代的数据湖表格式如Delta Lake、Apache Iceberg和Apache Hudi实际上也是在Parquet之上构建的,为其添加了事务、版本控制和其他管理功能。

随着数据领域的不断发展,Parquet也在进化。最近的改进包括:

  • 支持更高效的编码技术,如字节流分割
  • 增强对嵌套数据类型的能力
  • 与Apache Arrow更紧密的集成,提供高效的内存表示

总结

Parquet之所以能成为大数据存储的默认选择,是因为它巧妙地结合了列式存储、多层结构和高效压缩的优势,提供了无与伦比的查询性能和存储效率。无论你是数据科学家、数据分析师还是数据工程师,理解和掌握Parquet格式都将有助于你构建更高效的数据解决方案。

在这个数据驱动的时代,选择合适的存储格式就像为你的数据选择最合适的家——而Parquet无疑是目前最理想的选择之一。

Image
🏴‍☠️宝藏级🏴‍☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉Python|MySQL|数据分析|数据可视化|机器学习与数据挖掘|爬虫等,从入门到进阶!

长按👇关注- 数据STUDIO -设为星标,干货速递ImageImage