Skip to content

fix: stop pre-allocating and undercounting a hash table per COUNT(DISTINCT) group - #24857

Draft
adriangb wants to merge 6 commits into
mainfrom
claude/bytes-map-initial-capacity-accounting
Draft

fix: stop pre-allocating and undercounting a hash table per COUNT(DISTINCT) group#24857
adriangb wants to merge 6 commits into
mainfrom
claude/bytes-map-initial-capacity-accounting

Conversation

@adriangb

@adriangb adriangb commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

No existing issue. This was found while investigating a production out of memory. Happy to file one if you would like it tracked for the changelog.

Rationale for this change

ArrowBytesMap and ArrowBytesViewMap always pre-allocated their hash table, and ArrowBytesMap also pre-allocated an 8 KiB value buffer. That is the right trade for the single map backing a GROUP BY on one string column. It is the wrong trade for BytesDistinctCountAccumulator and BytesViewDistinctCountAccumulator, because GroupsAccumulatorAdapter creates one accumulator per group. A grouped COUNT(DISTINCT) over a high cardinality key holds hundreds of thousands of them at once and most see only a handful of values, so the pre-allocation dwarfs the data.

Both maps also misreported the table's footprint. ArrowBytesViewMap seeded its map_size with capacity() * size_of::<Entry<V>>(), which leaves out the control bytes. ArrowBytesMap seeded it with 0 despite pre-allocating, and HashTableAllocExt::insert_accounted only charges on growth, so any map staying under its pre-allocated capacity reported its table as free forever.

clear_shrink is the other half of the release path. GroupValuesBytes::clear_shrink and GroupValuesBytesView::clear_shrink went through take, which restores the configured warm-up capacity, so the memory the aggregate stream intends to hand back before spilling and before a downstream sort was never actually released.

Measured on this base

Hash table sizing, one map, nothing inserted:

ArrowBytesMap (Utf8, Entry<i32, ()>) ArrowBytesViewMap (Utf8View, Entry<()>)
Entry size 24 bytes 32 bytes
pre-allocated capacity requested 128 512
hashbrown capacity() 224 896
real table allocation 6,408 bytes 33,800 bytes
old capacity() * size_of::<Entry>() 5,376 bytes 28,672 bytes
undercount 1.19x 1.18x

One per-group accumulator holding a single 24-byte distinct value, size() in bytes:

before, actual before, reported after, actual and reported
BytesDistinctCountAccumulator 14,648 8,240 180
BytesViewDistinctCountAccumulator 33,920 28,792 260

The ArrowBytesMap row is the more extreme reporting error: the map really held 14,648 bytes and reported 8,240, because the whole 6,408-byte table was invisible to the old accounting.

Production motivation

A production process running DataFusion died holding 10.98 GB, roughly 75% of it in per-group COUNT(DISTINCT) accumulators allocated through GroupsAccumulatorAdapter. At ~254,000 live view accumulators this change takes that from ~8.6 GB to ~66 MB, and makes the reported figure exact rather than ~1.3 GB short.

What changes are included in this PR?

  • Split the constructors of both maps: new allocates nothing, with_capacity keeps the previous pre-allocating behavior. The capacity is remembered so take re-creates the map the way it was built.
  • GroupValuesBytes and GroupValuesBytesView move to with_capacity. The two distinct-count accumulators stay on new.
  • Drop map_size in favour of HashTable::allocation_size, which is exact, covers the control bytes, and is a constant time layout calculation, so size() stays cheap.
  • Add clear_and_release, which drops every allocation the map holds and remembers the configured capacity so the map warms back up on the next take. GroupValuesBytes::clear_shrink and GroupValuesBytesView::clear_shrink now call it.
  • datafusion/physical-expr-common/benches/arrow_bytes_map.rs moves to with_capacity so it keeps measuring the pre-allocating constructor. Its long_low_cardinality case is defined by the distinct values fitting inside the pre-allocated buffer, so switching it to the lazy constructor would change what the benchmark measures rather than how fast it runs.

What is the testing strategy for this PR?

Two new tests in datafusion/core/tests/memory_limit/mod.rs, group_by_count_distinct_utf8 and group_by_count_distinct_utf8_view, make the headline claim a binary observable rather than a number: a grouped COUNT(DISTINCT <string>) that could not run inside a memory limit before this change completes inside it now. They aggregate a new scenario of 4,000 groups holding 2 distinct values each, with spilling disabled and target_partitions pinned to 1, so completing means the query genuinely fit in the budget rather than spilled out of it.

The minimum budget the same query needs, swept against this PR's base commit:

value column before after limit the test uses
Utf8 ~35.5 MB (fails at 35 MB, passes at 36 MB) ~1.9 MB (fails at 1.8 MB, passes at 2.0 MB) 8 MB
Utf8View ~123 MB (fails at 120 MB, passes at 124 MB) ~2.5 MB (fails at 2.4 MB, passes at 2.6 MB) 16 MB

Each limit sits at least 4x above what this branch needs and at least 4x below what the base needs, so neither test is on a cliff edge. Checked out onto the base commit both fail with Resources exhausted: Additional allocation failed for FinalHashAggregateStream[0]; on this branch both pass, and they pass on 5 consecutive runs.

The avg(payload) in the test query is load bearing, and avg specifically. Without a second aggregate, single_distinct_aggregation_to_group_by rewrites the distinct aggregate into a plain two stage GROUP BY that does not use these accumulators at all, and the tests would pass by construction. That rule tolerates a non-distinct sum, min or max beside the distinct aggregate, because it re-aggregates its own partial results over the deduplicated inner group by and those three compose with themselves. avg does not, so the rule can never accept it, which is why ClickBench Q9 keeps its distinct aggregate.

This matters because #24859 proposes adding count to that allow list. Checked by cherry-picking #24859 onto this branch and re-planning:

-- avg version, unchanged, still goes through GroupsAccumulatorAdapter
AggregateExec: mode=Final, gby=[group_key@0], aggr=[count(DISTINCT t.value), avg(t.payload)]
  AggregateExec: mode=Partial, gby=[group_key@0], aggr=[count(DISTINCT t.value), avg(t.payload)]
    DataSourceExec: partitions=1, partition_sizes=[1]

-- count(*) version, rewritten away, no per group accumulators left
ProjectionExec: expr=[group_key@0, count(alias1)@1 as count(DISTINCT t.value), ...]
  AggregateExec: mode=Final, gby=[group_key@0], aggr=[count(alias1), sum(alias2)]
    AggregateExec: mode=Partial, gby=[group_key@0], aggr=[count(alias1), sum(alias2)]
      AggregateExec: mode=Final, gby=[group_key@0, alias1@1], aggr=[count(1) as alias2]
        AggregateExec: mode=Partial, gby=[group_key@0, value@1 as alias1], aggr=[]
          DataSourceExec: partitions=1, partition_sizes=[1]

With #24859 applied the count(*) form drops from needing ~1.9 MB to ~0.9 MB, so it would have passed the 8 MB test for the wrong reason and on the base commit too. The avg form needs ~1.9 MB either way.

No query results change. The rest is covered by the existing suites for every crate this touches, all run locally and passing: datafusion-physical-expr-common (85 lib, 8 doc), datafusion-functions-aggregate-common (47), datafusion-functions-aggregate -- count_distinct (2), datafusion-physical-plan -- group_values (96), and the full memory_limit module (34). cargo clippy --all-targets is clean on all three crates.

The per-accumulator byte figures in the tables above were measured directly on this base rather than asserted in a test, since the exact numbers depend on the hashbrown layout.

Benchmarks were not re-run for this revision, for the reason given about arrow_bytes_map.rs above.

Are there any user-facing changes?

Yes, in datafusion-physical-expr-common. ArrowBytesMap::new and ArrowBytesViewMap::new no longer pre-allocate; callers wanting the previous behavior should use the new with_capacity. Both types also gain clear_and_release. The change to new is a behavior change to an existing public constructor rather than an addition, so please let me know if you would like the api change label. Grouped COUNT(DISTINCT) on string and binary columns uses substantially less memory and reports its usage to the MemoryPool accurately, so a query that previously hit a memory limit may now succeed. No query results change.

Follow-ups, not in this PR

  • The same undercount class remains at five other production insert_accounted call sites (group_values/row.rs:171, multi_group_by/mod.rs:434,554, multi_group_by/dictionary.rs:197,584, array_agg.rs:989). All are one-map-per-query so the absolute error is bounded, and the fix is the same one-line swap.
  • The count_distinct_groups benchmarks in datafusion/functions-aggregate/benches/count_distinct.rs cover Int64, Int32 and UInt32 only, so the headline win has per-accumulator byte measurements but no criterion evidence.
  • GroupsAccumulatorAdapter has no way to tell an accumulator it is one of many, so the ungrouped COUNT(DISTINCT) also loses its warm-up here. A capacity hint would let the two paths differ.

…wBytesViewMap

Both maps tracked their hash table footprint in a `map_size` field that was
only ever incremented by `HashTableAllocExt::insert_accounted`, which charges
`capacity * size_of::<Entry>()` on growth and nothing else. That undercounts
in two ways.

`ArrowBytesViewMap::new` seeded `map_size` with
`capacity() * size_of::<Entry<V>>()`, which ignores the control bytes and the
trailing group that hashbrown allocates alongside the entry array, so the
reported size was roughly half the real allocation.

`ArrowBytesMap::new` seeded `map_size` with 0 despite pre-allocating a table
for 128 entries. Since `insert_accounted` only charges when the table grows,
any map holding fewer entries than the pre-allocated capacity reported its
hash table as free forever.

Drop the field and ask hashbrown for the exact figure with
`HashTable::allocation_size`, which covers entries, control bytes and the
trailing group. It is a constant time layout calculation, so `size()` stays
cheap, and it cannot drift out of sync with the table the way an
incrementally maintained counter can.
`ArrowBytesMap` and `ArrowBytesViewMap` always pre-allocated their hash
table, and `ArrowBytesMap` also pre-allocated an 8 KiB value buffer. That is
the right trade for the single map that backs a `GROUP BY` on one string
column, which goes on to hold every group value in the query. It is the wrong
trade for `BytesDistinctCountAccumulator` and
`BytesViewDistinctCountAccumulator`, because `GroupsAccumulatorAdapter`
creates one accumulator per group: a grouped `COUNT(DISTINCT)` over a high
cardinality key holds hundreds of thousands of them at once, and most see only
a handful of values, so the pre-allocation dwarfs the data.

Split the constructors. `new` no longer allocates anything, and
`with_capacity` keeps the previous behavior for the callers that want it. The
capacity is stored so `take` re-creates the map the way it was built. The
`GroupValuesBytes` and `GroupValuesBytesView` call sites move to
`with_capacity`; the two distinct-count accumulators stay on `new`.

The `arrow_bytes_map` benchmark also moves to `with_capacity`: its
`long_low_cardinality` case is defined by the distinct values fitting inside
the pre-allocated buffer.
Keep the comment about what `HashTable::allocation_size` covers next to the
value it describes, and say what the test helper's lower bound is derived
from.
`GroupValuesBytes::clear_shrink` and `GroupValuesBytesView::clear_shrink`
reset their map with `take()`, which restores the capacity the map was
configured with so the emptied map stays warm. That is what the emit path
wants, but `clear_shrink` exists to hand memory back before spilling and
before the spilled batch is sorted, so it left roughly 16 KiB (string and
binary) and 34 KiB (view) reserved instead of releasing it.

Add `clear_and_release` to `ArrowBytesMap` and `ArrowBytesViewMap`, which
empties the map and drops its allocations while remembering the configured
capacities so a later `take()` still warms the map up, and call it from the
two `clear_shrink` implementations. The pre-allocation stays at
construction, where the hot single column string `GROUP BY` path earns it.
@github-actions github-actions Bot added physical-expr Changes to the physical-expr crates functions Changes to functions implementation physical-plan Changes to the physical-plan crate labels Sep 1, 2026
@codecov-commenter

codecov-commenter commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.42105% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.67%. Comparing base (da89c7c) to head (54a0229).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...fusion/physical-expr-common/src/binary_view_map.rs 89.47% 8 Missing ⚠️
datafusion/physical-expr-common/src/binary_map.rs 94.56% 4 Missing and 1 partial ⚠️
...c/aggregates/group_values/single_group_by/bytes.rs 96.66% 1 Missing ⚠️
...regates/group_values/single_group_by/bytes_view.rs 96.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #24857      +/-   ##
==========================================
+ Coverage   81.60%   81.67%   +0.07%     
==========================================
  Files        1123     1123              
  Lines      408898   410550    +1652     
  Branches   408898   410550    +1652     
==========================================
+ Hits       333670   335320    +1650     
- Misses      55625    55633       +8     
+ Partials    19603    19597       -6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@adriangb

adriangb commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

run benchmark clickbench_partitioned external_aggr
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 4G

@adriangb

adriangb commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

run benchmark clickbench_extended
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 4G

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5500225182-2071-jkjtq 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/bytes-map-initial-capacity-accounting (84f07da) to da89c7c (merge-base) diff

Run configuration
run benchmark external_aggr
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5500225182-2070-4vmtd 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/bytes-map-initial-capacity-accounting (84f07da) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitioned
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

Results will be posted here when complete


File an issue against this benchmark runner

A grouped `COUNT(DISTINCT <string>)` gets one accumulator per group, and
each of those owns a hash set of the distinct values it has seen. Those
sets were created pre-allocated, so the query's memory use tracked the
number of groups rather than the amount of data.

Add two `memory_limit` tests that turn that into a binary observable, one
for `Utf8` and one for `Utf8View`, over a new scenario of 4,000 groups
holding 2 distinct values each. Measured against this branch's base
commit with spilling disabled and `target_partitions` pinned to 1:

| value column | budget needed before | budget needed after |
| ------------ | -------------------- | ------------------- |
| `Utf8`       | ~35.5 MB             | ~1.9 MB             |
| `Utf8View`   | ~123 MB              | ~2.7 MB             |

The tests run at 8 MB and 16 MB respectively, so each sits at least 4x
above what the branch needs and at least 4x below what the base needs.
Both fail on the base commit with `Resources exhausted` and pass here.
@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing claude/bytes-map-initial-capacity-accounting (84f07da) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitioned
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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

Comparing HEAD and claude_bytes-map-initial-capacity-accounting
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃       HEAD ┃ claude_bytes-map-initial-capacity-accounting ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │    1.23 ms │                                      1.32 ms │  1.08x slower │
│ QQuery 1  │   11.87 ms │                                     12.24 ms │     no change │
│ QQuery 2  │   37.03 ms │                                     37.14 ms │     no change │
│ QQuery 3  │   30.75 ms │                                     31.12 ms │     no change │
│ QQuery 4  │  223.17 ms │                                    221.08 ms │     no change │
│ QQuery 5  │  285.25 ms │                                    270.37 ms │ +1.06x faster │
│ QQuery 6  │    1.29 ms │                                      1.26 ms │     no change │
│ QQuery 7  │   13.79 ms │                                     13.00 ms │ +1.06x faster │
│ QQuery 8  │  337.69 ms │                                    324.59 ms │     no change │
│ QQuery 9  │  464.26 ms │                                    447.01 ms │     no change │
│ QQuery 10 │   69.85 ms │                                     69.34 ms │     no change │
│ QQuery 11 │   80.66 ms │                                     79.62 ms │     no change │
│ QQuery 12 │  264.12 ms │                                    263.94 ms │     no change │
│ QQuery 13 │  947.17 ms │                                    958.24 ms │     no change │
│ QQuery 14 │  282.13 ms │                                    281.33 ms │     no change │
│ QQuery 15 │  263.04 ms │                                    315.06 ms │  1.20x slower │
│ QQuery 16 │ 1233.84 ms │                                   1230.73 ms │     no change │
│ QQuery 17 │  894.36 ms │                                    892.01 ms │     no change │
│ QQuery 18 │ 2499.59 ms │                                   2436.75 ms │     no change │
│ QQuery 19 │   29.97 ms │                                     28.02 ms │ +1.07x faster │
│ QQuery 20 │  512.10 ms │                                    518.54 ms │     no change │
│ QQuery 21 │  512.03 ms │                                    517.63 ms │     no change │
│ QQuery 22 │  988.93 ms │                                    983.27 ms │     no change │
│ QQuery 23 │ 2987.12 ms │                                   2961.53 ms │     no change │
│ QQuery 24 │   42.32 ms │                                     40.54 ms │     no change │
│ QQuery 25 │  110.79 ms │                                    109.66 ms │     no change │
│ QQuery 26 │   42.14 ms │                                     40.99 ms │     no change │
│ QQuery 27 │  509.97 ms │                                    507.01 ms │     no change │
│ QQuery 28 │ 2894.22 ms │                                   2923.06 ms │     no change │
│ QQuery 29 │   40.70 ms │                                     41.01 ms │     no change │
│ QQuery 30 │  300.43 ms │                                    298.54 ms │     no change │
│ QQuery 31 │  272.84 ms │                                    276.82 ms │     no change │
│ QQuery 32 │ 3273.28 ms │                                   3195.54 ms │     no change │
│ QQuery 33 │ 2542.24 ms │                                   2548.40 ms │     no change │
│ QQuery 34 │ 2571.08 ms │                                   2612.72 ms │     no change │
│ QQuery 35 │  275.22 ms │                                    273.74 ms │     no change │
│ QQuery 36 │   67.98 ms │                                     64.78 ms │     no change │
│ QQuery 37 │   35.54 ms │                                     35.25 ms │     no change │
│ QQuery 38 │   42.61 ms │                                     39.68 ms │ +1.07x faster │
│ QQuery 39 │  131.98 ms │                                    129.49 ms │     no change │
│ QQuery 40 │   13.82 ms │                                     13.64 ms │     no change │
│ QQuery 41 │   13.57 ms │                                     13.26 ms │     no change │
│ QQuery 42 │   13.25 ms │                                     12.77 ms │     no change │
└───────────┴────────────┴──────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 26165.22ms │
│ Total Time (claude_bytes-map-initial-capacity-accounting)   │ 26072.05ms │
│ Average Time (HEAD)                                         │   608.49ms │
│ Average Time (claude_bytes-map-initial-capacity-accounting) │   606.33ms │
│ Queries Faster                                              │          4 │
│ Queries Slower                                              │          2 │
│ Queries with No Change                                      │         37 │
│ Queries with Failure                                        │          0 │
└─────────────────────────────────────────────────────────────┴────────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and claude_bytes-map-initial-capacity-accounting
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                   HEAD ┃ claude_bytes-map-initial-capacity-accounting ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │           1.23 / 4.06 ±5.38 / 14.82 ms │                 1.32 / 4.36 ±5.91 / 16.18 ms │  1.07x slower │
│ QQuery 1  │         11.87 / 11.94 ±0.08 / 12.10 ms │               12.24 / 12.61 ±0.27 / 12.95 ms │  1.06x slower │
│ QQuery 2  │         37.03 / 37.49 ±0.32 / 37.98 ms │               37.14 / 37.73 ±0.37 / 38.18 ms │     no change │
│ QQuery 3  │         30.75 / 31.60 ±0.76 / 32.93 ms │               31.12 / 31.74 ±0.69 / 32.99 ms │     no change │
│ QQuery 4  │     223.17 / 243.68 ±15.91 / 260.98 ms │            221.08 / 224.88 ±4.37 / 232.98 ms │ +1.08x faster │
│ QQuery 5  │      285.25 / 290.21 ±5.57 / 300.90 ms │            270.37 / 277.03 ±6.09 / 287.91 ms │     no change │
│ QQuery 6  │            1.29 / 1.44 ±0.22 / 1.87 ms │                  1.26 / 1.40 ±0.22 / 1.82 ms │     no change │
│ QQuery 7  │         13.79 / 14.95 ±1.85 / 18.63 ms │               13.00 / 13.12 ±0.09 / 13.25 ms │ +1.14x faster │
│ QQuery 8  │     337.69 / 347.18 ±12.36 / 370.95 ms │            324.59 / 331.44 ±4.33 / 336.10 ms │     no change │
│ QQuery 9  │      464.26 / 467.05 ±1.90 / 469.66 ms │            447.01 / 455.74 ±9.75 / 473.92 ms │     no change │
│ QQuery 10 │         69.85 / 73.01 ±4.83 / 82.61 ms │               69.34 / 69.73 ±0.43 / 70.50 ms │     no change │
│ QQuery 11 │         80.66 / 81.35 ±0.58 / 82.41 ms │               79.62 / 80.60 ±0.70 / 81.47 ms │     no change │
│ QQuery 12 │      264.12 / 270.83 ±4.24 / 276.22 ms │            263.94 / 271.24 ±5.45 / 278.74 ms │     no change │
│ QQuery 13 │      947.17 / 961.22 ±8.45 / 972.87 ms │           958.24 / 970.47 ±12.66 / 994.02 ms │     no change │
│ QQuery 14 │      282.13 / 284.23 ±2.33 / 288.74 ms │           281.33 / 310.02 ±14.60 / 321.02 ms │  1.09x slower │
│ QQuery 15 │      263.04 / 268.74 ±5.92 / 280.04 ms │            315.06 / 318.95 ±4.09 / 325.97 ms │  1.19x slower │
│ QQuery 16 │  1233.84 / 1262.17 ±22.84 / 1298.97 ms │        1230.73 / 1324.19 ±51.98 / 1376.75 ms │     no change │
│ QQuery 17 │     894.36 / 929.71 ±25.94 / 967.95 ms │           892.01 / 939.67 ±34.45 / 988.98 ms │     no change │
│ QQuery 18 │  2499.59 / 2568.04 ±76.59 / 2674.70 ms │        2436.75 / 2477.07 ±27.94 / 2517.30 ms │     no change │
│ QQuery 19 │         29.97 / 31.76 ±2.48 / 36.52 ms │               28.02 / 30.83 ±3.70 / 37.66 ms │     no change │
│ QQuery 20 │     512.10 / 529.24 ±15.08 / 551.64 ms │            518.54 / 525.61 ±5.47 / 532.44 ms │     no change │
│ QQuery 21 │      512.03 / 516.78 ±4.90 / 525.33 ms │            517.63 / 527.16 ±8.43 / 539.28 ms │     no change │
│ QQuery 22 │   988.93 / 1003.97 ±11.57 / 1019.44 ms │            983.27 / 986.60 ±3.36 / 991.15 ms │     no change │
│ QQuery 23 │  2987.12 / 3034.87 ±26.46 / 3065.23 ms │        2961.53 / 3042.62 ±79.27 / 3177.07 ms │     no change │
│ QQuery 24 │        42.32 / 49.37 ±12.31 / 73.93 ms │               40.54 / 42.40 ±3.01 / 48.39 ms │ +1.16x faster │
│ QQuery 25 │      110.79 / 114.05 ±2.95 / 118.96 ms │            109.66 / 118.71 ±9.38 / 131.77 ms │     no change │
│ QQuery 26 │         42.14 / 42.83 ±0.90 / 44.54 ms │               40.99 / 41.32 ±0.46 / 42.19 ms │     no change │
│ QQuery 27 │      509.97 / 517.58 ±8.40 / 530.46 ms │            507.01 / 517.48 ±7.45 / 529.40 ms │     no change │
│ QQuery 28 │  2894.22 / 2965.95 ±57.94 / 3052.00 ms │        2923.06 / 3014.22 ±87.54 / 3152.23 ms │     no change │
│ QQuery 29 │         40.70 / 49.12 ±9.85 / 63.66 ms │               41.01 / 41.28 ±0.21 / 41.59 ms │ +1.19x faster │
│ QQuery 30 │      300.43 / 310.25 ±8.78 / 325.15 ms │            298.54 / 309.37 ±9.10 / 321.54 ms │     no change │
│ QQuery 31 │      272.84 / 284.09 ±7.19 / 293.05 ms │            276.82 / 293.77 ±8.93 / 302.74 ms │     no change │
│ QQuery 32 │ 3273.28 / 3434.58 ±141.98 / 3640.55 ms │        3195.54 / 3315.02 ±98.24 / 3475.47 ms │     no change │
│ QQuery 33 │  2542.24 / 2618.04 ±42.80 / 2664.49 ms │        2548.40 / 2613.45 ±51.35 / 2686.77 ms │     no change │
│ QQuery 34 │  2571.08 / 2652.97 ±59.28 / 2736.72 ms │        2612.72 / 2684.77 ±79.48 / 2839.62 ms │     no change │
│ QQuery 35 │     275.22 / 293.18 ±21.73 / 334.99 ms │            273.74 / 279.79 ±3.21 / 282.44 ms │     no change │
│ QQuery 36 │       67.98 / 76.97 ±14.42 / 105.60 ms │             64.78 / 78.58 ±16.44 / 110.77 ms │     no change │
│ QQuery 37 │         35.54 / 37.11 ±1.86 / 40.68 ms │               35.25 / 37.01 ±1.35 / 38.82 ms │     no change │
│ QQuery 38 │         42.61 / 44.76 ±2.18 / 48.97 ms │               39.68 / 40.95 ±0.97 / 42.32 ms │ +1.09x faster │
│ QQuery 39 │      131.98 / 145.08 ±8.45 / 153.71 ms │           129.49 / 141.03 ±11.90 / 163.19 ms │     no change │
│ QQuery 40 │         13.82 / 14.28 ±0.25 / 14.54 ms │               13.64 / 14.35 ±0.80 / 15.92 ms │     no change │
│ QQuery 41 │         13.57 / 13.80 ±0.32 / 14.43 ms │               13.26 / 13.45 ±0.15 / 13.69 ms │     no change │
│ QQuery 42 │         13.25 / 15.52 ±2.64 / 19.62 ms │               12.77 / 13.00 ±0.17 / 13.24 ms │ +1.19x faster │
└───────────┴────────────────────────────────────────┴──────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 26945.09ms │
│ Total Time (claude_bytes-map-initial-capacity-accounting)   │ 26874.74ms │
│ Average Time (HEAD)                                         │   626.63ms │
│ Average Time (claude_bytes-map-initial-capacity-accounting) │   624.99ms │
│ Queries Faster                                              │          6 │
│ Queries Slower                                              │          4 │
│ Queries with No Change                                      │         33 │
│ Queries with Failure                                        │          0 │
└─────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: da89c7c (merge-base) | Changed: claude/bytes-map-initial-capacity-accounting

clickbench_partitioned

Query Base Changed Change
Query 0 0 B 0 B 0.0%
Query 1 104 B 104 B +0.0%
Query 2 936 B 936 B +0.0%
Query 3 312 B 312 B +0.0%
Query 4 774.7 MiB 772.2 MiB -0.3%
Query 5 1.2 GiB 1.2 GiB +1.5%
Query 6 0 B 0 B 0.0%
Query 7 60.0 MiB 50.2 MiB -16.4%
Query 8 875.0 MiB 868.6 MiB -0.7%
Query 9 551.9 MiB 593.9 MiB +7.6%
Query 10 106.0 MiB 115.6 MiB +9.0%
Query 11 112.2 MiB 109.3 MiB -2.6%
Query 12 1.3 GiB 1.4 GiB +4.1%
Query 13 1013.0 MiB 1.0 GiB +4.1%
Query 14 1.3 GiB 1.3 GiB +2.0%
Query 15 1.2 GiB 1.1 GiB -1.2%
Query 16 2.0 GiB 1.9 GiB -2.7%
Query 17 1.7 GiB 2.2 GiB +32.0%
Query 18 1.9 GiB 1.9 GiB +0.5%
Query 19 0 B 0 B 0.0%
Query 20 104 B 104 B +0.0%
Query 21 3.6 MiB 3.3 MiB -9.0%
Query 22 3.0 MiB 3.1 MiB +2.9%
Query 23 26.0 MiB 27.1 MiB +4.2%
Query 24 60.1 MiB 59.3 MiB -1.4%
Query 25 182.1 MiB 176.2 MiB -3.2%
Query 26 64.9 MiB 63.1 MiB -2.7%
Query 27 2.2 MiB 2.4 MiB +10.0%
Query 28 1.5 GiB 1.5 GiB -3.6%
Query 29 624 B 624 B +0.0%
Query 30 740.3 MiB 729.6 MiB -1.4%
Query 31 1.6 GiB 1.5 GiB -5.8%
Query 32 927.3 MiB 967.7 MiB +4.4%
Query 33 2.0 GiB 2.1 GiB +5.8%
Query 34 2.1 GiB 2.2 GiB +2.3%
Query 35 597.1 MiB 614.0 MiB +2.8%
Query 36 124.1 MiB 112.6 MiB -9.3%
Query 37 6.9 MiB 6.6 MiB -4.6%
Query 38 5.6 MiB 5.4 MiB -4.7%
Query 39 298.1 MiB 297.5 MiB -0.2%
Query 40 2.0 MiB 1.7 MiB -14.4%
Query 41 3.1 MiB 3.1 MiB +0.0%
Query 42 1.6 MiB 1.8 MiB +12.5%

Pool accounting vs. process RSS

Max pool peak is the largest reservation any single query in the run reached; peak RSS covers the whole invocation, including data loading and allocator retention, and the two high-water marks need not coincide in time. The gap is therefore an upper bound on what the pool did not account for, not a measurement of it.

Benchmark Side Max pool peak Peak RSS Gap RSS / pool
clickbench_partitioned base (da89c7c (merge-base)) 2.1 GiB 9.3 GiB 7.2 GiB 4.4×
clickbench_partitioned changed (claude/bytes-map-initial-capacity-accounting) 2.2 GiB 9.6 GiB 7.4 GiB 4.4×
Resource Usage

clickbench_partitioned — base (merge-base)

Metric Value
Wall time 140.0s
Peak memory 9.3 GiB
Avg memory 5.7 GiB
CPU user 1375.0s
CPU sys 130.4s
Peak spill 0 B

clickbench_partitioned — branch

Metric Value
Wall time 135.0s
Peak memory 9.6 GiB
Avg memory 5.4 GiB
CPU user 1365.1s
CPU sys 129.7s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5500235371-2073-mrv44 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/bytes-map-initial-capacity-accounting (84f07da) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_extended
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing claude/bytes-map-initial-capacity-accounting (84f07da) to da89c7c (merge-base) diff

Run configuration
run benchmark external_aggr
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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

Comparing HEAD and claude_bytes-map-initial-capacity-accounting
--------------------
Benchmark external_aggr.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query        ┃      HEAD ┃ claude_bytes-map-initial-capacity-accounting ┃    Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Q1(64.0 MB)  │  54.18 ms │                                     52.13 ms │ no change │
│ Q1(32.0 MB)  │  49.54 ms │                                     51.82 ms │ no change │
│ Q1(16.0 MB)  │  47.47 ms │                                     48.32 ms │ no change │
│ Q2(512.0 MB) │ 271.98 ms │                                    279.84 ms │ no change │
│ Q2(256.0 MB) │ 260.04 ms │                                    266.66 ms │ no change │
│ Q2(128.0 MB) │ 242.68 ms │                                    247.07 ms │ no change │
│ Q2(64.0 MB)  │ 241.69 ms │                                    243.40 ms │ no change │
│ Q2(32.0 MB)  │ 303.67 ms │                                    309.17 ms │ no change │
└──────────────┴───────────┴──────────────────────────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 1471.25ms │
│ Total Time (claude_bytes-map-initial-capacity-accounting)   │ 1498.41ms │
│ Average Time (HEAD)                                         │  183.91ms │
│ Average Time (claude_bytes-map-initial-capacity-accounting) │  187.30ms │
│ Queries Faster                                              │         0 │
│ Queries Slower                                              │         0 │
│ Queries with No Change                                      │         8 │
│ Queries with Failure                                        │         0 │
└─────────────────────────────────────────────────────────────┴───────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and claude_bytes-map-initial-capacity-accounting
--------------------
Benchmark external_aggr.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query        ┃                               HEAD ┃ claude_bytes-map-initial-capacity-accounting ┃    Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Q1(64.0 MB)  │     54.18 / 56.90 ±3.65 / 64.10 ms │               52.13 / 55.86 ±4.02 / 63.35 ms │ no change │
│ Q1(32.0 MB)  │     49.54 / 51.81 ±1.14 / 52.47 ms │               51.82 / 52.37 ±0.58 / 53.49 ms │ no change │
│ Q1(16.0 MB)  │     47.47 / 49.70 ±1.25 / 51.14 ms │               48.32 / 50.12 ±2.65 / 55.37 ms │ no change │
│ Q2(512.0 MB) │ 271.98 / 287.24 ±10.43 / 299.89 ms │            279.84 / 285.28 ±6.77 / 298.19 ms │ no change │
│ Q2(256.0 MB) │ 260.04 / 286.63 ±23.42 / 324.59 ms │           266.66 / 286.57 ±10.78 / 296.15 ms │ no change │
│ Q2(128.0 MB) │  242.68 / 247.19 ±3.10 / 250.81 ms │            247.07 / 255.69 ±6.26 / 263.97 ms │ no change │
│ Q2(64.0 MB)  │ 241.69 / 251.69 ±14.02 / 279.49 ms │            243.40 / 246.04 ±1.64 / 248.52 ms │ no change │
│ Q2(32.0 MB)  │  303.67 / 308.53 ±2.78 / 311.25 ms │            309.17 / 311.02 ±1.50 / 313.29 ms │ no change │
└──────────────┴────────────────────────────────────┴──────────────────────────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 1539.69ms │
│ Total Time (claude_bytes-map-initial-capacity-accounting)   │ 1542.94ms │
│ Average Time (HEAD)                                         │  192.46ms │
│ Average Time (claude_bytes-map-initial-capacity-accounting) │  192.87ms │
│ Queries Faster                                              │         0 │
│ Queries Slower                                              │         0 │
│ Queries with No Change                                      │         8 │
│ Queries with Failure                                        │         0 │
└─────────────────────────────────────────────────────────────┴───────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: da89c7c (merge-base) | Changed: claude/bytes-map-initial-capacity-accounting

external_aggr

Query Base Changed Change
1(64.0 MB) 36.8 MiB 36.8 MiB +0.0%
1(32.0 MB) 17.8 MiB 18.7 MiB +5.3%
1(16.0 MB) 11.3 MiB 11.4 MiB +0.4%
2(512.0 MB) 137.1 MiB 137.0 MiB -0.1%
2(256.0 MB) 97.9 MiB 97.9 MiB +0.0%
2(128.0 MB) 49.0 MiB 49.4 MiB +0.7%
2(64.0 MB) 29.2 MiB 29.2 MiB +0.0%
2(32.0 MB) 30.0 MiB 30.0 MiB +0.0%

Pool accounting vs. process RSS

Max pool peak is the largest reservation any single query in the run reached; peak RSS covers the whole invocation, including data loading and allocator retention, and the two high-water marks need not coincide in time. The gap is therefore an upper bound on what the pool did not account for, not a measurement of it.

Benchmark Side Max pool peak Peak RSS Gap RSS / pool
external_aggr base (da89c7c (merge-base)) 137.1 MiB 418.8 MiB 281.7 MiB 3.1×
external_aggr changed (claude/bytes-map-initial-capacity-accounting) 137.0 MiB 445.6 MiB 308.6 MiB 3.3×
Resource Usage

external_aggr — base (merge-base)

Metric Value
Wall time 510.1s
Peak memory 418.8 MiB
Avg memory 8.8 MiB
CPU user 25.8s
CPU sys 3.7s
Peak spill 0 B

external_aggr — branch

Metric Value
Wall time 520.1s
Peak memory 445.6 MiB
Avg memory 8.6 MiB
CPU user 21.9s
CPU sys 3.1s
Peak spill 0 B

File an issue against this benchmark runner

@github-actions github-actions Bot added the core Core DataFusion crate label Sep 1, 2026
@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing claude/bytes-map-initial-capacity-accounting (84f07da) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_extended
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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

Comparing HEAD and claude_bytes-map-initial-capacity-accounting
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃        HEAD ┃ claude_bytes-map-initial-capacity-accounting ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │   759.73 ms │                                    777.73 ms │     no change │
│ QQuery 1  │   191.85 ms │                                    188.58 ms │     no change │
│ QQuery 2  │   461.18 ms │                                    449.45 ms │     no change │
│ QQuery 3  │   316.36 ms │                                    315.46 ms │     no change │
│ QQuery 4  │  1939.19 ms │                                   1972.42 ms │     no change │
│ QQuery 5  │ 17961.07 ms │                                  18770.57 ms │     no change │
│ QQuery 6  │     2.71 ms │                                      2.51 ms │ +1.08x faster │
│ QQuery 7  │  6780.67 ms │                                   6541.07 ms │     no change │
│ QQuery 8  │   413.85 ms │                                    407.70 ms │     no change │
│ QQuery 9  │  2685.46 ms │                                   2806.90 ms │     no change │
│ QQuery 10 │   634.33 ms │                                    657.00 ms │     no change │
│ QQuery 11 │  1907.91 ms │                                   1769.18 ms │ +1.08x faster │
│ QQuery 12 │   199.35 ms │                                    187.42 ms │ +1.06x faster │
│ QQuery 13 │   567.73 ms │                                    537.52 ms │ +1.06x faster │
└───────────┴─────────────┴──────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 34821.39ms │
│ Total Time (claude_bytes-map-initial-capacity-accounting)   │ 35383.52ms │
│ Average Time (HEAD)                                         │  2487.24ms │
│ Average Time (claude_bytes-map-initial-capacity-accounting) │  2527.39ms │
│ Queries Faster                                              │          4 │
│ Queries Slower                                              │          0 │
│ Queries with No Change                                      │         10 │
│ Queries with Failure                                        │          0 │
└─────────────────────────────────────────────────────────────┴────────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and claude_bytes-map-initial-capacity-accounting
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                      HEAD ┃ claude_bytes-map-initial-capacity-accounting ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │      759.73 / 933.63 ±104.69 / 1025.92 ms │           777.73 / 847.50 ±54.54 / 904.14 ms │ +1.10x faster │
│ QQuery 1  │         191.85 / 192.97 ±0.72 / 194.07 ms │            188.58 / 189.46 ±0.60 / 190.27 ms │     no change │
│ QQuery 2  │         461.18 / 463.94 ±2.29 / 466.71 ms │            449.45 / 452.25 ±2.86 / 457.44 ms │     no change │
│ QQuery 3  │         316.36 / 320.10 ±2.65 / 323.24 ms │            315.46 / 317.01 ±1.64 / 320.03 ms │     no change │
│ QQuery 4  │     1939.19 / 2044.28 ±93.88 / 2205.14 ms │        1972.42 / 2012.03 ±32.28 / 2066.64 ms │     no change │
│ QQuery 5  │ 17961.07 / 18599.98 ±361.16 / 18964.48 ms │    18770.57 / 18949.41 ±196.42 / 19309.14 ms │     no change │
│ QQuery 6  │               2.71 / 3.47 ±1.14 / 5.69 ms │                  2.51 / 2.92 ±0.33 / 3.46 ms │ +1.19x faster │
│ QQuery 7  │  6780.67 / 8768.03 ±1639.91 / 10478.13 ms │      6541.07 / 7831.91 ±1401.44 / 9664.85 ms │ +1.12x faster │
│ QQuery 8  │         413.85 / 417.29 ±2.91 / 422.35 ms │            407.70 / 411.63 ±2.71 / 415.64 ms │     no change │
│ QQuery 9  │     2685.46 / 2737.34 ±60.65 / 2850.29 ms │       2806.90 / 2955.32 ±135.85 / 3181.21 ms │  1.08x slower │
│ QQuery 10 │         634.33 / 646.41 ±9.42 / 658.78 ms │            657.00 / 669.50 ±8.53 / 678.26 ms │     no change │
│ QQuery 11 │    1907.91 / 2042.36 ±129.72 / 2281.30 ms │       1769.18 / 1963.90 ±112.13 / 2082.76 ms │     no change │
│ QQuery 12 │         199.35 / 206.27 ±5.83 / 214.17 ms │           187.42 / 202.18 ±19.45 / 240.46 ms │     no change │
│ QQuery 13 │        567.73 / 585.03 ±16.61 / 610.85 ms │           537.52 / 562.53 ±27.74 / 614.97 ms │     no change │
└───────────┴───────────────────────────────────────────┴──────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 37961.07ms │
│ Total Time (claude_bytes-map-initial-capacity-accounting)   │ 37367.55ms │
│ Average Time (HEAD)                                         │  2711.51ms │
│ Average Time (claude_bytes-map-initial-capacity-accounting) │  2669.11ms │
│ Queries Faster                                              │          3 │
│ Queries Slower                                              │          1 │
│ Queries with No Change                                      │         10 │
│ Queries with Failure                                        │          0 │
└─────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: da89c7c (merge-base) | Changed: claude/bytes-map-initial-capacity-accounting

