Automatic sizing for GroupBy dictionaries.#12763
Conversation
Merging and selector dictionary sizes currently both default to 100MB. This is not optimal, because it can lead to OOM on small servers and insufficient resource utilization on larger servers. It also invites end users to try to tune it when queries run out of dictionary space, which can make things worse if the end user sets it to too high. So, this patch: - Adds automatic tuning for selector and merge dictionaries. Selectors use up to 15% of the heap and merge buffers use up to 30% of the heap (aggregate across all queries). - Updates out-of-memory error messages to emphasize enabling disk spilling vs. increasing memory parameters. With the memory parameters automatically sized, it is more likely that an end user will get benefit from enabling disk spilling. - Removes the query context parameters that allow lowering of configured dictionary sizes. These complicate the calculation, and I don't see a reasonable use case for them.
vogievetsky
left a comment
There was a problem hiding this comment.
I pair programmed with Gian on this (read: sat there and watched while making snarky comments about Java)
| private static final double MERGING_DICTIONARY_HEAP_FRACTION = 0.3; | ||
| private static final double SELECTOR_DICTIONARY_HEAP_FRACTION = 0.10; | ||
| private static final long MIN_AUTOMATIC_DICTIONARY_SIZE = 1_000_000; | ||
| private static final long MAX_AUTOMATIC_DICTIONARY_SIZE = 1_000_000_000; |
There was a problem hiding this comment.
These two MIN and MAX ensure the range of automatic computed value, I think it's better to document them.
There was a problem hiding this comment.
BTW, these two are in decimal byte format, should we use binary byte which is 1024 * 1024 and 1024 * 1024 * 1024 ?
There was a problem hiding this comment.
Yeah, I agree. I changed MIN to 1 byte and didn't document it (it seems unnecessary to mention). I documented the 1GB max.
There was a problem hiding this comment.
How about defining the MAX as 1GiB instead of 1GB? I think 1GiB would be less confusing.
There was a problem hiding this comment.
Ah, I missed the comment about binary bytes. I dunno if there is a reason to use binary bytes. I kept it decimal since they both seem fine to me.
There was a problem hiding this comment.
Hmm. Why is it less confusing to use 1GiB instead of 1GB? As one data point, the old default (100MB) was in decimal.
There was a problem hiding this comment.
When representing a size of memory, binary format is commonly used while decimal format is usually for disk size.
It's OK to keep it in decimal format.
There was a problem hiding this comment.
Ah, I see. I wrote it as decimal since I am not familiar with that convention. I'll leave it as is for this patch.
| private static final String CTX_KEY_NUM_PARALLEL_COMBINE_THREADS = "numParallelCombineThreads"; | ||
| private static final String CTX_KEY_MERGE_THREAD_LOCAL = "mergeThreadLocal"; | ||
| private static final double MERGING_DICTIONARY_HEAP_FRACTION = 0.3; | ||
| private static final double SELECTOR_DICTIONARY_HEAP_FRACTION = 0.10; |
There was a problem hiding this comment.
Why it's 0.3 and 0.1 for these two variables respectively? Could you add some explanation here?
There was a problem hiding this comment.
I added this explanation:
+ // Constants for sizing merging and selector dictionaries. Rationale for these constants:
+ // 1) In no case do we want total aggregate dictionary size to exceed 40% of max memory.
+ // 2) In no case do we want any dictionary to exceed 1GB of memory: if heaps are giant, better to spill at
+ // "reasonable" sizes rather than get giant dictionaries. (There is probably some other reason the user
+ // wanted a giant heap, so we shouldn't monopolize it with dictionaries.)
+ // 3) Use somewhat more memory for merging dictionary vs. selector dictionaries, because if a merging
+ // dictionary is full we must spill to disk, whereas if a selector dictionary is full we simply emit
+ // early to the merge buffer. So, a merging dictionary filling up has a more severe impact on
+ // query performance.
| @JsonProperty | ||
| // Size of on-heap string dictionary for merging, per-query; when exceeded, partial results will be spilled to disk | ||
| private long maxMergingDictionarySize = 100_000_000L; | ||
| private long maxMergingDictionarySize = AUTOMATIC; |
There was a problem hiding this comment.
I think we can change the type of maxSelectorDictionarySize and maxMergingDictionarySize from long to HumanReadableBytes to support human-readable format configuration.
There was a problem hiding this comment.
Good idea. I changed it.
| getMaxMergingDictionarySize() | ||
| )).longValue(), | ||
| getMaxMergingDictionarySize() | ||
| ); |
There was a problem hiding this comment.
Since these two are removed from QueryContext, should we document it in the release notes?
There was a problem hiding this comment.
Yes, we should. I added a release notes label.
|
Thanks for reviewing @FrankChen021 @vogievetsky |
* Automatic sizing for GroupBy dictionary sizes. Merging and selector dictionary sizes currently both default to 100MB. This is not optimal, because it can lead to OOM on small servers and insufficient resource utilization on larger servers. It also invites end users to try to tune it when queries run out of dictionary space, which can make things worse if the end user sets it to too high. So, this patch: - Adds automatic tuning for selector and merge dictionaries. Selectors use up to 15% of the heap and merge buffers use up to 30% of the heap (aggregate across all queries). - Updates out-of-memory error messages to emphasize enabling disk spilling vs. increasing memory parameters. With the memory parameters automatically sized, it is more likely that an end user will get benefit from enabling disk spilling. - Removes the query context parameters that allow lowering of configured dictionary sizes. These complicate the calculation, and I don't see a reasonable use case for them. * Adjust tests. * Review adjustments. * Additional comment. * Remove unused import.
Merging and selector dictionary sizes currently both default to 100MB.
This is not optimal, because it can lead to OOM on small servers and
insufficient resource utilization on larger servers. It also invites
end users to try to tune it when queries run out of dictionary space,
which can make things worse if the end user sets it to too high.
So, this patch:
Adds automatic tuning for selector and merge dictionaries. Selectors
use up to 10% of the heap and merge buffers use up to 30% of the heap
(aggregate across all queries).
Updates out-of-memory error messages to emphasize enabling disk
spilling vs. increasing memory parameters. With the memory parameters
automatically sized, it is more likely that an end user will get
benefit from enabling disk spilling.
Removes the query context parameters that allow lowering of configured
dictionary sizes. These complicate the calculation, and I don't see a
reasonable use case for them.