Skip to content

Automatic sizing for GroupBy dictionaries.#12763

Merged
gianm merged 5 commits into
apache:masterfrom
gianm:merging-dictionary-size
Jul 11, 2022
Merged

Automatic sizing for GroupBy dictionaries.#12763
gianm merged 5 commits into
apache:masterfrom
gianm:merging-dictionary-size

Conversation

@gianm

@gianm gianm commented Jul 9, 2022

Copy link
Copy Markdown
Contributor

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.

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 vogievetsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These two MIN and MAX ensure the range of automatic computed value, I think it's better to document them.

@FrankChen021 FrankChen021 Jul 9, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

BTW, these two are in decimal byte format, should we use binary byte which is 1024 * 1024 and 1024 * 1024 * 1024 ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I agree. I changed MIN to 1 byte and didn't document it (it seems unnecessary to mention). I documented the 1GB max.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How about defining the MAX as 1GiB instead of 1GB? I think 1GiB would be less confusing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@gianm gianm Jul 11, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm. Why is it less confusing to use 1GiB instead of 1GB? As one data point, the old default (100MB) was in decimal.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why it's 0.3 and 0.1 for these two variables respectively? Could you add some explanation here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can change the type of maxSelectorDictionarySize and maxMergingDictionarySize from long to HumanReadableBytes to support human-readable format configuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea. I changed it.

getMaxMergingDictionarySize()
)).longValue(),
getMaxMergingDictionarySize()
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since these two are removed from QueryContext, should we document it in the release notes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, we should. I added a release notes label.

@gianm

gianm commented Jul 11, 2022

Copy link
Copy Markdown
Contributor Author

Thanks for reviewing @FrankChen021 @vogievetsky

@gianm
gianm merged commit 97207cd into apache:master Jul 11, 2022
@gianm
gianm deleted the merging-dictionary-size branch July 11, 2022 15:20
@abhishekagarwal87 abhishekagarwal87 added this to the 24.0.0 milestone Aug 26, 2022
riovic918data pushed a commit to riovic918data/druid that referenced this pull request Jun 12, 2026
* 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.
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.

4 participants