Skip to content

Configuration

Faster GZIP with libdeflate (Java 22+)

Hardwood can use libdeflate for GZIP decompression, which is significantly faster than the built-in Java implementation. This feature requires Java 22 or newer (it uses the Foreign Function & Memory API which became stable in Java 22).

Enabling libdeflate is two steps: a JVM flag to allow native access, plus the native library installed on the system.

1. JVM flag (allow Hardwood to bind native functions):

--enable-native-access=ALL-UNNAMED

2. Native library — install on your system:

brew install libdeflate
apt install libdeflate-dev
dnf install libdeflate-devel
vcpkg install libdeflate

Or download from GitHub releases.

When libdeflate is installed and available on the library path, Hardwood will automatically use it for GZIP decompression. To disable libdeflate and use the built-in Java implementation instead, set the system property:

-Dhardwood.uselibdeflate=false

SIMD Acceleration with Vector API (Java 22+)

Hardwood can use the Java Vector API (SIMD) to accelerate certain decoding operations like counting non-null values, marking nulls, and dictionary lookups. This feature requires Java 22 or newer and is enabled automatically when available.

To enable the Vector API incubator module, add this JVM argument:

--add-modules jdk.incubator.vector

When SIMD is available and enabled, you'll see an INFO log message at startup:

SIMD support: enabled (256-bit vectors)

The vector width depends on your CPU (128-bit for SSE/NEON, 256-bit for AVX2, 512-bit for AVX-512).

SIMD engages only when the incubator module is added, so to run scalar operations (for debugging or comparison) simply omit the --add-modules jdk.incubator.vector argument.

JFR (Java Flight Recorder) Events

Hardwood emits JFR events during file reading, enabling detailed performance profiling with zero overhead when recording is off. Start a JFR recording to capture them:

java -XX:StartFlightRecording=filename=recording.jfr,settings=profile ...

Or attach dynamically via jcmd <pid> JFR.start.

Available events:

Event Category Description
dev.hardwood.FileOpened I/O File opened and metadata read. Fields: file, fileSize, rowGroupCount, columnCount
dev.hardwood.FileMapping I/O Memory-mapping of a file region. Fields: file, offset, size
dev.hardwood.RowGroupScanned Decode Page boundaries scanned in a column chunk. Fields: file, rowGroupIndex, column, pageCount, scanStrategy (sequential or offset-index)
dev.hardwood.PageDecoded Decode Single data page decoded. Fields: column, compressedSize, uncompressedSize
dev.hardwood.RowGroupFilter Filter Row groups dropped by statistics/bloom predicate pushdown. Fields: file, totalRowGroups, rowGroupsKept, rowGroupsSkipped, rowGroupsFullyMatching
dev.hardwood.RowGroupByteRangeFilter Filter Row groups selected by a byte-range split predicate (split-aware reading). Fields: file, totalRowGroups, rowGroupsKept, rowGroupsSkipped
dev.hardwood.PageFilter Filter Pages filtered by Column Index predicate pushdown, one event per column chunk considered. Fields: file, rowGroupIndex, column, totalPages, pagesKept, pagesSkipped
dev.hardwood.RecordFilter Filter Records filtered by record-level predicate evaluation, one event per file read. Fields: file, totalRecords, recordsKept, recordsSkipped
dev.hardwood.BatchWait Pipeline Consumer blocked waiting for the assembly pipeline; the event's duration is the stall. Fields: column

Events appear under the Hardwood category in JDK Mission Control (JMC) or any JFR analysis tool. Use them to identify:

  • I/O bottlenecks — large FileMapping durations
  • Filter effectivenessRowGroupFilter shows how many row groups were dropped by statistics/bloom pushdown and RowGroupByteRangeFilter how many were excluded by split selection; PageFilter shows how many of a column chunk's pages survived the Column Index within a row group that was kept, with no event for a column chunk the Column Index did not narrow — including a chunk that has no Column Index, whose pages are instead dropped from page-header statistics and are not reported; RecordFilter shows how many individual records the predicate decided on per file once that pruning is done
  • Decode hotspotsPageDecoded events with large uncompressed sizes or high frequency
  • Pipeline stallsBatchWait events indicate the reader is waiting for decoded data

Reader Options

Read-time behaviour is configured with an immutable ReaderConfig, passed to ParquetFileReader.open(...). It is separate from HardwoodContext, which holds the shared runtime resources (the decode thread pool and native decompression pools): a single context can back reads with different ReaderConfigs, so a behaviour knob never forces a fresh thread pool.

import dev.hardwood.HardwoodContext;
import dev.hardwood.reader.ParquetFileReader;
import dev.hardwood.reader.ReaderConfig;

ReaderConfig config = ReaderConfig.builder()
        .option("hardwood.fixed-list-fast-path", "true")
        .build();

try (HardwoodContext context = HardwoodContext.create();
     ParquetFileReader reader = ParquetFileReader.open(inputFile, context, config)) {
    // ...
}

Obtain the defaults with ReaderConfig.defaults(). The open(inputFile) and open(inputFile, context) overloads use the defaults.

Write-time behaviour is configured separately, with a WriterConfig passed to ParquetFileWriter.create(...) — see the Writer Reference.

Options are string-keyed and keys are matched case-sensitively; boolean option values are compared case-insensitively. An unrecognised key is ignored (so a transitional flag can be retired without breaking callers) but logged at WARNING, so a typo in a live key surfaces rather than silently taking the default.

Option Default Description
hardwood.fixed-list-fast-path false Set to "true" to decode fixed-size LIST columns (e.g. embedding vectors, where every row holds the same number of non-null elements) without reconstructing per-row definition and repetition levels. Off by default, so every column takes the general nested-decode path unless the option is enabled.
hardwood.metadata-filtering true Set to "false" to disable metadata-based filtering: no row groups or pages are skipped from min/max statistics, bloom filters, dictionary pages, or page indexes, and filter predicates are instead evaluated against every decoded row. Filtered results then depend only on the data pages, for files whose footer or page-index metadata is unreliable.

System Properties Reference

Property Default Description
hardwood.uselibdeflate true Set to false to disable libdeflate for GZIP decompression