基于R语言理解Parquet文件格式

Apache Parquet 是流行列存储文件格式,在Hadoop生态中被广泛使用,如Pig, Spark, 和 Hive。它是独立于语言的二进制格式,扩展名为.parquet,用于高效大数据集存储。本文主要介绍parquet格式如何实现高效数据存储。

列存储

parquet特征

  • 跨平台,很多系统支持并使用的文件格式
  • 按列布局存储数据,存储元数据

后者支持有效存储、查询数据。

假设有下列数据:

tibble::tibble(id = 1:3, 
              name = c("n1", "n2", "n3"), 
              age = c(20, 35, 62))

# A tibble: 3 x 3
#     id name    age
#  <int> <chr> <dbl>
#1     1 n1       20
#2     2 n2       35
#3     3 n3       62

如果存储为CSV文件,在R终端中看到的是文件存储格式的镜像,为行存储,可有效实现文件查询,如:

SELECT * FROM table_name WHERE id == 2

只要定位到第二行并返回数据,另外追加行到数据集也很方便,仅需要在文件结尾增加一行。但如果想汇总age列数据,那么可能是低效的,因为需要变量每一行并确定那个值是age,再返回。

parquet使用列存储,按列布局,列数据按顺序存储。

1 2 3
n1 n2 n3
20 35 62

使用该布局,执行下面查询也是不方便,但如果需要汇总所有age,则仅需要简单汇总第三行。

SELECT * FROM dd WHERE id == 2

读写parquet文件

在R中,读写parquet文件需要使用arrow包:

# install.packages("arrow")
library("arrow")
packageVersion("arrow")
#> [1] '6.0.1'

创建parquet文件,需要使用write_parquet()函数:

# Use the penguins data set
data(penguins, package = "palmerpenguins")

# Create a temporary file for the output
parquet = tempfile(fileext = ".parquet")
write_parquet(penguins, sink = parquet)

读文件使用read_parquet()。使用parquet文件格式其中一个优势是文件占用控件较小,当考虑云存储成本时,应对大数据集时该优势尤为重要。

减少文件大小可通过两种方法实现:

  • 文件压缩:可以通过给write_parquet()方法的参数compression设置压缩算法,默认为snappy。
  • 巧妙的输出存储方式,即编码方式。

Parquet 编码

既然parquet使用列存储,则相同类型值被存储在一起。这打开了优化技巧的世界,当数据按行存储时是不支持的,例如CSV文件。下面介绍列存储中常用的编码。

行程编码(Run length encoding)

假设列仅包含单个值(每行数据一样),则可以仅存储"值重复N次",而不是像CSV格式中重复存储相同的值。这意味着当N很大时,存储所需空间很小。如果列有多个值,我们可以使用查找表。在parquet中这称为行程编码,列如下面列:

c(4, 4, 4, 4, 4, 1, 2, 2, 2, 2)
#>  [1] 4 4 4 4 4 1 2 2 2 2

则会存储:

  • 4 重复5次
  • 1 重复1次
  • 2 重复4次

下面通过简单示例演示该过程,字符A在数据框中重复多次:

x = data.frame(x = rep("A", 1e6))

然后分别保存为parquet和csv格式进行对比:

library(arrow)
parquet = tempfile(fileext = ".parquet")
csv = tempfile(fileext = ".csv")

arrow::write_parquet(x, sink = parquet, compression = "uncompressed")
readr::write_csv(x, file = csv)

使用fs包进行对比:

# Could also use file.info()
fs::file_info(c(parquet, csv))[, "size"]
#> # A tibble: 2 × 1
#>          size
#>   <fs::bytes>
#> 1        1014
#> 2       1.91M

结果显示parquet文件很小,CSV文件将近2M,文件空间减少了500倍。

字典编码(Dictionary encoding)

假设存储下面字符向量:

c("Jumping Rivers", "Jumping Rivers", "Jumping Rivers")
#> [1] "Jumping Rivers" "Jumping Rivers" "Jumping Rivers"

保存上面数据,可以简单使用0代替"Jumping Rivers",使用map映射两者关系,可以有效减少存储空间,特别对于大向量。

x = data.frame(x = rep("Jumping Rivers", 1e6))
arrow::write_parquet(x, sink = parquet)
readr::write_csv(x, file = csv)
fs::file_info(c(parquet, csv))[, "size"]
#> # A tibble: 2 × 1
#>          size
#>   <fs::bytes>
#> 1       1.09K
#> 2      14.31M

增量编码(Delta encoding)

增量编码典型用于连续时间戳。时间一般使用Unix时间表示,即从1970-01-01开始的秒数,存储方式对于人类不友好,一般需要格式化之后再展示,举例:

(time = Sys.time())
#> [1] "2022-03-16 17:47:36 GMT"
unclass(time)
#> [1] 1647452856

如果列有大量时间戳数据,为了减少存储空间,可以给所有列减去列中的最小值,举例:

c(1628426074, 1628426078, 1628426080)
#> [1] 1628426074 1628426078 1628426080

采用增量,仅需要这样存储,以1628426074为基数的偏移量:

c(0, 4, 6)
#> [1] 0 4 6

其他编码

RDS是R语言支持的文件格式,使用readRDS()/saveRDS() 和 load()/save()函数进行读写。RDS主要优势能够存储R对象——环境、list、函数等。如果仅对矩形数据框,如data frame,那么使用RDS的理由:

  • 文件格式已经存在很久,不会可能改变,因此向后兼容
  • 不依赖任何外部包,仅Base R

使用parquet优势为:

  • 文件大小相对较小,如果需要压缩,可在write_parquet()中设置compression = “gzip”。一般来说parquet文件节省空间,对一些场景,节省%5也非常值得。

本文介绍了parquet文件格式,以及R对其读写支持。同时还介绍了几种编码方式,从而加深理解为什么parquet文件占用空间较小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值