Skip to content

Conversation

@amogh-jahagirdar
Copy link
Contributor

This PR contains the updated expire snapshots procedure for branching and tagging. Implements the algorithm outlined here https://github.com/apache/iceberg/blob/master/format/spec.md#snapshot-retention-policy.

@github-actions github-actions bot added the core label Apr 18, 2022
@amogh-jahagirdar
Copy link
Contributor Author

Still missing unit tests on this but wanted to get feedback on the code while I add the tests.

@amogh-jahagirdar amogh-jahagirdar force-pushed the snapshot-ref-retention branch 4 times, most recently from f76a8ef to 14f60f9 Compare May 2, 2022 00:53
@amogh-jahagirdar amogh-jahagirdar force-pushed the snapshot-ref-retention branch 3 times, most recently from ec89b89 to cf77cd9 Compare May 2, 2022 03:47
@amogh-jahagirdar
Copy link
Contributor Author

amogh-jahagirdar commented May 31, 2022

Ok, after stepping through the debugger I think I see what's going on. The failing incremental scan test can't find a staged snapshot because the updated expiration logic actually will remove it.

Expire snapshots previously would retain staged snapshots so long as it's not outside of the "expire older than" age. However, in this updated logic, we're not considering that for unreferenced staged snapshots. https://github.com/apache/iceberg/blob/master/core/src/main/java/org/apache/iceberg/RemoveSnapshots.java#L173
This logic will retain any snapshot within the expire older than, which I think the updated procedure should do for any snapshot which is not part of any branches.

Currently age retention is only applied to snapshots which are part of branches. I think we definitely want to preserve the existing behavior to avoid deleting staged snapshots that people may expect should be retained based on existing behavior, so I will add logic for that.

@rdblue @jackye1995 let me know if you see any other cases around this.

@amogh-jahagirdar amogh-jahagirdar force-pushed the snapshot-ref-retention branch 2 times, most recently from ddaf479 to 1182aa5 Compare May 31, 2022 05:57
@amogh-jahagirdar amogh-jahagirdar force-pushed the snapshot-ref-retention branch from 1182aa5 to cb56150 Compare May 31, 2022 06:28
return retainedRefs;
}

private Set<Long> computeDanglingSnapshotsToRetain(Map<SnapshotRef, Set<Long>> branchSnapshots) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rdblue Let me know what you think of this logic to handle the case of unreferenced staged snapshots which should still be retained based on the default expiration age. I reorganized the code to minimize the computation of branch snapshots. I also should add a test for this case.

Copy link
Contributor Author

@amogh-jahagirdar amogh-jahagirdar May 31, 2022

Choose a reason for hiding this comment

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

This should be updated to be any unreferenced snapshot not just branch history. If there's a staged snapshot which is tagged, that should not be counted as a dangling snapshot to retain.

Edit: Updated to handle this case and added a test

@amogh-jahagirdar amogh-jahagirdar force-pushed the snapshot-ref-retention branch 6 times, most recently from fc07862 to 031bbb1 Compare May 31, 2022 17:28
@rdblue
Copy link
Contributor

rdblue commented May 31, 2022

@amogh-jahagirdar, I agree that we shouldn't remove unreferenced snapshots until they are older than the default expiration timestamp. Good catch!

@amogh-jahagirdar amogh-jahagirdar requested a review from rdblue May 31, 2022 23:15
Map<SnapshotRef, Set<Long>> refSnapshots = computeRefSnapshots(base.refs().values());

// Identify unreferenced snapshots which should be retained
Set<Long> unreferencedSnapshotsToRetain = computeUnreferencedSnapshotsToRetain(refSnapshots);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be passed a single set of retained snapshots and look for any others, rather than using a complex map. Just do this analysis last, after you know all the other snapshots that are retained for other reasons.

Copy link
Contributor Author

@amogh-jahagirdar amogh-jahagirdar Jun 1, 2022

Choose a reason for hiding this comment

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

Yeah I'm still thinking if there's a way to simplify this.

I think we'll need the structure for maintaining what are the referenced snapshots ("referenced" meaning for a branch all it's ancestors, and for a tag, the snapshot it refers to). I don't think we can construct the snapshot retention set and then after that look through all the table's snapshots , and correctly derive which snapshots are the unreferenced snapshots which should still be retained based on expiration age.

The reason is some of the table's snapshots may no longer need to be retained due to branch retention policy but if we go through those snapshots and they're still within defaultExpirationAge we'd retain them which I don't think we want since the branch has it's own lifecycle. I think if a snapshot was part of a branch which expired due to it's own retention policy then we don't want to retain it on the basis of the defaultExpireOlderThan.

In other words, by just using the retention set and the existing table snapshots I don't think we can differentiate between a snapshot which should actually be removed based on branch retention or if it's a snapshot which was never part of any reference.

I think we need to be able to identify what are the snapshots which are not part of any branch lineage or are tagged and for that we need a set of all the snapshots which encapsulate all snapshots which are currently part of a reference . We do the grouping so that it can be reused when determining which branch snapshots to keep (so that the ancestors don't have to be iterated through again). Let me know what you think @rdblue !

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with you on the behavior, but I think there's a simpler way to implement it. I went ahead and added it in amogh-jahagirdar#1, and fixed some tests. Can you review and merge that PR so we can get this in?

Copy link
Contributor Author

@amogh-jahagirdar amogh-jahagirdar Jun 14, 2022

Choose a reason for hiding this comment

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

Merged! I think the new approach simplifies the code. what I was originally going for was not having to traverse the branch ancestors multiple times, which is why the map was retained but that may be overkill. Thanks for adding more tests!

@amogh-jahagirdar amogh-jahagirdar force-pushed the snapshot-ref-retention branch from 031bbb1 to 5036b0f Compare June 1, 2022 06:26
…ng (#1)

Cleanup remaining issues with snapshot expiration
@rdblue rdblue merged commit 98dc9fe into apache:master Jun 14, 2022
@rdblue
Copy link
Contributor

rdblue commented Jun 14, 2022

Thanks, @amogh-jahagirdar!

@namrathamyske
Copy link
Contributor

namrathamyske commented Jul 6, 2022

@amogh-jahagirdar @rdblue With this PR, the breaking change is to set cleanExpiredFiles to false if the table has more than one ref. Is this Issue addressed anywhere?

@amogh-jahagirdar
Copy link
Contributor Author

amogh-jahagirdar commented Jul 7, 2022

@namrathamyske

With this PR, the breaking change is to set cleanExpiredFiles to false if the table has more than one ref.

No, the operation will fail if cleanExpiredFiles is set and there are multiple retained refs. It shouldn't break any existing cases prior to the addition of branching/tagging and should be backwards compatible but please report in case you ran into any issues!

See this comment . To do a correct removal of files with multiple references, a reachability analysis needs to be done which requires quite a bit more implementation which is why for this PR we opted for just failing if cleanExpiredFiles is set and there are multiple branches.

@namrathamyske
Copy link
Contributor

namrathamyske commented Jul 8, 2022

@amogh-jahagirdar Thanks for the info. Earlier i never set cleanExpiredFiles. It defaults to True. So i did not get any exception when I try expire snapshots. Now i need to explicitly set cleanExpiredFiles as False when I have multiple refs.

.run(item -> {
TableMetadata updated = internalApply();
if (cleanExpiredFiles && updated.refs().size() > 1) {
throw new UnsupportedOperationException("Cannot incrementally clean files for tables with more than 1 ref");
Copy link
Contributor

@zinking zinking Oct 12, 2022

Choose a reason for hiding this comment

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

@amogh-jahagirdar I have a simple use case blocked by this line.

I am tagging one of the snapshots on the mainline as never expire, but as soon as I tag it, the normal expire procedure (expire with clean) stopped working, as there are two refs now.

is the constraint correct here ? because in my scenario, I am merely indicating SKIP this snapshot, with other terms stay the same.

Copy link
Contributor

@zinking zinking Oct 12, 2022

Choose a reason for hiding this comment

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

I think, if there's only one branch main, and tags are all on main, it is safe to carry on.
is that correct ? otherwise when is the expired file supposed to be cleanedUp ? through removeOrphan?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants