3510 字
18 分钟
深入理解Zarr:云原生时代的大规模科学数据存储与分析利器

在当今数据驱动的时代,我们处理的数据规模正在以前所未有的速度增长。从气候模拟、卫星遥感影像到基因组学,科学数据的体量常常达到 TB 甚至 PB 级别。数据密集型计算分析工作流程的最大瓶颈往往不在于代码或硬件,而在于数据的存储方式。数据存储格式的选择直接影响着性能、可扩展性和协作效率。无论是处理气候模型、卫星图像还是大规模机器学习数据集,数据结构都可能决定工作的边界。

为了应对这些挑战,我们需要一种全新的数据存储方案。但在介绍 Zarr 之前,我们有必要先了解一下那些服务了科学计算数十年的传统格式,以及它们在当今云时代所面临的困境。

传统存储格式的挑战:NetCDF/HDF5 的局限性#

长期以来,NetCDF 和 HDF5 等格式一直是存储多维科学数据的黄金标准。它们非常出色,能够将数据、元数据、维度和坐标信息打包在一个可移植的、自描述的单文件中。对于在本地硬盘上进行数据存储和分析的场景,这套方案堪称完美。

然而,随着数据规模的爆炸式增长和计算范式向云端迁移,这种基于单体文件的设计开始暴露其固有的局限性,尤其是在数据分析和可视化所需的高效、快速读取方面:

1. 并发访问的瓶颈#

这些格式的核心设计是“一个文件”。当多个进程或计算节点尝试同时向这一个文件写入数据时,为了防止数据损坏,必须引入文件锁(File Locking)机制。这种机制严重限制了并行写入的性能,使其无法充分利用现代分布式计算集群的威力。

2. 云端对象存储的“水土不服”#

云存储(如 AWS S3)本质上是对象存储,而非传统的文件系统。它对整个对象的读(GET)和写(PUT)操作进行了高度优化。当您想更新一个存储在 S3 上的 100GB HDF5 文件中的一小部分数据时,您无法原地修改。您必须:

  • • 将整个 100GB 的文件下载下来。
  • • 在内存中修改这部分数据。
  • • 再将修改后的整个 100GB 文件上传回去,覆盖旧文件。 这是一个极其缓慢且成本高昂的“读-改-写”过程,完全违背了云存储的设计哲学。

3. 分析与可视化读取效率低下#

数据分析和可视化常常需要快速读取数据的某个切片(Slice),例如一张地图的特定区域或一个时间序列的某个时间段。虽然 NetCDF/HDF5 内部支持分块,但由于所有数据都封装在一个单体文件中,客户端仍然需要解析复杂的文件头和元数据结构来定位数据。在网络环境中,这种延迟积少成多,使得交互式分析和动态可视化变得非常缓慢,用户体验不佳。

Zarr 正是在这样的背景下应运而生——一个简单而强大的开源、云原生协议,专为存储分块压缩的 N 维数组而设计。

Zarr 核心概念#

1. 什么是 Zarr?#

Zarr 是一个用于存储分块(Chunked)、压缩 N 维数组的开源规范。它主要用于存储多维数组数据集,如时间、空间或其他变量的测量值,以 N 维数组的形式组织。您可以将其理解为一种专为数组数据设计的。它从根本上摒弃了单体文件的设计,通过一种巧妙的方式组织数据,使其在云环境中能够被大规模并行读写,从而成为现代分布式计算框架(如 Dask、Spark 和 Ray)的理想搭档。

一个生动的比喻是:“Zarr 是数组数据的 Parquet”。Parquet 是针对表格数据优化的列式存储格式,而 Zarr 是针对多维数组数据优化的分块存储协议。两者都高效、压缩,专为分析和可扩展访问模式设计,特别适用于云端和分布式环境。

2. 核心设计原则#

Zarr 专门为张量数据而设计——多维数组是标量(0D)、向量(1D)和矩阵(2D)向更高维度的泛化,广泛用于机器学习和科学计算中的复杂结构化数据表示。

Zarr 规范的核心是将数据存储在 chunks(块)中——大型数组的小型、可管理的片段,可以独立读写。每个块都被压缩以节省空间,整个数据集以分层目录结构组织,配有简单的 JSON 元数据文件。

Zarr 的核心特性:

  • • 灵活性:支持本地文件系统、Amazon S3 或 Google Cloud Storage 等云对象存储,以及分布式文件系统
  • • 高效性:支持快速并行 I/O,适合 Dask、Ray、Spark 或 Beam 等分布式计算工具
  • • 自描述性:元数据直接嵌入数据旁边,每个数组携带自身信息,如维度、数据类型或自定义属性
  • • 开放性:开放规范、不断发展的生态系统和社区治理,不依赖任何单一供应商或平台

Zarr 数据存储结构#

Zarr 的核心在于其分层和分块的数据结构。一个 Zarr 数据集本质上是一个层次化的目录(或对象存储前缀),其中包含元数据文件和数据块文件。

其结构主要包含两个核心概念:

1. 分块 (Chunks)#

Zarr 会将一个庞大的 N 维数组切分成许多个小的、独立的“块”。例如,一个 (10000, 10000) 的二维数组可以被切分成一百个 (1000, 1000) 大小的块。每个块都经过独立的压缩,并作为一个单独的对象进行存储。

2. 存储与元数据 (Stores & Metadata)#

Zarr 使用一个灵活的“存储”(Store)接口来管理这些数据块,该接口本质上是一个键/值存储。

  • • 键 (Key) 是一个字符串,代表了数据块在数组中的位置,例如 c/0/1
  • • 值 (Value) 是对应数据块的二进制字节流。
  • • 元数据则以 .json 文件的形式存储。例如,.zarray 文件描述了数组的整体信息(如维度、块大小、数据类型和压缩器),而 .zgroup 文件则用于将多个数组组织在一起。

这种结构意味着,一个 Zarr 数据集在本地文件系统上是一个目录,而在 S3 等对象存储上则是一个共享相同前缀的对象集合。

为什么 Zarr 是”云原生”格式?#

Zarr 的“云原生”特性源于其将大数组分解为独立对象(数据块)的设计哲学。这与云对象存储的工作模式完美契合,并带来了几个关键优势:

  • • 避免并发写入冲突:由于每个块都是一个独立的对象,多个进程或节点可以同时向不同的数据块写入数据,完全无需锁定,实现了真正的并行 I/O。
  • • 高效的部分读取:当您只需要访问数组的一小部分时,Zarr 只需读取包含相关数据的特定块(对象)。这避免了下载和解析整个庞大文件的开销,极大地提升了数据切片(Slicing)的性能,对于交互式分析和可视化至关重要。
  • • 与 HTTP 协议的亲和性:云对象存储的 API 基于 HTTP。Zarr 的每个块都可以通过一个简单的 HTTP GET 请求来获取,这使得它能够轻松地与 Web 服务和分布式计算系统集成。

Python API应用#

如果您熟悉 NumPy,那么使用 Zarr 会感觉非常自然。核心 Python API 提供类似的基于数组的接口,并增加了对数据存储和访问方式的控制。

1. 基础操作示例#

import numpy as np
import zarr
# Create a 2D array with shape (100, 100), chunked in 10x10 blocks
z = zarr.open('zarr-data', mode='w', shape=(100, 100), chunks=(10, 10), dtype='f4')
# Fill the array with a default value
z[:] = 0
# Create a 10x10 block of data
data = np.ones((10, 10), dtype='f4') * 99
# Write to a slice that spans multiple chunks
z[5:15, 5:15] = data
# Read back the same slice
# Zarr handles chunking transparently; you can slice across chunk boundaries
print(z[5:15, 5:15])   # This reads data spanning multiple 10x10 chunks
[[99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]
 [99. 99. 99. 99. 99. 99. 99. 99. 99. 99.]]

2. 分组管理示例#

# 创建分层数据结构
root = zarr.open_group('scientific_data.zarr', mode='w')
# 创建不同的数据组
temperature = root.create_group('temperature')
humidity = root.create_group('humidity')
# 在组中创建数组
temp_data = temperature.create_dataset('daily',
                                      shape=(365, 100, 100),
                                      chunks=(30, 50, 50))
humidity_data = humidity.create_dataset('hourly',
                                       shape=(8760, 100, 100),
                                       chunks=(240, 50, 50))

Zarr 生态系统工具详解#

1. Xarray:高级标记数组接口#

Xarray 提供与 Zarr 无缝协作的高级标记数组接口,是处理大型多维数据集的理想工具:

import xarray as xr
import numpy as np
# 创建带标签的数据集
data = np.random.random((365, 180, 360))
ds = xr.Dataset({
    'temperature': (['time', 'lat', 'lon'], data)
}, coords={
    'time': pd.date_range('2023-01-01', periods=365),
    'lat': np.linspace(-90, 90, 180),
    'lon': np.linspace(-180, 180, 360)
})
# 写入 Zarr
ds.to_zarr('climate_data.zarr')
# 读取并进行懒加载
ds_loaded = xr.open_zarr('climate_data.zarr')

2. Dask:并行计算支持#

Dask 为 Zarr 带来并行性和可扩展性:

import dask.array as da
import zarr
# 创建大型 Dask 数组
x = da.random.random((100000, 100000), chunks=(10000, 10000))
# 写入 Zarr
zarr.save_array('large_array.zarr', x)
# 读取为 Dask 数组
y = da.from_zarr('large_array.zarr')
# 执行分布式计算
result = y.mean(axis=0).compute()

应用场景#

1. 气候与地球科学#

被广泛用于存储卫星观测、大气海洋模型输出和遥感反演数据产品。目前,欧洲空间局正在将整个 Sentinel 卫星档案迁移到 Zarr 格式。

2. 生物成像与生命科学#

OME-Zarr 正在成为存储多分辨率显微镜和 3D 成像数据的新兴标准,被 EMBL 和 Chan Zuckerberg Initiative 等领先机构采用。

3. 基因组学#

用于存储大规模测序数据集,包括参考基因组比对和高维基因表达矩阵。

4. 机器学习#

在大规模机器学习管道中实现训练数据的高效流式传输,与 Dask 和 PyTorch 等工具无缝集成。

什么时候应该使用 Zarr?#

Zarr 是解决特定挑战的理想工具。您应该在以下场景中优先考虑使用 Zarr:

  • • 处理大于内存的 N 维数组:当您的数组无法一次性载入内存时,Zarr 的分块机制让您能够高效地对数据进行核外(out-of-core)处理。
  • • 需要对大型数组进行快速切片:例如,在时间序列分析或地理空间数据处理中,如果您需要频繁地访问数据立方体的特定时间点或空间区域,Zarr 的性能会远超传统格式。
  • • 在云端进行并行计算:当您的工作流构建在 AWS、Google Cloud 或 Azure 之上,并且使用 Dask、Spark 等框架进行分布式计算时,Zarr 是实现高性能数据访问的首选格式。

性能优化策略#

虽然 Zarr 的分块架构本身就提供了显著的性能优势,但要在生产环境中实现最佳性能,还需要根据具体的数据访问模式和计算需求进行精心优化。合理的分块策略、压缩配置和存储参数调优可以将 Zarr 的性能提升几个数量级。

分块大小的选择是影响性能的关键因素——过小的块会增加元数据开销和网络请求次数,过大的块则会降低并行度并增加内存使用。

压缩算法的选择需要在压缩率和解压速度之间找到平衡点,不同的数据类型和访问模式需要不同的优化策略。此外,在云环境中,还需要考虑网络延迟、带宽限制和存储成本等因素。

以下是一些经过实战验证的优化技巧,可以帮助您在不同场景下发挥 Zarr 的最大潜力:

1. 分块策略#

# 根据访问模式优化分块
# 时间序列访问:时间维度较小的块
time_optimized = zarr.open_array('timeseries.zarr',
                                shape=(8760, 1000, 1000),
                                chunks=(24, 500, 500))  # 24小时块
# 空间分析:空间维度较大的块
spatial_optimized = zarr.open_array('spatial.zarr',
                                   shape=(365, 3600, 7200),
                                   chunks=(1, 1800, 3600))  # 半球块

2. 压缩配置#

import zarr
from numcodecs import Blosc
# 配置高效压缩
compressor = Blosc(cname='lz4', clevel=5, shuffle=Blosc.BITSHUFFLE)
z = zarr.open_array('compressed_data.zarr',
                   mode='w',
                   shape=(10000, 10000),
                   chunks=(1000, 1000),
                   dtype='f4',
                   compressor=compressor)

最后#

Zarr 不仅是存储规范,更正在成为跨行业数据的共享语言。从地球观测到生物成像再到机器学习,Zarr 实现了更具互操作性、面向未来的工作流程。行星级数据的未来是分块的、云优化的和开放的。

 

深入理解Zarr:云原生时代的大规模科学数据存储与分析利器
https://blog.scidatalab.net/posts/深入理解zarr-云原生时代的大规模科学数据存储与分析利器/
作者
Echo
发布于
2025-09-07
许可协议
CC BY-NC-SA 4.0