Skip to content

fix(parquet): use signed comparison for BYTE_ARRAY decimal statistics - #11087

Open
yuefdev wants to merge 1 commit into
apache:mainfrom
yuefdev:fix-byte-array-decimal-stats
Open

yuefdev wants to merge 1 commit into
apache:mainfrom
yuefdev:fix-byte-array-decimal-stats

Conversation

@yuefdev

@yuefdev yuefdev commented Sep 14, 2026

Copy link
Copy Markdown

Which issue does this PR close?

Rationale for this change

ByteArrayEncoder, used by the arrow writer for Binary, Utf8 and the other byte array types, computes min/max using plain byte order. For a BYTE_ARRAY column annotated as DECIMAL the values are big-endian two's complement, so the statistics come out as if they were unsigned: writing -1, 0, 1 gives min 0x00 and max 0xFF.

The non-arrow writer and the FixedSizeBinary path already compare decimals with compare_greater_byte_array_decimals, which is why only this path was affected.

What changes are included in this PR?

  • ByteArrayEncoder now notes whether the column is a decimal (logical or converted type) and, if so, uses compare_greater_byte_array_decimals when computing and merging min/max. Everything else keeps the plain byte comparison.
  • compute_min_max takes the comparison as a generic parameter, so there's no extra per-value branch. compare_greater_byte_array_decimals is now pub(crate).

Are these changes tested?

Yes. test_byte_array_decimal_statistics writes -1, 0, 1 from a BinaryArray into a BYTE_ARRAY DECIMAL(2, 0) column. It fails on main (min is [0] instead of [255]) and passes with this change. The reproducer from the issue now reports (-1, 1) for all three paths.

Ran locally on Windows with Rust 1.98.1:

  • cargo test -p parquet: all passing
  • cargo test -p parquet --all-features --no-fail-fast: lib (1436) and all integration suites pass. parquet-fromcsv's test_command_help fails, but it fails on main too here (CRLF checkout of the help text via core.autocrlf). The all-features doc tests wouldn't build on this machine (stale artefacts in target/ and a pagefile limit), so I'm leaving those to CI.
  • cargo clippy -p parquet --all-targets --all-features -- -D warnings and cargo fmt --all -- --check: clean

Are there any user-facing changes?

Statistics for BYTE_ARRAY decimal columns written through the arrow writer are now correct. No API changes.

AI assistance was used for the fix, the test and this description.

@github-actions github-actions Bot added the parquet Changes to the parquet crate label Sep 14, 2026
ByteArrayEncoder computed min/max with plain byte order, so decimals
written from a byte array to a BYTE_ARRAY DECIMAL column got unsigned
statistics (-1, 0, 1 gave min 0 and max -1). Use the same signed
comparison as the generic column writer for decimal columns.

Closes apache#11073
@yuefdev
yuefdev force-pushed the fix-byte-array-decimal-stats branch from c15a048 to 5c59f2a Compare September 14, 2026 23:38
@Jefffrey

Copy link
Copy Markdown
Contributor

@neilconway do you have time to help look at this, since you worked on something similar recently

@etseidl

etseidl commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

run benchmark arrow_writer

env:
  BENCH_FILTER: string

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5692059386-2388-nqsst 6.12.94+ #1 SMP Tue Aug 4 08:44:15 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing fix-byte-array-decimal-stats (5c59f2a) to fbdbf93 (merge-base) diff

Run configuration
run benchmark arrow_writer
env:
  BENCH_FILTER: "string"

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench arrow_writer
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing fix-byte-array-decimal-stats (5c59f2a) to fbdbf93 (merge-base) diff

Run configuration
run benchmark arrow_writer
env:
  BENCH_FILTER: "string"
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                            fix-byte-array-decimal-stats            main
-----                                                            ----------------------------            ----
large_string_distinct/delta_byte_array                           1.00     61.3±2.96ms     4.1 GB/sec     1.03     63.5±5.75ms     3.9 GB/sec
large_string_distinct/plain                                      1.00     68.7±5.97ms     3.6 GB/sec     1.01     69.1±5.82ms     3.6 GB/sec
large_string_distinct_nullable/delta_byte_array                  1.00     59.1±4.48ms     4.2 GB/sec     1.01     59.7±5.85ms     4.2 GB/sec
large_string_non_null/bloom_filter                               1.02     52.2±1.38ms     4.8 GB/sec     1.00     51.0±0.09ms     4.9 GB/sec
large_string_non_null/cdc                                        1.00    240.5±3.01ms  1064.4 MB/sec     1.00    239.9±2.01ms  1067.1 MB/sec
large_string_non_null/default                                    1.00     51.2±0.07ms     4.9 GB/sec     1.03     52.9±2.28ms     4.7 GB/sec
large_string_non_null/number_distinct_values                     1.00     67.9±0.19ms     3.7 GB/sec     1.05     71.4±3.22ms     3.5 GB/sec
large_string_non_null/parquet_2                                  1.00     51.2±0.08ms     4.9 GB/sec     1.07     55.0±2.33ms     4.5 GB/sec
large_string_non_null/zstd                                       1.00     51.8±1.22ms     4.8 GB/sec     1.01     52.2±1.67ms     4.8 GB/sec
large_string_non_null/zstd_parquet_2                             1.01     51.7±1.51ms     4.8 GB/sec     1.00     51.0±0.08ms     4.9 GB/sec
large_string_shared_prefix/delta_byte_array                      1.02     44.9±3.16ms     5.6 GB/sec     1.00     43.9±2.17ms     5.7 GB/sec
large_string_shared_prefix/plain                                 1.02     94.8±5.73ms     2.6 GB/sec     1.00     93.2±1.64ms     2.7 GB/sec
large_string_shared_prefix_list/delta_byte_array                 1.00     54.3±0.39ms     4.6 GB/sec     1.00     54.6±0.17ms     4.6 GB/sec
large_string_shared_prefix_nullable/delta_byte_array             1.00     40.5±0.25ms     6.2 GB/sec     1.00     40.4±0.14ms     6.2 GB/sec
large_string_shared_prefix_nullable/plain                        1.00     64.5±0.45ms     3.9 GB/sec     1.01     65.0±0.38ms     3.8 GB/sec
large_string_shared_prefix_nullable_dense/delta_byte_array       1.04     23.0±1.81ms     5.4 GB/sec     1.00     22.2±0.91ms     5.6 GB/sec
large_string_shared_prefix_nullable_trailing/delta_byte_array    1.00     46.8±4.07ms     5.3 GB/sec     1.00     46.9±3.85ms     5.3 GB/sec
medium_string_shared_prefix_nullable/delta_byte_array            1.01     35.5±0.06ms     7.0 GB/sec     1.00     35.3±0.08ms     7.1 GB/sec
short_string_non_null/bloom_filter                               1.00     24.3±0.26ms   493.5 MB/sec     1.29     31.4±0.18ms   382.0 MB/sec
short_string_non_null/cdc                                        1.00     18.9±0.09ms   636.5 MB/sec     1.31     24.7±0.16ms   484.9 MB/sec
short_string_non_null/default                                    1.00     14.0±0.10ms   854.8 MB/sec     1.47     20.6±0.16ms   582.9 MB/sec
short_string_non_null/number_distinct_values                     1.00     66.6±1.66ms   180.3 MB/sec     1.09     72.4±0.36ms   165.8 MB/sec
short_string_non_null/parquet_2                                  1.00     24.4±0.16ms   491.0 MB/sec     1.32     32.3±0.17ms   371.5 MB/sec
short_string_non_null/zstd                                       1.00     34.1±0.18ms   352.3 MB/sec     1.21     41.2±0.32ms   291.0 MB/sec
short_string_non_null/zstd_parquet_2                             1.00     27.2±0.10ms   441.3 MB/sec     1.30     35.4±0.21ms   339.3 MB/sec
small_string_distinct/delta_byte_array                           1.00   981.5±11.12µs     8.0 GB/sec     1.02  1003.9±10.22µs     7.8 GB/sec
small_string_distinct/plain                                      1.00   804.0±99.24µs     9.8 GB/sec     1.02   816.9±59.51µs     9.6 GB/sec
small_string_partial_prefix/delta_byte_array                     1.00  1088.3±145.46µs     7.2 GB/sec    1.00  1088.6±154.02µs     7.2 GB/sec
small_string_partial_prefix/plain                                1.06  1132.3±187.61µs     6.9 GB/sec    1.00  1067.6±113.03µs     7.3 GB/sec
small_string_shared_prefix/delta_byte_array                      1.02   1014.1±5.74µs     7.7 GB/sec     1.00    992.6±1.65µs     7.9 GB/sec
small_string_shared_prefix/plain                                 1.00  1092.0±53.56µs     7.2 GB/sec     1.02  1112.9±42.06µs     7.0 GB/sec
string/bloom_filter                                              1.01   195.3±14.84ms     2.6 GB/sec     1.00   193.8±14.98ms     2.6 GB/sec
string/cdc                                                       1.00    198.2±2.29ms     2.6 GB/sec     1.04    206.5±6.42ms     2.5 GB/sec
string/default                                                   1.04    109.1±7.64ms     4.7 GB/sec     1.00    104.8±4.41ms     4.9 GB/sec
string/number_distinct_values                                    1.00    203.0±8.50ms     2.5 GB/sec     1.10    222.3±9.94ms     2.3 GB/sec
string/parquet_2                                                 1.00     94.3±1.70ms     5.4 GB/sec     1.07    101.2±5.97ms     5.1 GB/sec
string/zstd                                                      1.00   421.4±10.15ms  1244.0 MB/sec     1.01    426.5±8.41ms  1229.2 MB/sec
string/zstd_parquet_2                                            1.00    385.4±4.53ms  1360.2 MB/sec     1.01    391.0±6.69ms  1340.9 MB/sec
string_and_binary_view/bloom_filter                              1.02     64.4±2.64ms   501.1 MB/sec     1.00     63.3±0.35ms   509.1 MB/sec
string_and_binary_view/cdc                                       1.00     58.8±2.49ms   548.9 MB/sec     1.06     62.3±2.45ms   517.3 MB/sec
string_and_binary_view/default                                   1.00     46.8±1.47ms   688.7 MB/sec     1.07     50.0±0.97ms   645.3 MB/sec
string_and_binary_view/number_distinct_values                    1.00     49.6±2.55ms   649.8 MB/sec     1.02     50.6±1.93ms   637.0 MB/sec
string_and_binary_view/parquet_2                                 1.00     57.6±0.88ms   560.2 MB/sec     1.11     64.0±1.60ms   504.3 MB/sec
string_and_binary_view/zstd                                      1.00     83.4±0.15ms   386.8 MB/sec     1.05     87.4±1.89ms   368.9 MB/sec
string_and_binary_view/zstd_parquet_2                            1.00     71.1±0.19ms   453.5 MB/sec     1.09     77.6±1.94ms   415.7 MB/sec
string_dictionary/bloom_filter                                   1.00     85.7±5.29ms     3.0 GB/sec     1.00     85.5±8.35ms     3.0 GB/sec
string_dictionary/cdc                                            1.00     46.0±1.48ms     5.6 GB/sec     1.03     47.5±2.58ms     5.4 GB/sec
string_dictionary/default                                        1.01     41.7±1.86ms     6.2 GB/sec     1.00     41.5±1.31ms     6.2 GB/sec
string_dictionary/number_distinct_values                         1.04     73.5±4.28ms     3.5 GB/sec     1.00     70.9±2.00ms     3.6 GB/sec
string_dictionary/parquet_2                                      1.00     48.1±0.94ms     5.4 GB/sec     1.00     48.2±0.76ms     5.3 GB/sec
string_dictionary/zstd                                           1.01    207.6±5.94ms  1272.1 MB/sec     1.00    204.7±3.98ms  1290.4 MB/sec
string_dictionary/zstd_parquet_2                                 1.00    192.7±1.25ms  1370.8 MB/sec     1.00    192.2±1.22ms  1374.4 MB/sec
string_dictionary_low_cardinality_100/bloom_filter               1.00     23.5±0.14ms   170.3 MB/sec     1.07     25.1±0.09ms   159.7 MB/sec
string_dictionary_low_cardinality_100/cdc                        1.00     25.8±0.14ms   155.6 MB/sec     1.06     27.2±0.12ms   147.2 MB/sec
string_dictionary_low_cardinality_100/default                    1.00     23.5±0.15ms   170.8 MB/sec     1.07     25.0±0.08ms   160.4 MB/sec
string_dictionary_low_cardinality_100/number_distinct_values     1.00     39.3±0.20ms   101.8 MB/sec     1.03     40.7±0.14ms    98.5 MB/sec
string_dictionary_low_cardinality_100/parquet_2                  1.00     23.5±0.16ms   170.7 MB/sec     1.07     25.1±0.13ms   159.8 MB/sec
string_dictionary_low_cardinality_100/zstd                       1.00     23.9±0.18ms   167.5 MB/sec     1.06     25.4±0.14ms   157.8 MB/sec
string_dictionary_low_cardinality_100/zstd_parquet_2             1.00     23.9±0.15ms   167.8 MB/sec     1.06     25.4±0.15ms   157.6 MB/sec
string_dictionary_low_cardinality_20/bloom_filter                1.00     23.7±0.13ms   169.2 MB/sec     1.03     24.4±0.09ms   163.8 MB/sec
string_dictionary_low_cardinality_20/cdc                         1.00     26.1±0.12ms   153.7 MB/sec     1.02     26.7±0.15ms   149.9 MB/sec
string_dictionary_low_cardinality_20/default                     1.00     23.5±0.14ms   170.4 MB/sec     1.04     24.3±0.08ms   164.5 MB/sec
string_dictionary_low_cardinality_20/number_distinct_values      1.00     38.9±0.22ms   103.0 MB/sec     1.02     39.8±0.20ms   100.6 MB/sec
string_dictionary_low_cardinality_20/parquet_2                   1.00     23.5±0.13ms   170.5 MB/sec     1.04     24.4±0.12ms   164.1 MB/sec
string_dictionary_low_cardinality_20/zstd                        1.00     23.7±0.19ms   169.1 MB/sec     1.03     24.5±0.08ms   163.5 MB/sec
string_dictionary_low_cardinality_20/zstd_parquet_2              1.00     23.6±0.12ms   169.9 MB/sec     1.04     24.6±0.12ms   163.0 MB/sec
string_dictionary_low_cardinality_400/bloom_filter               1.00     24.4±0.18ms   164.5 MB/sec     1.04     25.4±0.15ms   157.9 MB/sec
string_dictionary_low_cardinality_400/cdc                        1.00     26.9±0.35ms   149.4 MB/sec     1.03     27.7±0.22ms   144.9 MB/sec
string_dictionary_low_cardinality_400/default                    1.00     24.4±0.25ms   164.4 MB/sec     1.04     25.3±0.17ms   158.7 MB/sec
string_dictionary_low_cardinality_400/number_distinct_values     1.00     40.0±0.27ms   100.2 MB/sec     1.03     41.2±0.33ms    97.3 MB/sec
string_dictionary_low_cardinality_400/parquet_2                  1.00     24.7±0.20ms   162.7 MB/sec     1.04     25.7±0.24ms   156.4 MB/sec
string_dictionary_low_cardinality_400/zstd                       1.00     24.8±0.19ms   161.6 MB/sec     1.05     26.0±0.21ms   154.1 MB/sec
string_dictionary_low_cardinality_400/zstd_parquet_2             1.00     24.7±0.19ms   162.2 MB/sec     1.04     25.8±0.14ms   155.4 MB/sec
string_non_null/bloom_filter                                     1.03   238.9±19.68ms     2.1 GB/sec     1.00   232.9±22.68ms     2.2 GB/sec
string_non_null/cdc                                              1.01   260.1±14.01ms  2015.0 MB/sec     1.00   258.0±17.46ms  2031.4 MB/sec
string_non_null/default                                          1.00   115.8±10.36ms     4.4 GB/sec     1.05   121.7±14.26ms     4.2 GB/sec
string_non_null/number_distinct_values                           1.02   287.4±14.34ms  1823.2 MB/sec     1.00   282.3±14.45ms  1856.1 MB/sec
string_non_null/parquet_2                                        1.04   116.0±10.79ms     4.4 GB/sec     1.00    111.4±5.75ms     4.6 GB/sec
string_non_null/zstd                                             1.00   547.7±15.15ms   956.6 MB/sec     1.02   560.1±16.65ms   935.5 MB/sec
string_non_null/zstd_parquet_2                                   1.02    502.0±8.56ms  1043.8 MB/sec     1.00    493.5±8.73ms  1061.7 MB/sec
string_ree/bloom_filter                                          1.01    159.6±6.47ms   102.8 MB/sec     1.00    158.3±6.53ms   103.6 MB/sec
string_ree/cdc                                                   1.00    224.8±2.66ms    73.0 MB/sec     1.02    230.0±3.17ms    71.3 MB/sec
string_ree/default                                               1.00    128.5±1.53ms   127.7 MB/sec     1.00    128.6±1.59ms   127.6 MB/sec
string_ree/number_distinct_values                                1.00    169.2±6.32ms    97.0 MB/sec     1.04    176.1±6.70ms    93.2 MB/sec
string_ree/parquet_2                                             1.00    132.0±1.65ms   124.3 MB/sec     1.02    134.5±1.52ms   122.0 MB/sec
string_ree/zstd                                                  1.00    187.4±5.89ms    87.6 MB/sec     1.02    190.8±7.00ms    86.0 MB/sec
string_ree/zstd_parquet_2                                        1.00    147.0±2.07ms   111.6 MB/sec     1.02    149.4±1.74ms   109.8 MB/sec
string_ree_95pct_null/bloom_filter                               1.00     24.7±0.39ms    57.0 MB/sec     1.02     25.1±0.30ms    56.1 MB/sec
string_ree_95pct_null/cdc                                        1.00     32.4±0.51ms    43.4 MB/sec     1.01     32.6±0.42ms    43.1 MB/sec
string_ree_95pct_null/default                                    1.00     24.5±0.37ms    57.5 MB/sec     1.00     24.5±0.30ms    57.4 MB/sec
string_ree_95pct_null/number_distinct_values                     1.00     26.6±0.34ms    53.0 MB/sec     1.01     26.7±0.27ms    52.7 MB/sec
string_ree_95pct_null/parquet_2                                  1.00     24.4±0.08ms    57.8 MB/sec     1.01     24.7±0.07ms    57.0 MB/sec
string_ree_95pct_null/zstd                                       1.00     25.1±0.18ms    56.1 MB/sec     1.01     25.4±0.08ms    55.3 MB/sec
string_ree_95pct_null/zstd_parquet_2                             1.00     25.2±0.41ms    55.8 MB/sec     1.02     25.7±0.43ms    54.7 MB/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 1425.3s
Peak memory 2.9 GiB
Avg memory 2.6 GiB
CPU user 1344.3s
CPU sys 76.1s
Peak spill 0 B

branch

Metric Value
Wall time 1440.3s
Peak memory 2.9 GiB
Avg memory 2.6 GiB
CPU user 1355.5s
CPU sys 78.2s
Peak spill 0 B

File an issue against this benchmark runner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parquet Changes to the parquet crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parquet ArrowColumnWriter writer produces incorrect statistics for Decimal represented as ByteArray

4 participants