clickbench_extended

Query Base Changed Change
Query 0 812.8 MiB 846.5 MiB +4.1%
Query 1 3.4 MiB 2.4 MiB -29.4%
Query 2 98.6 MiB 11.7 MiB -88.1%
Query 3 11.8 MiB 11.7 MiB -0.9%
Query 4 1.4 GiB 1.4 GiB +0.1%
Query 5 1.5 GiB 1.5 GiB -0.0%
Query 6 104 B 104 B +0.0%
Query 7 1.3 GiB 1.2 GiB -1.9%
Query 8 37.2 MiB 37.3 MiB +0.4%
Query 9 2.1 GiB 2.1 GiB -0.5%
Query 10 1.9 MiB 2.1 MiB +10.5%
Query 11 2.2 GiB 2.2 GiB -0.5%
Query 12 1.3 MiB 1.0 MiB -21.2%
Query 13 520 B 520 B +0.0%

Pool accounting vs. process RSS

Max pool peak is the largest reservation any single query in the run reached; peak RSS covers the whole invocation, including data loading and allocator retention, and the two high-water marks need not coincide in time. The gap is therefore an upper bound on what the pool did not account for, not a measurement of it.

Benchmark Side Max pool peak Peak RSS Gap RSS / pool
clickbench_extended base (da89c7c (merge-base)) 2.2 GiB 9.9 GiB 7.7 GiB 4.5×
clickbench_extended changed (claude/bytes-map-initial-capacity-accounting) 2.2 GiB 10.7 GiB 8.5 GiB 4.9×
Resource Usage

clickbench_extended — base (merge-base)

Metric Value
Wall time 195.0s
Peak memory 9.9 GiB
Avg memory 3.7 GiB
CPU user 1920.2s
CPU sys 108.2s
Peak spill 0 B

clickbench_extended — branch

Metric Value
Wall time 190.0s
Peak memory 10.7 GiB
Avg memory 4.4 GiB
CPU user 1873.9s
CPU sys 110.8s
Peak spill 0 B

File an issue against this benchmark runner

The two grouped `COUNT(DISTINCT <string>)` memory limit tests only reach
the per group accumulators while
`single_distinct_aggregation_to_group_by` declines to rewrite the query.
They leant on `count(*)` for that, which the rule rejects only because
`count` is missing from the `sum`/`min`/`max` allow list.
#24859 proposes adding `count` to that list, which would
rewrite the query, remove the accumulators, and leave both tests passing
at any memory limit while still looking like they test something.

Aggregate `avg(payload)` over a new `Int64` column instead. `avg` cannot
be added to that list: the rule re-aggregates its own partial results
over the deduplicated inner group by, and averaging per group averages of
different sizes gives the wrong answer. That is why ClickBench Q9 keeps
its distinct aggregate under #24859.

Verified from the physical plan with #24859 cherry-picked on top of this
branch: the `avg` query still plans as
`aggr=[count(DISTINCT t.value), avg(t.payload)]`, while the `count(*)`
query becomes `aggr=[count(alias1), sum(alias2)]` over an inner
`GROUP BY group_key, value`, and drops from needing ~1.9 MB to ~0.9 MB.

Re-swept both thresholds against the base commit. `Utf8` needs ~35.5 MB
before and ~1.9 MB after; `Utf8View` needs ~123 MB before and ~2.5 MB
after, so the 8 MB and 16 MB limits keep at least 4x margin on each side
and are unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate functions Changes to functions implementation physical-expr Changes to the physical-expr crates physical-plan Changes to the physical-plan crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants