-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Spark 3.3: Output the net changes across snapshots in CDC #7326
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 3 commits
978c396
ccdcde9
dd95e5f
79efbaf
cec8dbf
10292fc
516040f
058eb7d
1bcff45
95fa7a6
2b3c5de
5bab4e1
414285c
72617a6
7b20c7c
15d8944
bca5bba
ed90942
ac94c5d
7a5607a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,14 +48,16 @@ | |
| */ | ||
| class RemoveCarryoverIterator extends ChangelogIterator { | ||
| private final int[] indicesToIdentifySameRow; | ||
| private final StructType rowType; | ||
|
|
||
| private Row cachedDeletedRow = null; | ||
| private long deletedRowCount = 0; | ||
| private Row cachedNextRecord = null; | ||
|
|
||
| RemoveCarryoverIterator(Iterator<Row> rowIterator, StructType rowType) { | ||
| super(rowIterator, rowType); | ||
| this.indicesToIdentifySameRow = generateIndicesToIdentifySameRow(rowType.size()); | ||
| this.rowType = rowType; | ||
|
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: if we pass the variable to the super class , can we just store it in super class and get it via super method? (I think cleaner if subclass has only fields that it only knows about). Also looks like both subclass do this. |
||
| this.indicesToIdentifySameRow = generateIndicesToIdentifySameRow(); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -139,8 +141,8 @@ private boolean hasCachedDeleteRow() { | |
| return cachedDeletedRow != null; | ||
| } | ||
|
|
||
| private int[] generateIndicesToIdentifySameRow(int columnSize) { | ||
| int[] indices = new int[columnSize - 1]; | ||
| private int[] generateIndicesToIdentifySameRow() { | ||
| int[] indices = new int[rowType.size() - 1]; | ||
| for (int i = 0; i < indices.length; i++) { | ||
| if (i < changeTypeIndex()) { | ||
| indices[i] = i; | ||
|
|
@@ -151,13 +153,21 @@ private int[] generateIndicesToIdentifySameRow(int columnSize) { | |
| return indices; | ||
| } | ||
|
|
||
| private boolean isSameRecord(Row currentRow, Row nextRow) { | ||
| for (int idx : indicesToIdentifySameRow) { | ||
| protected boolean isSameRecord(Row currentRow, Row nextRow) { | ||
|
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. Can we move this to base class?
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. The class
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. Code-wise, I am thinking that it is a bit harder to read to have RemoveNetCarryoverIterator to extend RemoveCarryoverIterator. So was thinking we can move have an extra base class, then its easier to see what is the different methods and what is same, not sure what you think. Now its a bit trickier to see that.
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. Move it to ChangelogIterator, and let RemoveNetCarryoverIterator inherit ChangelogIterator directly. It should be easier to read. |
||
| for (int idx : indicesToIdentifySameRow()) { | ||
|
flyrain marked this conversation as resolved.
Outdated
|
||
| if (isDifferentValue(currentRow, nextRow, idx)) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| protected StructType rowType() { | ||
|
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. Can we move these up to base class ?
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. The class
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. Removed it. |
||
| return rowType; | ||
| } | ||
|
|
||
| protected int[] indicesToIdentifySameRow() { | ||
| return indicesToIdentifySameRow; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.apache.iceberg.spark; | ||
|
|
||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import org.apache.iceberg.MetadataColumns; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.Lists; | ||
| import org.apache.spark.sql.Row; | ||
| import org.apache.spark.sql.types.StructType; | ||
|
|
||
| /** | ||
| * This class computes the net changes across multiple snapshots. It takes a row iterator, and | ||
|
flyrain marked this conversation as resolved.
Outdated
|
||
| * assumes the following: | ||
| * | ||
| * <ul> | ||
| * <li>The row iterator is partitioned by all columns. | ||
| * <li>The row iterator is sorted by all columns, change order, and change type. The change order | ||
| * is 1-to-1 mapping to snapshot id. | ||
| * </ul> | ||
| */ | ||
| public class RemoveNetCarryoverIterator extends RemoveCarryoverIterator { | ||
|
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. We've got this new iterator that can do everything
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. Why do we need to use the list here? Don't we only need to know the first row and the last?
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. Look a bit more. We should be able to archive the same without a list. Let me post a new commit soon.
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 used the list since we can either cache a delete row or a insert row. But looking a bit more. It is a either-or at any time. We will never have cached rows with mixed insert/delete, since we will remove them as a pair in that case. For example, cached rows could be or But it will never be With that, a cached row count is good enough. Removed the list in the new commit. This new iterator can do more than its parent with the same cost. I think it is good idea to just keep one. Thoughts?
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 think one iterator is fine
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. let me merge them.
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. The code becomes entangled when I merge them. Too many if-else clauses. We'd better off leave as it is. |
||
|
|
||
| private final List<Row> cachedRows = Lists.newArrayList(); | ||
| private final int[] indicesToIdentifySameRow; | ||
|
|
||
| private Row cachedNextRow = null; | ||
|
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: rely on java default |
||
|
|
||
| protected RemoveNetCarryoverIterator(Iterator<Row> rowIterator, StructType rowType) { | ||
| super(rowIterator, rowType); | ||
| this.indicesToIdentifySameRow = generateIndicesToIdentifySameRow(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasNext() { | ||
| if (!cachedRows.isEmpty()) { | ||
| return true; | ||
| } | ||
|
|
||
| if (cachedNextRow != null) { | ||
| return true; | ||
| } | ||
|
|
||
| return rowIterator().hasNext(); | ||
| } | ||
|
|
||
| @Override | ||
| public Row next() { | ||
| // if there are cached rows, return one of them from the beginning | ||
| if (!cachedRows.isEmpty()) { | ||
| return cachedRows.remove(0); | ||
| } | ||
|
|
||
| Row currentRow = getCurrentRow(); | ||
|
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. Same for 'cachedRow'. Can we just have currentRow as member variable, and do this.currentRow = getCurrentRow();
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. Didn't get it. Could you elaborate it? |
||
|
|
||
| // if there is no next row, return the current row | ||
|
flyrain marked this conversation as resolved.
Outdated
|
||
| if (!rowIterator().hasNext()) { | ||
| return currentRow; | ||
| } | ||
|
|
||
| Row nextRow = rowIterator().next(); | ||
|
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. Not 100% sure, but instead of 'cachedNextRow', can we just have 'nextRow' as the member variable. Then 'this.nextRow = rowIterator.next()'? I think 'cachedNextRow' is used only in getCurrentRow() which is above this code. So henceforth should be the same?
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. When
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. Here is an example, we need to drain 'a' first, then get the cached next row (1, 'b', insert), and try to match it with the row (1, 'b', delete)
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. Maybe not clear: I think it will work to remove 'currentRow'. But I am not sure if I am too aggressive for removing 'nextRow'. Please check if I made a mistake.
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. Made the change accordingly. Thanks for the suggestion |
||
|
|
||
| cachedRows.add(currentRow); | ||
| // if they are the same row, remove the cached row or stack to the cached row | ||
|
flyrain marked this conversation as resolved.
Outdated
|
||
| while (isSameRecord(currentRow, nextRow)) { | ||
| if (matched(currentRow, nextRow)) { | ||
| // if matched, remove both rows, remove the last row in the cache | ||
| cachedRows.remove(cachedRows.size() - 1); | ||
| nextRow = null; | ||
| } else { | ||
| // stack the row into the cache | ||
| cachedRows.add(nextRow); | ||
| nextRow = null; | ||
|
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. Maybe missing something, but why do we need to nullify nextRow here? And in both if/else cases?
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. we cached the next row by doing Then caught by
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 keep both
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 mean, let's move it out of the if/else. Even Intellij suggests 'Common part can be extracted from 'if'
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. Still keep them there. I will consider a plus for people to understand the code, though there is a bit duplication.
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. Consolidated them to one line. |
||
| } | ||
|
|
||
| // break the loop if there is no next row or the cache is empty | ||
|
flyrain marked this conversation as resolved.
Outdated
|
||
| if (cachedRows.isEmpty() || !rowIterator().hasNext()) { | ||
| break; | ||
| } | ||
|
|
||
| // get the current row from the cache, and get the next row from the iterator for the next | ||
| // loop | ||
| currentRow = cachedRows.get(cachedRows.size() - 1); | ||
| nextRow = rowIterator().next(); | ||
| } | ||
|
|
||
| // if they are different rows, hit the boundary, cache the next row | ||
| cachedNextRow = nextRow; | ||
| return null; | ||
| } | ||
|
|
||
| private Row getCurrentRow() { | ||
| Row currentRow; | ||
| if (cachedNextRow != null) { | ||
| currentRow = cachedNextRow; | ||
| cachedNextRow = null; | ||
| } else { | ||
| currentRow = rowIterator().next(); | ||
| } | ||
| return currentRow; | ||
| } | ||
|
|
||
| private boolean matched(Row currentRow, Row nextRow) { | ||
|
flyrain marked this conversation as resolved.
Outdated
|
||
| return (nextRow.getString(changeTypeIndex()).equals(INSERT) | ||
|
flyrain marked this conversation as resolved.
Outdated
|
||
| && currentRow.getString(changeTypeIndex()).equals(DELETE)) | ||
| || (nextRow.getString(changeTypeIndex()).equals(DELETE) | ||
| && currentRow.getString(changeTypeIndex()).equals(INSERT)); | ||
| } | ||
|
|
||
| private int[] generateIndicesToIdentifySameRow() { | ||
| int changeOrdinalIndex = rowType().fieldIndex(MetadataColumns.CHANGE_ORDINAL.name()); | ||
|
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'm a bit lost why this method is not the same as RemoveCarryoverIterator? Are 'changeOrdinalIndex' and 'snapshotIdIndex' not in the rows of the other iterator?
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. The indices generated here is used for comparing if two records are the same. In
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. Cant we share the code by doing the same thing and making a set to identify metadata column index? From RemoveCarryoverIterator, the set will be only changeTypeIndex? Let me know if I miss something.
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. Extract the method out. Thanks for the suggestion. |
||
| int snapshotIdIndex = rowType().fieldIndex(MetadataColumns.COMMIT_SNAPSHOT_ID.name()); | ||
|
|
||
| int[] indices = new int[rowType().size() - 3]; | ||
|
|
||
| for (int i = 0, j = 0; i < indices.length; i++) { | ||
| if (i != changeTypeIndex() && i != changeOrdinalIndex && i != snapshotIdIndex) { | ||
| indices[j] = i; | ||
| j++; | ||
| } | ||
| } | ||
| return indices; | ||
| } | ||
|
|
||
| @Override | ||
| protected int[] indicesToIdentifySameRow() { | ||
| return indicesToIdentifySameRow; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.