Skip to content

HIVE-24203: Implement stats annotation rule for the LateralViewJoinOperator - #1531

Merged
kgyrtkirk merged 5 commits into
apache:masterfrom
okumin:HIVE-24203
Jan 13, 2021
Merged

kgyrtkirk merged 5 commits into
apache:masterfrom
okumin:HIVE-24203

Conversation

@okumin

@okumin okumin commented Sep 28, 2020

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Estimate statistics of LATERAL VIEW correctly.

StatsRulesProcFactory doesn't have any rules to handle a JOIN by LATERAL VIEW.
This can cause an underestimation in case that UDTF in LATERAL VIEW generates multiple rows.

Why are the changes needed?

Significant underestimation can happen when LATERAL VIEW increases the number of records a lot and the source table has large.

Does this PR introduce any user-facing change?

No.

How was this patch tested?

Added one test case.

@@ -0,0 +1,38 @@
set hive.fetch.task.conversion=none;

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.

To make EXPLAIN show Statistics. I'm thinking to create another ticket and add this line to other annotate_stats_*.q.

e.g. https://github.com/apache/hive/blob/master/ql/src/test/results/clientpositive/llap/annotate_stats_select.q.out

}

/**
* LateralViewJoinOperator joins the output of select with the output of UDTF.

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.

Could you provide a bit more details about what the rule does? Most of the other rules in this class give a general overview of the cost model they implement.

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.

@zabetak Thanks for taking a look!
I added a description. Please feel free to ask me if something doesn't make sense.

*
* For each row of the source, the left branch just picks columns and the right branch processes UDTF.
* And then LVJ joins a row from the left branch with rows from the right branch.
* The join has one-to-many relationship since UDTF can generate multiple rows.

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.

Just leaving a note. I took a quick look at the UDTF logic and it seems the selectivity is hardcoded via config. It seems the outer flag is not taken into account either, which could be a straightforward improvement for the estimates, i.e., UDFT will produce at least as many rows as it receives.

return null;
}

final Statistics selectStats = parents.get(LateralViewJoinOperator.SELECT_TAG).getStatistics().clone();

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.

Do you need to clone them? Are you modifying them? (Same for next line)

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.

As for udtfStats, we can totally avoid clone.
As for udtfStats, its column stats will be updated. However, looks like StatsUtils.getColStatisticsFromExprMap clones them?
Anyway I think we can remove them if CI passes. I will try it.

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.

Nothing was not unexpectedly broken. CI failed but it would not be related to this PR...

Lateral View Join Operator
outputColumnNames: _col0, _col1, _col5, _col6
Statistics: Num rows: 0 Data size: 24 Basic stats: PARTIAL Column stats: NONE
Statistics: Num rows: 0 Data size: 24 Basic stats: PARTIAL Column stats: COMPLETE

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.

With clone, the following condition is not satisfied since the basic stats of parent operators are PARTIAL.

if (satisfyPrecondition(selectStats) && satisfyPrecondition(udtfStats)) {

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.

BTW, it would be better that the UDTF rule puts one in num rows in case that it becomes zero.

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.

definetly - I don't think it will be 0 in reality!

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.

This is an edge case since HIVE_STATS_UDTF_FACTOR is greater than or equal to 1. Anyway, I created a ticket.
https://issues.apache.org/jira/browse/HIVE-24240

final Statistics selectStats = parents.get(LateralViewJoinOperator.SELECT_TAG).getStatistics();
final Statistics udtfStats = parents.get(LateralViewJoinOperator.UDTF_TAG).getStatistics();

final double factor = (double) udtfStats.getNumRows() / (double) selectStats.getNumRows();

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 know selectStats.getNumRows() should not be zero - but just in case... could you also add the resulting logic as StatsUtils or something like that?

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.

Added steps to check both numbers and ensure at least one record on stats.
5039634

Comment on lines +2983 to +2988
joinedStats = applyRuntimeStats(aspCtx.getParseContext().getContext(), joinedStats, lop);
lop.setStatistics(joinedStats);

if (LOG.isDebugEnabled()) {
LOG.debug("[0] STATS-" + lop.toString() + ": " + joinedStats.extendedToString());
}

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.

this seems to be a common expression in both branches of the if - could you move it outside?

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 wonder if we should switch [0] or [1] based on a condition. I can see some rules use a different marker based on maybe the existence of column stats.

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 don't know what's the point of these [0]/[1] markers; from one of the historical commits it seems to me like these are some kind of "log message indexes" inside the method ....
I think we could stop doing that...

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 also agree and I did that.
d333d5d

colStats.setNumNulls(StatsUtils.safeMult(colStats.getNumNulls(), factor));
// When factor > 1, the same records are duplicated and countDistinct never changes.
if (factor < 1.0) {
colStats.setCountDistint(StatsUtils.safeMult(colStats.getCountDistint(), factor));

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 should make sure that NDV is at least 1 in case numrows is >0

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.

Now I think this is available for this purpose if we add updating num trues and num falses?

public static void updateStats(Statistics stats, long newNumRows,
boolean useColStats, Operator<? extends OperatorDesc> op,
Set<String> affectedColumns) {
if (newNumRows < 0) {
LOG.debug("STATS-" + op.toString() + ": Overflow in number of rows. "
+ newNumRows + " rows will be set to Long.MAX_VALUE");
newNumRows = StatsUtils.getMaxIfOverflow(newNumRows);
}
if (newNumRows == 0) {
LOG.debug("STATS-" + op.toString() + ": Equals 0 in number of rows. "
+ newNumRows + " rows will be set to 1");
newNumRows = 1;
}
long oldRowCount = stats.getNumRows();
double ratio = (double) newNumRows / (double) oldRowCount;
stats.setNumRows(newNumRows);
if (useColStats) {
List<ColStatistics> colStats = stats.getColumnStats();
for (ColStatistics cs : colStats) {
long oldDV = cs.getCountDistint();
if (affectedColumns.contains(cs.getColumnName())) {
long newDV = oldDV;
// if ratio is greater than 1, then number of rows increases. This can happen
// when some operators like GROUPBY duplicates the input rows in which case
// number of distincts should not change. Update the distinct count only when
// the output number of rows is less than input number of rows.
if (ratio <= 1.0) {
newDV = (long) Math.ceil(ratio * oldDV);
}
cs.setCountDistint(newDV);
cs.setFilterColumn();
oldDV = newDV;
}
if (oldDV > newNumRows) {
cs.setCountDistint(newNumRows);
}
long newNumNulls = Math.round(ratio * cs.getNumNulls());
cs.setNumNulls(newNumNulls > newNumRows ? newNumRows: newNumNulls);
}
stats.setColumnStats(colStats);
long newDataSize = StatsUtils.getDataSizeFromColumnStats(newNumRows, colStats);
stats.setDataSize(StatsUtils.getMaxIfOverflow(newDataSize));
} else {
long newDataSize = (long) (ratio * stats.getDataSize());
stats.setDataSize(StatsUtils.getMaxIfOverflow(newDataSize));
}
}

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.

This method may include additional logging and logics to optimize JOIN such as cs.setFilterColumn. It would be better to implement a simple and separate utility.

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.

Ceiled. I moved this method since I'd like to reuse it for HIVE-24240.
5039634

Lateral View Join Operator
outputColumnNames: _col0, _col1, _col5, _col6
Statistics: Num rows: 0 Data size: 24 Basic stats: PARTIAL Column stats: NONE
Statistics: Num rows: 0 Data size: 24 Basic stats: PARTIAL Column stats: COMPLETE

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.

definetly - I don't think it will be 0 in reality!


final double factor = (double) udtfStats.getNumRows() / (double) selectStats.getNumRows();
final long udtfNumRows = Math.max(udtfStats.getNumRows(), 1);
final double factor = (double) udtfNumRows / (double) Math.max(selectStats.getNumRows(), 1);

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.

factor will be greater than 0.0 and must not 0 or infinity.

@okumin

okumin commented Oct 14, 2020

Copy link
Copy Markdown
Contributor Author

@kgyrtkirk I have updated some points so that # of rows will never be 0.
Could you please have a look when you have a chance?

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Feel free to reach out on the dev@hive.apache.org list if the patch is in need of reviews.

@github-actions github-actions Bot added the stale label Dec 14, 2020
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