Skip to content

Improve aggregate performance with adaptive sizing in accumulators / avoiding reallocations in accumulators #7065

Description

@alamb

UPDATE: Now that @Rachelint has begun work on #11931 I turned this ticket into an "epic" (aka I'll link related tasks here)

Is your feature request related to a problem or challenge?

Making aggregations fast in datafusion helps its adoption and makes it even cooler

As part of the new #6904 work, @yjshen had an idea #6800 (comment) that could avoid a copy in the accumulator implementations:

Describe the solution you'd like

Adaptive sizing(perhaps?): How would the hash table header and states in each accumulator initialize and grow their sizes afterward?

Here is the structure of the current group operator

                                         ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
                                         │┌────────────┐│   │┌────────────┐│   │┌────────────┐│
    ┌─────┐                              ││accumulator ││   ││accumulator ││   ││accumulator ││
    │  5  │                              ││     0      ││   ││     0      ││   ││     0      ││
    ├─────┤                              ││ ┌────────┐ ││   ││ ┌────────┐ ││   ││ ┌────────┐ ││
    │  9  │                              ││ │ state  │ ││   ││ │ state  │ ││   ││ │ state  │ ││
    ├─────┤                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    │     │                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    ├─────┤                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    │  1  │                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    ├─────┤                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    │     │                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    └─────┘                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
                                         ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
                                         ││ └────────┘ ││   ││ └────────┘ ││   ││ └────────┘ ││
                                         │└────────────┘│   │└────────────┘│   │└────────────┘│
    Hash Table                           └──────────────┘   └──────────────┘   └──────────────┘
                                                                                               
                                          New NewAccumulator                                   
                                                                                               
                                                                                               
                                                                                               
stores "group indexes"                     There is one GroupsAccumulator per aggregate           
which are indexes into                     (NOT PER GROUP). Internally, each                   
Vec<GroupState>                            GroupsAccumulator manages the state for                
                                           multiple groups                                     

The implementation of this, such as Average is to use a Vec<T>. While this approach is simple to implement, it also means that as the Vec grows, the accumulated vales may be copied (up to 2 times on average, given a doubling strategy)

An alternate, suggested by @yjshen is to segment the state into fixed-sized vectors, allocate a new vector at a time, fill it until full, then create a new vector for upcoming new states.

                                         ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
                                         │┌────────────┐│   │┌────────────┐│   │┌────────────┐│
    ┌─────┐                              ││accumulator ││   ││accumulator ││   ││accumulator ││
    │  5  │                              ││     AGG    ││   ││     SUM    ││   ││     0      ││
    ├─────┤                              ││ ┌────────┐ ││   ││ ┌────────┐ ││   ││ ┌────────┐ ││
    │  9  │                              ││ │ state- │ ││   ││ │ state- │ ││   ││ │ state  │ ││
    ├─────┤                              ││ │segment-│ ││   ││ │segment-│ ││   ││ │        │ ││
    │     │                              ││ │   1    │ ││   ││ │   1    │ ││   ││ │        │ ││
    ├─────┤                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    │  1  │                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    ├─────┤                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    │     │                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
    └─────┘                              ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
                                         ││ │        │ ││   ││ │        │ ││   ││ │        │ ││
                                         ││ └────────┘ ││   ││ └────────┘ ││   ││ └────────┘ ││
                                         ││            ││   ││            ││   │└────────────┘│
    Hash Table                           ││ ┌────────┐ ││   ││ ┌────────┐ ││   └──────────────┘
                                         ││ │ state- │ ││   ││ │ state- │ ││                   
                                         ││ │segment-│ ││   ││ │segment-│ ││                   
                                         ││ │   2    │ ││   ││ │   2    │ ││                   
                                         ││ │        │ ││   ││ │        │ ││                   
                                         ││ │        │ ││   ││ │        │ ││                   
                                         ││ │        │ ││   ││ │        │ ││                   
                                         ││ │        │ ││   ││ │        │ ││                   
                                         ││ │        │ ││   ││ │        │ ││                   
                                         ││ │        │ ││   ││ │        │ ││                   
                                         ││ └────────┘ ││   ││ └────────┘ ││                   
                                         │└────────────┘│   │└────────────┘│                   
                                         └──────────────┘   └──────────────┘                   
                                                                                               

Thru this segmented approach, we could avoid memory copy for each resize, which the number of resizing would be great for high cardinality aggs, and grows the size more predictably.

But admittedly, this approach would also bring complexity for both header pointer management and update span multiple vectors.

Implementation Steps:

Activity

  1. my-vegetable-has-exploded commented on Nov 27, 2023

    @my-vegetable-has-exploded
    Contributor

    It seems like deque in cpp ?
    deque in cpp
    But what does adaptive mean in with adaptive sizing?
    thanks, @alamb

  2. alamb commented on Nov 28, 2023

    @alamb
    ContributorAuthor

    But what does adaptive mean in with adaptive sizing?

    I think @yjshen meant something like storing the values in Vec<Vec<T>> rather than Vec<T>

    And when more space was needed, create a new Vec::with_capacity rather than push to the existing Vec (which might reallocate and copy)

  3. avantgardnerio commented on Aug 1, 2024

    @avantgardnerio
    Contributor

    We (Coralogix) may want to tackle this but combine with ColumnStatistics::distinct_count to only allocate once.

  4. alamb commented on Aug 1, 2024

    @alamb
    ContributorAuthor

    Using statistics to improve the allocation performance certainly seems like a good idea -- though I am not sure the default distinct statistics are very reliable

  5. Rachelint commented on Aug 6, 2024

    @Rachelint
    Contributor

    take

  6. Rachelint commented on Aug 6, 2024

    @Rachelint
    Contributor

    Working on an poc for performance improvement which is related to this.

  7. changed the title [-]Improve aggregate performance with adaptive sizing in accumulators / avoiding reallocations in accumulators[/-] [+][EPIC] Improve aggregate performance with adaptive sizing in accumulators / avoiding reallocations in accumulators[/+] on Aug 22, 2024
  8. Rachelint commented on Apr 5, 2025

    @Rachelint
    Contributor

    Restart pushing this improvement forward in #15591.
    Plan to impl a simple sketch for it firstly, and then file related issues to impl rests of it (support in many many accmulators and group values...)

  9. alamb commented on Apr 5, 2025

    @alamb
    ContributorAuthor

    Welcome back @Rachelint -- I predict it is going to be a great few months in DataFusion for performance

    BTW if you are interested in other low level things, I would love some help sorting out parquet filter pushdown (would involve figuring out how to evaluate predicates on string columns in two passes without slowing things down)

    The eventual goal is

  10. Rachelint commented on Apr 5, 2025

    @Rachelint
    Contributor

    Welcome back @Rachelint -- I predict it is going to be a great few months in DataFusion for performance

    BTW if you are interested in other low level things, I would love some help sorting out parquet filter pushdown (would involve figuring out how to evaluate predicates on string columns in two passes without slowing things down)

    The eventual goal is

    * [Enable parquet filter pushdown by default #3463](https://github.com/apache/datafusion/issues/3463)
    
    * more details on [Parquet decoder / decoded page Cache arrow-rs#7363](https://github.com/apache/arrow-rs/issues/7363)
    

    Thanks, it seems interesting!

  11. Rachelint commented on Apr 28, 2025

    @Rachelint
    Contributor

    The formal sketch pr #15591 is ready for review now.

  12. alamb commented on Apr 28, 2025

    @alamb
    ContributorAuthor

    I will try and find time for a review over the next few days

  13. added
    EPICA larger project, actively underway, with sub tasks
    and removed
    EPICA larger project, actively underway, with sub tasks
    on Aug 12, 2025
  14. changed the title [-][EPIC] Improve aggregate performance with adaptive sizing in accumulators / avoiding reallocations in accumulators[/-] [+] Improve aggregate performance with adaptive sizing in accumulators / avoiding reallocations in accumulators[/+] on Aug 12, 2025
  15. alamb commented on Aug 12, 2025

    @alamb
    ContributorAuthor

    I don't think this is an epic in the sense that it is a project being actively worked on, so updating title

  16. avantgardnerio commented on Aug 13, 2025

    @avantgardnerio
    Contributor

    Have we considered using the fallible_collections crate to avoid OOMs in general?

  17. alamb commented on Aug 13, 2025

    @alamb
    ContributorAuthor

    Have we considered using the fallible_collections crate to avoid OOMs in general?

    Not that I know of

  18. alamb commented on Aug 27, 2026

    @alamb
    ContributorAuthor

    I have captured as much as I can about the overall issue in a new ticket that explains this issue better including the symptoms and what we have tried so far

  19. reopened this on Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions