-
Notifications
You must be signed in to change notification settings - Fork 9.1k
HADOOP-18013. ABFS: add cloud trash policy with per-schema policy selection #4729
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
Draft
steveloughran
wants to merge
4
commits into
apache:trunk
Choose a base branch
from
steveloughran:azure/HADOOP-18013-resilient-trash-policy
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+566
−60
Draft
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
fa478bb
HADOOP-18013. ABFS: add resilient trash policy.
steveloughran 4cd392b
HADOOP-18013. resilient trash; use fs.getURI over getSchema
steveloughran 224fd8a
HADOOP-18013. preparing to add cleanup to resilient trash
steveloughran 5a16dea
HADOOP-18013. Cloud Trash Policy.
steveloughran File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
220 changes: 220 additions & 0 deletions
220
...ommon-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CloudStoreTrashPolicy.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| /* | ||
| * 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.hadoop.fs; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.Date; | ||
| import java.util.concurrent.atomic.AtomicLong; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import org.apache.hadoop.classification.VisibleForTesting; | ||
| import org.apache.hadoop.conf.Configuration; | ||
| import org.apache.hadoop.fs.statistics.DurationTrackerFactory; | ||
| import org.apache.hadoop.util.DurationInfo; | ||
| import org.apache.hadoop.util.Time; | ||
|
|
||
| import static java.util.Objects.requireNonNull; | ||
| import static org.apache.hadoop.fs.statistics.IOStatisticsSupport.bindToDurationTrackerFactory; | ||
| import static org.apache.hadoop.fs.statistics.StoreStatisticNames.TRASH_CREATE_CHECKPOINT; | ||
| import static org.apache.hadoop.fs.statistics.StoreStatisticNames.TRASH_DELETE_CHECKPOINT; | ||
| import static org.apache.hadoop.fs.statistics.StoreStatisticNames.TRASH_MOVE_TO_TRASH; | ||
| import static org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.invokeTrackingDuration; | ||
| import static org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.trackDurationOfInvocation; | ||
| import static org.apache.hadoop.util.functional.RemoteIterators.foreach; | ||
| import static org.apache.hadoop.util.functional.RemoteIterators.remoteIteratorFromIterable; | ||
|
|
||
| /** | ||
| * A Cloud Store Trash Policy designed to be resilient to | ||
| * race conditions and configurable to automatically clean up | ||
| * the current user's older checkpoints whenever invoked. | ||
| * | ||
| * | ||
| * The duration of trash operations are tracked in | ||
| * the target FileSystem's statistics, if it is configured | ||
| * to track these statistics: | ||
| * <ul> | ||
| * <li>{@link org.apache.hadoop.fs.statistics.StoreStatisticNames#TRASH_CREATE_CHECKPOINT}</li> | ||
| * <li>{@link org.apache.hadoop.fs.statistics.StoreStatisticNames#TRASH_DELETE_CHECKPOINT}</li> | ||
| * <li>{@link org.apache.hadoop.fs.statistics.StoreStatisticNames#TRASH_MOVE_TO_TRASH}</li> | ||
| * </ul> | ||
| */ | ||
| public class CloudStoreTrashPolicy extends TrashPolicyDefault { | ||
|
|
||
| private static final Logger LOG = | ||
| LoggerFactory.getLogger(CloudStoreTrashPolicy.class); | ||
|
|
||
| /** | ||
| * Configuration option to clean up old trash: {@value}. | ||
| */ | ||
| public static final String CLEANUP_OLD_CHECKPOINTS = "fs.trash.cleanup.old.checkpoints"; | ||
|
|
||
| /** | ||
| * Default value of {@link #CLEANUP_OLD_CHECKPOINTS}: {@value}. | ||
| * This still requires the cleanup interval to be > 0. | ||
| */ | ||
| public static final boolean CLEANUP_OLD_CHECKPOINTS_DEFAULT = true; | ||
|
|
||
| /** | ||
| * Should old trash be cleaned up? | ||
| */ | ||
| private boolean cleanupOldTrash; | ||
|
|
||
| /** | ||
| * Duration tracker if the FS provides one through its statistics. | ||
| */ | ||
| private DurationTrackerFactory durationTrackerFactory; | ||
|
|
||
| public CloudStoreTrashPolicy() { | ||
| } | ||
|
|
||
| public boolean cleanupOldTrash() { | ||
| return cleanupOldTrash; | ||
| } | ||
|
|
||
| public DurationTrackerFactory getDurationTrackerFactory() { | ||
| return durationTrackerFactory; | ||
| } | ||
|
|
||
| /** | ||
| * Set the duration tracker factory; useful for testing. | ||
| * @param durationTrackerFactory factory. | ||
| */ | ||
| public void setDurationTrackerFactory( | ||
| final DurationTrackerFactory durationTrackerFactory) { | ||
| this.durationTrackerFactory = requireNonNull(durationTrackerFactory); | ||
| } | ||
|
|
||
| @Override | ||
| public void initialize(final Configuration conf, final FileSystem fs) { | ||
| super.initialize(conf, fs); | ||
| cleanupOldTrash = getDeletionInterval() > 0 | ||
| && conf.getBoolean(CLEANUP_OLD_CHECKPOINTS, | ||
| CLEANUP_OLD_CHECKPOINTS_DEFAULT); | ||
| // get any duration tracker | ||
| setDurationTrackerFactory(bindToDurationTrackerFactory(fs)); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean moveToTrash(Path path) throws IOException { | ||
| if (!isEnabled()) { | ||
| return false; | ||
| } | ||
| boolean moved; | ||
|
|
||
| if (!path.isAbsolute()) { | ||
| // make path absolute | ||
| path = new Path(fs.getWorkingDirectory(), path); | ||
| } | ||
| if (!fs.exists(path)) { | ||
| // path doesn't actually exist. | ||
| LOG.info("'{} was deleted before it could be moved to trash", path); | ||
| moved = true; | ||
| } else { | ||
|
|
||
| try (DurationInfo info = new DurationInfo(LOG, true, "moveToTrash(%s)", | ||
| path)) { | ||
|
|
||
| // need for lambda expression. | ||
| Path p = path; | ||
| moved = invokeTrackingDuration( | ||
| durationTrackerFactory.trackDuration(TRASH_MOVE_TO_TRASH), () -> | ||
| super.moveToTrash(p)); | ||
|
|
||
| } catch (IOException e) { | ||
| if (!fs.exists(path)) { | ||
| // race condition with the trash setup; something else moved it. | ||
| // note that checking for FNFE is not sufficient as this may occur in | ||
| // the rename, at which point the exception may get downgraded. | ||
| LOG.info("'{} was deleted before it could be moved to trash", path); | ||
| LOG.debug("IOE raised on moveToTrash({})", path, e); | ||
| // report success | ||
| moved = true; | ||
| } else { | ||
| // source path still exists, so throw the exception and skip cleanup | ||
| // don't bother trying to cleanup here as it will only complicate | ||
| // error reporting | ||
| throw e; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // add cleanup | ||
| if (cleanupOldTrash()) { | ||
| executeTrashCleanup(); | ||
| } | ||
| return moved; | ||
| } | ||
|
|
||
| /** | ||
| * Execute the cleanup. | ||
| * @throws IOException failure | ||
| */ | ||
| @VisibleForTesting | ||
| public void executeTrashCleanup() throws IOException { | ||
| FileSystem fs = getFileSystem(); | ||
| AtomicLong count = new AtomicLong(); | ||
| long now = Time.now(); | ||
|
|
||
| // list the roots, iterate through | ||
| // expecting only one root for object stores. | ||
| foreach( | ||
| remoteIteratorFromIterable(fs.getTrashRoots(false)), | ||
| trashRoot -> { | ||
| try { | ||
| count.addAndGet(deleteCheckpoint(trashRoot.getPath(), false)); | ||
| createCheckpoint(trashRoot.getPath(), new Date(now)); | ||
| } catch (IOException e) { | ||
| LOG.warn("Trash caught:{} Skipping {}", e, trashRoot.getPath()); | ||
| LOG.debug("Trash caught", e); | ||
| } | ||
| }); | ||
| LOG.debug("Cleaned up {} checkpoints", count.get()); | ||
| } | ||
|
|
||
| /** | ||
| * Delete a checkpoint; update the duration tracker statistics. | ||
| * @param trashRoot trash root. | ||
| * @param deleteImmediately should all entries be deleted? | ||
| * @return a count of delete checkpoints. | ||
| * @throws IOException outcome | ||
| */ | ||
| @Override | ||
| protected int deleteCheckpoint(final Path trashRoot, | ||
| final boolean deleteImmediately) | ||
| throws IOException { | ||
| return invokeTrackingDuration( | ||
| durationTrackerFactory.trackDuration(TRASH_DELETE_CHECKPOINT), | ||
| () -> super.deleteCheckpoint(trashRoot, deleteImmediately)); | ||
| } | ||
|
|
||
| /** | ||
| * Create a checkpoint; update the duration tracker statistics. | ||
| * @param trashRoot trash root. | ||
| * @param date date of checkpoint | ||
| * @throws IOException outcome | ||
| */ | ||
| @Override | ||
| protected void createCheckpoint(final Path trashRoot, final Date date) | ||
| throws IOException { | ||
| trackDurationOfInvocation(durationTrackerFactory, TRASH_CREATE_CHECKPOINT, () -> | ||
| super.createCheckpoint(trashRoot, date)); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
moveToTrash() will be called by thousands of clients. IIRC, a new snapshot will be created, as long as the CURRENT dir exists. super.moveToTrash() will create the CURRENT dir if it does not exist. So, I'd image every moveToTrash() would create a new checkpoint which is probably not ideal.
Each client will also try to delete the same set of snapshots. I'd image some of clients will fail due to FILE_NOT_FOUND exception, because a checkpoint dir is removed by other clients.
Cleaning is something we need to handle for trash and if we could make this approach work, I would think that would be great.
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.
hmmm. good explanation of the problems you see.
that auto cleanup was added because of the reported problem of user home dirs being full. maybe we need to think of better strategies here, even if just hadoop fs -expunge updated to work better in this world.