趋近智
磁盘上的数据存储是线性的。无论您表的逻辑结构如何,底层对象存储系统,无论是Amazon S3、Azure Blob Storage还是Google Cloud Storage,都将数据序列化为连续的字节流。数据湖的效率很大程度上由您如何将二维数据库表(行和列)映射到这个一维字节流的方式决定。
这种映射要求在写入性能和读取性能之间进行权衡。在数据工程中,我们通常将文件格式分为两种不同的架构:行式和列式。弄清这两种格式的机械差异对设计高效的数据湖是必需的,尤其是在从原始数据摄取(Bronze层)到精炼分析(Silver/Gold层)的过程中。
在行式格式中,数据按记录顺序存储。如果您的表包含 user_id、timestamp 和 event_type 列,文件会写入第一行的所有字段,紧接着是第二行的所有字段。
行式格式的常见例子包括CSV、JSON(换行符分隔)和Apache Avro。
想想一个包含用户交易日志的数据集。在基于行的布局中,磁盘上的字节看起来像这样:
这种结构对于写入操作非常高效。当应用程序生成新记录时,系统只需将新行追加到文件末尾。这种“仅追加”模式最大限度地减少了磁盘寻道,并与事务系统(OLTP)或流式摄取管道的写入模式很吻合。
然而,这种布局会给分析查询(OLAP)带来较大的开销。分析通常涉及对大量记录中的特定指标进行聚合。假设您想从上述数据集中计算平均 Amount。要获取 Amount 值,查询引擎必须读取整个文件,包括 ID 和 Date 字段,因为 Amount 字节与其他数据交错存储。
如果一个表有100列,而您的查询只需要其中的3列,行式格式会强制引擎读取和解析97列不相关的数据。这导致高I/O放大,即从存储中读取的数据量远超实际计算所需的数据量。
逻辑表如何序列化为物理存储块的对比。行式存储将同一记录的字段保持在一起,而列式存储则将同一列的值归类。
列式存储改变了数据布局。系统不是一行接一行地写入,而是按列归类值。所有 user_id 值连续存储,接着是所有 timestamp 值,然后是所有 event_type 值。
列式格式的常见例子包括Apache Parquet和Apache ORC。
在列式布局中,字节流看起来像这样:
这种架构促成了一种称为**投影下推(projection pushdown)**的技术。当用户运行 SELECT AVG(Amount) FROM transactions 这样的查询时,查询引擎可以直接跳转到 Amount 列块开始的字节偏移量。它只读取该块,并完全跳过 ID 和 Date 块。
对于数据湖中常见的宽表(通常包含数百列),这种I/O减少效果明显。如果您从100列中选择5列,您实际上只读取了5%的数据。由于网络带宽和磁盘I/O通常是分布式处理中的瓶颈,减少数据量直接带来更快的查询执行和更低的云出口成本。
除了I/O修剪之外,列式存储还提供卓越的压缩比。压缩算法通过识别数据中的重复模式来工作。在行式格式中,当您遍历文件时,数据类型会不断变化(例如:整数、字符串、日期、整数...)。这种异构性使得编解码器难以发现模式。
在列式文件中,相邻数据总是相同类型。整数与整数存储在一起,字符串与字符串存储在一起。这种同构性促成了轻量级、高效编码方案的使用。
例如,设想一个 country 列,其中值“United States”连续出现1,000次。列式格式可以使用行程编码(RLE)将其存储为一个元组:("United States", 1000)。与存储1,000次字符串相比,这占据的空间可以忽略不计。我们将在“Apache Parquet的内部构造”部分中介绍RLE和字典编码等具体编码细节。
为了做出明智的架构决定,我们必须量化 (quantization)这些格式的性能影响。让我们分析读取使用模式。
定义的变量:
扫描复杂性对比:
行式扫描: 要提取 列,系统会为每行 读取完整的行大小 。
列式扫描: 要提取 列,系统仅读取对应这些列的行大小一部分。为简化起见,假设列大小均匀:
随着比率 的减小(即查询对列的选择性增强),行式存储和列式存储之间的性能差距会扩大。
相对数据扫描需求。列式存储的性能与选择的列数成反比,而行式存储的成本与投影无关,保持不变。
在现代数据湖架构中,您很少会只选择一种格式。而是根据数据管道的阶段来选择格式。
在以下情况使用行式(Avro/JSON):
在以下情况使用列式(Parquet/Iceberg):
Medallion架构中的标准模式是,先将数据存放为行式格式以确保捕获的可靠性,然后运行ETL作业将数据转换并重写为列式格式供下游消费者使用。这种方法兼顾了行式日志的持久性和列式分析的性能。
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•