在现代数据湖和分析管道中,Apache Parquet 已成为标配的列式存储格式,但传统 Java 解析器如 parquet-java 依赖 Hadoop 栈且单线程,难以发挥多核 CPU 潜力。Hardwood 作为全新实现,以 Java 21 为目标,提供零 Hadoop 依赖的高性能解析,其核心在于页级(page-level)多线程解码和自适应预取机制,实现最小内存占用下的高吞吐。

Parquet 文件结构为行组(row group)→ 列块(column chunk)→ 数据页(data page),每个页包含值、重复 / 定义级别(RL/ DL)和压缩元数据。Hardwood 的页级并行主义将单个列块的页解码任务分发至多个 worker 线程,利用所有 CPU 核心,而非粗粒度的行组或文件级并行。这避免了列间同步开销,并通过 memory-mapped I/O 最小化拷贝。证据显示,在 NYC 黄出租车数据集(9.2 GB,650M 行,平坦 schema),使用 ColumnReader 多文件模式,仅求和 3 列即达 580M 行 / 秒(M3 Max 16 核,1.12 秒平均)。

自适应预取是另一关键:监控各列页解码速率,动态为慢列(如复杂类型)分配更多预取资源,确保所有列同步推进。同时,跨文件预取(cross-file prefetching)在处理文件 N 末尾时提前映射文件 N+1,消除文件切换 stall。Overture Maps POI 文件(900 MB,9M 行,嵌套 schema)全列解析仅需 1.27 秒(7M 行 / 秒),证明了机制在嵌套场景的有效性。

工程化落地参数与配置

  1. 依赖引入(Maven 示例,最小 deps)

    <dependency>
      <groupId>dev.hardwood</groupId>
      <artifactId>hardwood-core</artifactId>
      <version>1.0.0.Alpha1</version>
    </dependency>
    <!-- 可选压缩:Snappy -->
    <dependency>
      <groupId>org.xerial.snappy</groupId>
      <artifactId>snappy-java</artifactId>
    </dependency>
    

    使用 BOM 统一版本管理,避免版本冲突。

  2. JVM 优化(Java 21+,推荐 25)

    参数 作用
    --add-modules jdk.incubator.vector - 启用 Vector API SIMD 加速解码(如 null 计数、字典查找),256-bit AVX2 下提速显著
    --enable-native-access=ALL-UNNAMED - libdeflate 加速 GZIP(Java 22+,需系统安装 libdeflate-dev)
    -Dhardwood.simd.disabled=false 默认 确保 SIMD 启用
    -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints 可选 JFR 详细追踪
  3. 线程池与读取模式

    • 共享线程池:Hardwood hardwood = Hardwood.create(); 默认线程数 ≈ availableProcessors () - 1。
    • 高吞吐首选 ColumnReader:返回 primitive 数组(如 double[]),支持 JIT 向量化,避免 per-row 虚调用。 示例(多文件列求和):
      try (Hardwood h = Hardwood.create();
           MultiFileParquetReader pr = h.openAll(files);
           var cols = pr.createColumnReaders(ColumnProjection.columns("col1", "col2"))) {
        var col1 = cols.getColumnReader("col1");
        double sum = 0;
        while (col1.nextBatch()) {
          double[] vals = col1.getDoubles();
          BitSet nulls = col1.getElementNulls();
          for (int i = 0; i < col1.getRecordCount(); ++i) {
            if (nulls == null || !nulls.get(i)) sum += vals[i];
          }
        }
      }
      
    • 投影(projection):ColumnProjection.columns("col1") 跳过无关列,I/O/ 解码节省 50%+。
  4. 调优参数清单

    参数 默认 / 推荐 场景
    线程数 CPU-1 大文件多核
    批次大小(batch size) 内部动态,~64K 值 / 批 调大减开销
    预取深度(prefetch depth) 自适应,监控 JFR 慢列设高(SSD vs HDD)
    文件上限 / 文件 2GB 大数据集拆分多文件

监控与回滚策略

集成 JDK Flight Recorder(JFR)追踪关键指标:

  • Prefetch misses:高则增预取深度或优化存储。
  • Page decoding times:列间不均 → 检查压缩 / 编码。
  • Thread pool starvation:增线程或减并发。

示例启用:-XX:StartFlightRecording=duration=60s,filename=hardwood.jfr

风险限:Alpha 版无谓推(predicate pushdown),平坦 schema 最优;嵌套 / 大文件 (>2GB / 文件) 需多文件。回滚:引入 hardwood-parquet-java-compat 兼容 parquet-java API,无需改代码。

通过上述配置,Hardwood 在无额外 deps 下实现 parquet-java 数倍吞吐,适用于 ETL、ML 特征工程等场景。

资料来源