-
Notifications
You must be signed in to change notification settings - Fork 4.8k
HIVE-24167: TPC-DS query 14 fails while generating plan for the filter #5037
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
Changes from all commits
217b26d
6ed384e
dbfe700
ebc0278
393bc43
e43fe90
e5c26bd
e8b694f
a95ebad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,6 @@ | |
| import java.lang.reflect.Modifier; | ||
| import java.util.ArrayList; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.IdentityHashMap; | ||
|
|
@@ -200,54 +199,46 @@ public void merge(Object o1, Object o2) { | |
| } | ||
|
|
||
| private void link(Object o1, Object o2, boolean mayMerge) { | ||
|
|
||
| Set<Object> keySet = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()); | ||
| keySet.add(o1); | ||
| keySet.add(o2); | ||
| keySet.add(getKeyFor(o1)); | ||
| keySet.add(getKeyFor(o2)); | ||
|
|
||
| Set<EquivGroup> mGroups = Collections.newSetFromMap(new IdentityHashMap<EquivGroup, Boolean>()); | ||
|
|
||
| for (Object object : keySet) { | ||
| EquivGroup group = objectMap.get(object); | ||
| if (group != null) { | ||
| mGroups.add(group); | ||
| } | ||
| // Caches signatures on the first access. A signature of an Operator could change as optimizers could mutate it, | ||
| // keeping its semantics. The current implementation caches the signature before optimizations so that we can | ||
| // link Operators with their signatures at consistent timing. | ||
| registerSignature(o1); | ||
| registerSignature(o2); | ||
|
Comment on lines
+205
to
+206
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Linking operators implicitly (using signatures) was added by @kgyrtkirk in HIVE-18926. This change reverts that logic and takes signatures out of the equation for determining equivalent groups. My understanding is that if this change goes in operators can be linked with signatures only explicitly. I don't see test failures and .q.out changes due to this so it seems that the change does not have a big impact on existing use-cases. Moreover, it fixes some known compilation crashes so I consider this an improvement over the current situation. I don't see a significant risk in merging this change but I will let @kgyrtkirk comment in case I am missing something. Assuming that we do not allow implicit linking via signatures I don't know what's the point of registering the signature at this stage. My understanding is that the cache is meant to improve performance not a means to perform equivalence checks. If we don't make use of the signature here then I don't think we should compute it. @okumin although you added some comments justifying the registration I would appreciate some additional clarifications regarding these additions.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
haven't read all of it - but if thats true - please don't do that... what's the issue - is it an equiv violation? can you put the stacktrace somewhere? all these things could enable to back-map runtime statistict up to the calcite join planning phase...but I think that feature was never completed/enabled
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to get a failing test for these changes you would need 2 ops which have conflicting signatures stored into the metastore ; loaded back and they might get applied incorrectly...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's because some optimization can mutate Operators, changing their true signatures. One example I remember is TableScanPPD. I'm not 100% sure which signatures should be used in that case, but it sounds more reasonable and predictable for me to use ones before optimization than ones in the middle of optimization. I would say Operators should not be mutable for the signing purpose, but it is a too fundamental change. I will make a reply to @kgyrtkirk in another thread as I guess those comments were written before reading my gist.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed mentioning what are affected by such optimizations. Many test cases on the following revision failed with That's because it tries to LINK a pre-optimized operator with a post-optimized operator.
Potentially, GA and GB are actually mergeable though the current implementation doesn't do so. |
||
| final EquivGroup linkedGroup1 = objectMap.get(o1); | ||
| final EquivGroup linkedGroup2 = objectMap.get(o2); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these changes will weaken this stuff - so that 2 OPs which have the same signature will be allowed -> that's not good.... |
||
|
|
||
| if (linkedGroup1 == null && linkedGroup2 == null) { | ||
| final EquivGroup group = new EquivGroup(); | ||
| group.add(o1); | ||
| group.add(o2); | ||
| groups.add(group); | ||
| return; | ||
| } | ||
| if (mGroups.size() > 1) { | ||
| if (!mayMerge) { | ||
| throw new RuntimeException("equivalence mapping violation"); | ||
| } | ||
| EquivGroup newGrp = new EquivGroup(); | ||
| newGrp.add(o1); | ||
| newGrp.add(o2); | ||
| for (EquivGroup g : mGroups) { | ||
| for (Object o : g.members) { | ||
| newGrp.add(o); | ||
| } | ||
| } | ||
| groups.add(newGrp); | ||
| groups.removeAll(mGroups); | ||
| } else { | ||
| EquivGroup targetGroup = mGroups.isEmpty() ? new EquivGroup() : mGroups.iterator().next(); | ||
| groups.add(targetGroup); | ||
| targetGroup.add(o1); | ||
| targetGroup.add(o2); | ||
| if (linkedGroup1 == null || linkedGroup2 == null || linkedGroup1 == linkedGroup2) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's all these changes do better than the old? |
||
| final EquivGroup group = linkedGroup1 != null ? linkedGroup1 : linkedGroup2; | ||
| group.add(o1); | ||
| group.add(o2); | ||
| return; | ||
| } | ||
|
|
||
| if (!mayMerge) { | ||
| throw new RuntimeException(String.format( | ||
| "Failed to link %s and %s. This error mostly means a bug of Hive", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: An uncaught Adding the operators which led to exception in the message is good idea, makes sense.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this error message pretty cloudy on what the issue is... I think the sentecte "Equivalence mapping violation" should be there - as that's what the problem is...we have 2 groups; not allowed to put them into the same equiv group -> big trouble... |
||
| o1, o2 | ||
| )); | ||
| } | ||
| EquivGroup newGrp = new EquivGroup(); | ||
| newGrp.add(o1); | ||
| newGrp.add(o2); | ||
| linkedGroup1.members.forEach(newGrp::add); | ||
| linkedGroup2.members.forEach(newGrp::add); | ||
| groups.add(newGrp); | ||
| groups.remove(linkedGroup1); | ||
| groups.remove(linkedGroup2); | ||
| } | ||
|
|
||
| private OpTreeSignatureFactory signatureCache = OpTreeSignatureFactory.newCache(); | ||
|
|
||
| private Object getKeyFor(Object o) { | ||
| if (o instanceof Operator) { | ||
| Operator<?> operator = (Operator<?>) o; | ||
| return signatureCache.getSignature(operator); | ||
| } | ||
| return o; | ||
| } | ||
|
|
||
| public <T> List<T> getAll(Class<T> clazz) { | ||
| List<T> ret = new ArrayList<>(); | ||
| for (EquivGroup g : groups) { | ||
|
|
@@ -256,12 +247,6 @@ public <T> List<T> getAll(Class<T> clazz) { | |
| return ret; | ||
| } | ||
|
|
||
| public void runMapper(GroupTransformer mapper) { | ||
| for (EquivGroup equivGroup : groups) { | ||
| mapper.map(equivGroup); | ||
| } | ||
| } | ||
|
|
||
| public <T> List<T> lookupAll(Class<T> clazz, Object key) { | ||
| EquivGroup group = objectMap.get(key); | ||
| if (group == null) { | ||
|
|
@@ -286,8 +271,13 @@ public Iterator<EquivGroup> iterateGroups() { | |
| } | ||
|
|
||
| public OpTreeSignature getSignatureOf(Operator<?> op) { | ||
| OpTreeSignature sig = signatureCache.getSignature(op); | ||
| return sig; | ||
| return signatureCache.getSignature(op); | ||
| } | ||
|
|
||
| private void registerSignature(Object o) { | ||
| if (o instanceof Operator) { | ||
| getSignatureOf((Operator<?>) o); | ||
| } | ||
| } | ||
|
Comment on lines
+277
to
281
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. whats the point of this method? the cache will compute it if needed...what the point of pre-registering - if that's needed its not a cache anymore! |
||
|
|
||
| public void clearSignatureCache() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --! qt:dataset:src | ||
|
|
||
| set hive.optimize.cte.materialize.threshold=1; | ||
| set hive.optimize.cte.materialize.full.aggregate.only=false; | ||
|
|
||
| EXPLAIN CBO | ||
| WITH materialized_cte AS ( | ||
| SELECT key, value FROM src WHERE key != '100' | ||
| ), | ||
| another_materialized_cte AS ( | ||
| SELECT key, value FROM src WHERE key != '100' | ||
| ) | ||
| SELECT a.key, a.value, b.key, b.value | ||
| FROM materialized_cte a | ||
| JOIN another_materialized_cte b ON a.key = b.key | ||
| ORDER BY a.key; | ||
|
Comment on lines
+6
to
+16
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding (or replacing this with) a traditional |
||
|
|
||
| WITH materialized_cte AS ( | ||
| SELECT key, value FROM src WHERE key != '100' | ||
| ), | ||
| another_materialized_cte AS ( | ||
| SELECT key, value FROM src WHERE key != '100' | ||
| ) | ||
| SELECT a.key, a.value, b.key, b.value | ||
| FROM materialized_cte a | ||
| JOIN another_materialized_cte b ON a.key = b.key | ||
| ORDER BY a.key; | ||
|
Comment on lines
+18
to
+27
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is in the compilation phase so I don't think we need to actually run the query. Consider dropping the execution. If you opt to keep it then I would suggest crafting a much simpler test (without 1K lines in the output). It would be nice to keep the execution time of our test suite as low as possible. Moreover, it is not easy to see what the |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove this comment and don't pre-heat caches unless it gives real benefits
the purpose of the cache is not something like this ; but to prevent
O(N^2)computation ( and also to reduce storage size by making it more serialize friendly...)