Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allocate memory lazily in BestBucketsDeferringCollector #43339

Merged

Conversation

jimczi
Copy link
Contributor

@jimczi jimczi commented Jun 18, 2019

While investigating memory consumption of deeply nested aggregations for #43091
the memory used to keep track of the doc ids and buckets in the BestBucketsDeferringCollector
showed up as one of the main contributor. In my tests half of the memory held in the
BestBucketsDeferringCollector is associated to segments that don't have matching docs
in the selected buckets. This is expected on fields that have a big cardinality since each
bucket can appear in very few segments. By allocating the builders lazily this change
reduces the memory consumption by a factor 2 (from 1GB to 512MB), hence reducing the
impact on gcs for these volatile allocations. This commit also switches the PackedLongValues.Builder
with a RoaringDocIdSet in order to handle very sparse buckets more efficiently.

I ran all my tests on the geonames rally track with the following query:

{
    "size": 0,
    "aggs": {
        "country_population": {
            "terms": {
                "size": 100,
                "field": "country_code.raw"
            },
            "aggs": {
                "admin1_code": {
                    "terms": {
                        "size": 100,
                        "field": "admin1_code.raw"
                    },
                    "aggs": {
                        "admin2_code": {
                            "terms": {
                                "size": 100,
                                "field": "admin2_code.raw"
                            },
                            "aggs": {
                                "sum_population": {
                                    "sum": {
                                        "field": "population"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

While investigating memory consumption of deeply nested aggregations for elastic#43091
the memory used to keep track of the doc ids and buckets in the BestBucketsDeferringCollector
showed up as one of the main contributor. In my tests half of the memory held in the
 BestBucketsDeferringCollector is associated to segments that don't have matching docs
 in the selected buckets. This is expected on fields that have a big cardinality since each
 bucket can appear in very few segments. By allocating the builders lazily this change
 reduces the memory consumption by a factor 2 (from 1GB to 512MB), hence reducing the
impact on gcs for these volatile allocations. This commit also switches the PackedLongValues.Builder
with a RoaringDocIdSet in order to handle very sparse buckets more efficiently.

I ran all my tests on the `geoname` rally track with the following query:

````
{
    "size": 0,
    "aggs": {
        "country_population": {
            "terms": {
                "size": 100,
                "field": "country_code.raw"
            },
            "aggs": {
                "admin1_code": {
                    "terms": {
                        "size": 100,
                        "field": "admin1_code.raw"
                    },
                    "aggs": {
                        "admin2_code": {
                            "terms": {
                                "size": 100,
                                "field": "admin2_code.raw"
                            },
                            "aggs": {
                                "sum_population": {
                                    "sum": {
                                        "field": "population"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
````
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-analytics-geo

Copy link
Contributor

@jpountz jpountz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to lazy allocation, can you leave a comment so that we don't disable this optimization by mistake? I'm surprised RoaringDocIdSet performs better, I'd actually expect it to require a bit more memory due to the fact it needs random-access capabilities.

@jimczi
Copy link
Contributor Author

jimczi commented Jun 18, 2019

I'm surprised RoaringDocIdSet performs better, I'd actually expect it to require a bit more memory due to the fact it needs random-access capabilities.

There was no real difference but I thought that RoaringDocIdSet would be a bit faster when deltas are big. I ran the benchmark again and the results are equivalent, however the RoaringDocIdSet doesn't handle duplicates so I reverted the change. I'll reevaluate this change in a follow up, the lazy allocation is enough for now to lower the memory usage.

Copy link
Contributor

@polyfractal polyfractal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat! Had some comments about runtime speed and children aggs but looks like that's moot since you removed the Roaring stuff :)

Note to self: I wonder if the MergingBucketsDeferringCollector should get a similar treatment? It's nearly the same structure, just with addition code to handle merging buckets together. I suspect it's not a big deal given the only agg is the auto_date_histo. But rare_terms will use it... I can take a closer look and see if we can do the same thing :)

if (context == null) {
context = ctx;
docIdSetBuilder = new RoaringDocIdSet.Builder(context.reader().maxDoc());
bucketsBuilder = PackedLongValues.packedBuilder(PackedInts.DEFAULT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, have we tried COMPACT before to see how it affects memory usage and runtime speed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, at least I didn't ;)

DocIdSetIterator docIt = null;
if (needsScores && entry.docDeltas.size() > 0) {
DocIdSetIterator scoreIt = null;
if (needsScores) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming that since we're lazily creating these structures, we should never have an entry with empty docDeltasBuilder? Should we put an assert here to make sure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a check in the Entry ctr: ecea58a

@jimczi
Copy link
Contributor Author

jimczi commented Jun 18, 2019

I wonder if the MergingBucketsDeferringCollector should get a similar treatment? It's nearly the same structure, just with addition code to handle merging buckets together. I suspect it's not a big deal given the only agg is the auto_date_histo. But rare_terms will use it... I can take a closer look and see if we can do the same thing :

I pushed a change that refactors MergingBucketsDeferringCollector to be an extension of BestBucketsDeferringCollector in order to allocate the builders lazily in both cases:
ce7f447
WDYT ?

@polyfractal
Copy link
Contributor

Well that was easy enough, thanks @jimczi! Looks like it should integrate smoothly with the changes I have on my rare_terms branch as well.

@jimczi jimczi merged commit 5b1de3c into elastic:master Jun 19, 2019
@jimczi jimczi deleted the enhancements/best_buckets_collector_lazy branch June 19, 2019 19:25
jimczi added a commit that referenced this pull request Jun 19, 2019
While investigating memory consumption of deeply nested aggregations for #43091
the memory used to keep track of the doc ids and buckets in the BestBucketsDeferringCollector
showed up as one of the main contributor. In my tests half of the memory held in the
 BestBucketsDeferringCollector is associated to segments that don't have matching docs
 in the selected buckets. This is expected on fields that have a big cardinality since each
 bucket can appear in very few segments. By allocating the builders lazily this change
 reduces the memory consumption by a factor 2 (from 1GB to 512MB), hence reducing the
impact on gcs for these volatile allocations. This commit also switches the PackedLongValues.Builder
with a RoaringDocIdSet in order to handle very sparse buckets more efficiently.

I ran all my tests on the `geoname` rally track with the following query:

````
{
    "size": 0,
    "aggs": {
        "country_population": {
            "terms": {
                "size": 100,
                "field": "country_code.raw"
            },
            "aggs": {
                "admin1_code": {
                    "terms": {
                        "size": 100,
                        "field": "admin1_code.raw"
                    },
                    "aggs": {
                        "admin2_code": {
                            "terms": {
                                "size": 100,
                                "field": "admin2_code.raw"
                            },
                            "aggs": {
                                "sum_population": {
                                    "sum": {
                                        "field": "population"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
````
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants