Paimon 在特定场景(如流式 Lookup Join)下,会为了极致的查询性能而引入额外的存储(本地磁盘 LookupFile
)和计算(构建 LookupFile
)开销。但这是一种用一次性的、可控的开销,换取后续持续、高吞吐、低延迟查询的典型权衡。
当把写入、存储、Compaction 和查询的全链路开销综合对比时,Paimon 在其设计的分析型场景中,整体资源消耗和效率往往优于 HBase。
下面我们来进行一个综合的对比分析。
对比维度 |
Paimon (为数据湖分析与流式追加优化) |
HBase (为在线随机读写与强一致性优化) |
结论与权衡 |
写入路径开销 |
轻量级内存追加
|
同步WAL,有序内存写入 1. 写WAL : 每条数据都需 同步写入HDFS上的Write-Ahead Log ,确保单点强持久性,但引入了 高昂的远程I/O 。 2. 内存操作 : 写入MemStore(通常是跳表),需 实时维护内存结构有序 ,存在一定的计算开销和内存碎片风险。 |
Paimon写入效率压倒性优势。 Paimon专为高吞吐流式场景设计,其 “先追加,后排序,批量提交” 的模式,完美规避了远程I/O瓶颈。 HBase的同步WAL机制虽保证了单条写入的强一致性,但在大规模批量写入时,成为了性能的主要瓶颈。 |
最终存储开销 |
极致压缩,元数据极简 采用 Parquet列式存储 ,天然具备极高的压缩比。元数据开销小,仅在文件级别记录统计信息,无冗余。 |
行式存储,元数据冗余 HFile虽为二进制格式,但需为每个单元格(Cell)存储完整的 |
Paimon存储成本显著更优。得益于列存和高效压缩,Paimon的存储占用通常仅为HBase的 1/3甚至更少 。在PB级数据规模下,这意味着 巨大的成本节约 。 |
查询路径与延迟 |
Paimon内存的MemStore和sort buffer溢写的临时文件不可见,需要等待checkpoint提交,因此有一段延迟。
|
通用化、稳定的低延迟设计 1. 查缓存 : 优先访问BlockCache(内存)。 2. 查文件 : 若未命中,通过内存索引快速定位到远程HDFS上的HFile并读取。整个过程对用户透明,提供 普适性的低延迟 保证。 |
各擅胜场,取决于应用模式。 • HBase提供的是 通用的、可预期的低延迟 随机读取能力。 • Paimon则更加“智能”和“场景化”:分析场景下发挥列存优势;高频点查场景下,通过 “预热” 将远程读转化为本地读,实现了 极致的摊销后延迟 。 |
Compaction开销 |
读写放大 合并多个Parquet文件,写入新的Parquet文件。读写单位是文件。 |
读写放大 合并多个HFile,写入新的HFile。读写单位是文件。 |
两者都存在读写放大问题,这是LSM-Tree架构的固有成本。 |
具体问题
写入时
Sort Buffer
和溢写文件的开销需要考虑吗?- 需要,但这是一种内部优化。这个开销是临时的、在本地的。它的目的是将大量无序的写入,在本地磁盘中整理成有序的批次,然后一次性高效地写入最终的列式文件。这避免了对远程存储进行大量的随机小文件写入。相比之下,HBase 的 WAL 是对远程存储的持续写入,开销更大。
Lookup 时重写 Parquet 变为
Lookup File
的开销需要考虑吗?- 需要,但这是一种用空间换时间的查询优化策略。这个开销只发生在特定的
changelog-producer = 'lookup'
或 Flink Lookup Join 场景下。Paimon 的设计哲学是:既然流作业会持续不断地进行 lookup,那么与其每次都去远程读 Parquet(即使有谓词下推,对于点查也不是最高效的格式),不如一次性支付“预处理”成本,在计算节点本地构建一个为点查高度优化的索引文件(LookupFile
)。这个开销一旦支付,后续成千上万次的 lookup 都会受益,整体吞吐量和延迟会远优于重复访问远程存储。
- 需要,但这是一种用空间换时间的查询优化策略。这个开销只发生在特定的
Paimon 依赖 Flink Checkpoint,没有 WAL
- 完全正确!这是 Paimon 架构的一个基石。它将一致性保证的难题交给了 Flink 的分布式快照机制。这使得 Paimon 的写入器 (
MergeTreeWriter
) 无需处理复杂的 WAL 逻辑,极大地简化了设计并消除了 WAL 带来的写入开销。这是它相比 HBase 在写入路径上更轻量级的核心原因之一。
- 完全正确!这是 Paimon 架构的一个基石。它将一致性保证的难题交给了 Flink 的分布式快照机制。这使得 Paimon 的写入器 (
Paimon 和 HBase 空间占用
对比维度 | Paimon (使用 Parquet) | HBase | 空间影响分析 |
---|---|---|---|
1. 存储格式 | 列式存储 (Columnar) | 面向列族的键值存储 | 这是最关键的区别。Parquet 将同一列的数据连续存储,而 HBase 虽叫“列式数据库”,但其物理存储更接近按行(Row Key)组织的、稀疏的、多维度的 Map。 |
2. 数据压缩 | 极高压缩率 | 中等压缩率 | 列式存储把相同类型的数据放在一起,熵值更低,因此压缩效果极好(如字典编码、行程编码 RLE)。HBase 只能对 KeyValue 块进行通用压缩(如 Snappy, Gzip),效果不如列式存储。 |
3. 元数据开销 | 非常低 | 非常高 | Parquet 只需在文件头/尾存储一次列名和类型。而 HBase 为每一个单元格(Cell)都存储了完整的 Key,这个 Key 包含:RowKey + Column Family + Column Qualifier + Timestamp + Type。当列多、RowKey 长时,元数据开销会急剧膨胀,甚至超过数据本身。 |
4. 更新与版本 | LSM-Tree (Copy-on-Write) | LSM-Tree (Append-only) | 两者都通过追加新版本数据和定期 Compaction 来处理更新。Paimon 写新文件,HBase 写新的 KeyValue。在 Compaction 前都会有空间放大,但 HBase 的放大效应因其高元数据开销而更显著。 |
结合代码分析 Paimon 的写入过程
在MergeTreeWriter.java
文件中,flushWriteBuffer
方法是写入流程的核心:
// ... existing code ...
private void flushWriteBuffer(boolean waitForLatestCompaction, boolean forcedFullCompaction)
throws Exception {
if (writeBuffer.size() > 0) {
// ... existing code ...
final RollingFileWriter<KeyValue, DataFileMeta> dataWriter =
writerFactory.createRollingMergeTreeFileWriter(0, FileSource.APPEND);
try {
// 1. 从内存/磁盘排序缓冲中取出数据
writeBuffer.forEach(
keyComparator,
mergeFunction,
changelogWriter == null ? null : changelogWriter::write,
// 2. 交给 dataWriter 写入
dataWriter::write);
} finally {
// ... existing code ...
// 3. 关闭 writer,完成一个 Parquet 文件的生成
dataWriter.close();
}
// 4. 获取新生成文件的元数据
List<DataFileMeta> dataMetas = dataWriter.result();
// ... existing code ...
}
// ... existing code ...
}
// ... existing code ...
这里的 dataWriter
(最终实现是 ParquetWriter
或类似) 就是将内存中的 KeyValue
数据转换并写入成高效的 Parquet 文件的执行者。在这个写入过程中,它会执行:
- 列式转换:将行式数据
KeyValue
拆分成列。 - 编码与压缩:对每一列数据应用最高效的编码(如字典、RLE)和压缩算法。
- 写入文件:将压缩后的列数据、页头、列元数据、文件元数据等组织成一个完整的 Parquet 文件。
这个过程天然地就利用了列存的所有优势来节省空间。
为什么 Paimon/Parquet 能省空间?
- 列存带来的高压缩率:这是最主要的因素。相同类型的数据连续存放,极大提升了压缩算法的效率。
- 极低的元数据开销:相对于 HBase 为每个 Cell 存储一遍长长的 Key,Parquet 的元数据开销几乎可以忽略不计。想象一个有 100 列的表,HBase 会存储 100 次
RowKey + Column Family + Timestamp
,而 Parquet 只在文件元数据中存一次列名。 - 编码优化:Parquet 的字典编码(Dictionary Encoding)和行程编码(Run-Length Encoding, RLE)等技术能进一步压缩数据,特别是对于低基数(重复值多)的列,效果拔群。
因此,“Paimon 写入 Parquet 占用的磁盘空间只有 HBase 的 1/3” 在许多场景下是一个合理且偏保守的估计。尤其是在表宽(列多)、RowKey 和列名较长、数据重复度高的场景下,Paimon/Parquet 的空间优势会更加明显,节省的空间可能会远超 2/3。
综合结论
Paimon 为了实现不同目标所付出的“代价”。
- Paimon 的“开销”是有策略的、可控的投资:无论是写缓冲还是
LookupFile
,都是为了优化整个数据处理链路中的某个环节(通常是 I/O)而引入的。这些开销服务于其作为数据湖存储核心的定位,旨在最大化分析吞吐量和效率。 - HBase 的“开销”是为实现其核心价值的固有成本:WAL 和高冗余的元数据存储是 HBase 实现低延迟、高可靠随机读写的基石,这个成本是持续存在的。
因此,不能简单地说 Paimon 的查找开销比 HBase 大。更准确的说法是:Paimon 为流式分析场景下的高频查找,选择了一次性的构建开销来换取后续的极致性能,而在其他方面(特别是写入和存储)的开销远小于 HBase。
补充HBase 的核心设计
HBase 的核心设计更偏向于 OLTP (在线事务处理),而不是一个原生的 OLAP (在线分析处理) 系统。
HBase 的核心优势:
它的设计目标是基于 RowKey 提供海量数据的毫秒级随机读写。这非常适合需要快速根据唯一 ID(或小范围扫描)来查找、更新或插入单条记录的场景,比如用户画像、订单系统、监控数据存储等。这是典型的 OLTP 特征。
OLAP 的核心需求:
OLAP 通常涉及对大量数据进行的复杂聚合查询(比如
GROUP BY
,SUM
,AVG
)、多维度分析和报表生成。这类查询往往需要扫描表的大部分数据,而不是通过一个 Key 去精确定位。
HBase 的原生 API(Get/Scan)和其存储结构(HFile)并不直接擅长处理这种大规模的聚合扫描。直接在 HBase 上跑复杂的分析查询,性能通常不理想。
为什么会听到 HBase 用于分析的说法
这通常指的是 HTAP (混合事务/分析处理) 场景。人们通过在 HBase 之上架设其他系统来弥补它在分析能力上的短板,最典型的就是 Apache Phoenix。
HBase + Phoenix: Phoenix 在 HBase 之上提供了一个 SQL 层,支持二级索引和复杂的 SQL 查询。这使得 HBase 看上去像一个关系型数据库,能够处理一些分析类的 SQL 查询。它试图在同一份数据上同时满足 OLTP 的快速点查和 OLAP 的分析需求。
然而,即便如此,它与为纯粹 OLAP 设计的系统相比,在处理海量数据复杂分析时仍然有差距。
与 Paimon 的对比
相比之下,Paimon 的定位非常清晰,它就是为 OLAP 而生的数据湖存储格式。
Paimon 本身不执行查询,它负责高效地组织和存储数据。
然后,由 Flink、Spark、Trino/Presto 这类强大的分布式计算引擎在 Paimon 存储的数据之上执行真正的 OLAP 查询。
这种存储计算分离的架构是现代数据分析平台的主流,它让存储层和计算层可以独立优化和扩展,从而在处理超大规模数据的复杂分析时获得极高的性能和灵活性。
总结
特性 |
HBase |
Paimon + 计算引擎 (Flink/Spark...) |
核心定位 |
OLTP / Key-Value 存储 |
OLAP 数据湖存储 |
扩展能力 |
可通过 Phoenix 等扩展为 HTAP |
专为 OLAP 设计的存储底座 |
典型场景 |
在线业务的实时读写 |
数据仓库、数据湖分析、BI 报表 |
架构 |
存储计算耦合 |
存储计算分离 |
所以,HBase 的设计基因是服务在线业务的随机读写,而 Paimon 的基因是服务数据分析的高吞吐写入和高效存储。将 HBase 用于分析是一种能力的扩展和延伸,而 Paimon 则从一开始就处在数据分析的核心赛道上。