Skip to content

Commit d1bce57

Browse files
authored
HBASE-29604 BackupHFileCleaner uses flawed time based check (#7360)
Adds javadoc mentioning the concurrent usage and thread-safety need of FileCleanerDelegate#getDeletableFiles. Fixes a potential thread-safety issue in BackupHFileCleaner: this class tracks timestamps to block the deletion of recently loaded HFiles that might be needed for backup purposes. The timestamps were being registered from inside the concurrent method, which could result in recently added files getting deleted. Moved the timestamp registration to the postClean method, which is called only a single time per cleaner run, so recently loaded HFiles are in fact protected from deletion. Signed-off-by: Nick Dimiduk <[email protected]>
1 parent d8b1912 commit d1bce57

File tree

4 files changed

+28
-10
lines changed

4 files changed

+28
-10
lines changed

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupHFileCleaner.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@ public class BackupHFileCleaner extends BaseHFileCleanerDelegate implements Abor
5252
private boolean stopped = false;
5353
private boolean aborted = false;
5454
private Connection connection;
55-
// timestamp of most recent read from backup system table
56-
private long prevReadFromBackupTbl = 0;
57-
// timestamp of 2nd most recent read from backup system table
58-
private long secondPrevReadFromBackupTbl = 0;
55+
// timestamp of most recent completed cleaning run
56+
private volatile long previousCleaningCompletionTimestamp = 0;
57+
58+
@Override
59+
public void postClean() {
60+
previousCleaningCompletionTimestamp = EnvironmentEdgeManager.currentTime();
61+
}
5962

6063
@Override
6164
public Iterable<FileStatus> getDeletableFiles(Iterable<FileStatus> files) {
@@ -79,12 +82,12 @@ public Iterable<FileStatus> getDeletableFiles(Iterable<FileStatus> files) {
7982
return Collections.emptyList();
8083
}
8184

82-
secondPrevReadFromBackupTbl = prevReadFromBackupTbl;
83-
prevReadFromBackupTbl = EnvironmentEdgeManager.currentTime();
85+
// Pin the threshold, we don't want the result to change depending on evaluation time.
86+
final long recentFileThreshold = previousCleaningCompletionTimestamp;
8487

8588
return Iterables.filter(files, file -> {
8689
// If the file is recent, be conservative and wait for one more scan of the bulk loads
87-
if (file.getModificationTime() > secondPrevReadFromBackupTbl) {
90+
if (file.getModificationTime() > recentFileThreshold) {
8891
LOG.debug("Preventing deletion due to timestamp: {}", file.getPath().toString());
8992
return false;
9093
}

hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupHFileCleaner.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,11 @@ protected Set<TableName> fetchFullyBackedUpTables(BackupSystemTable tbl) {
108108
Iterable<FileStatus> deletable;
109109

110110
// The first call will not allow any deletions because of the timestamp mechanism.
111-
deletable = cleaner.getDeletableFiles(List.of(file1, file1Archived, file2, file3));
111+
deletable = callCleaner(cleaner, List.of(file1, file1Archived, file2, file3));
112112
assertEquals(Set.of(), Sets.newHashSet(deletable));
113113

114114
// No bulk loads registered, so all files can be deleted.
115-
deletable = cleaner.getDeletableFiles(List.of(file1, file1Archived, file2, file3));
115+
deletable = callCleaner(cleaner, List.of(file1, file1Archived, file2, file3));
116116
assertEquals(Set.of(file1, file1Archived, file2, file3), Sets.newHashSet(deletable));
117117

118118
// Register some bulk loads.
@@ -125,10 +125,17 @@ protected Set<TableName> fetchFullyBackedUpTables(BackupSystemTable tbl) {
125125
}
126126

127127
// File 1 can no longer be deleted, because it is registered as a bulk load.
128-
deletable = cleaner.getDeletableFiles(List.of(file1, file1Archived, file2, file3));
128+
deletable = callCleaner(cleaner, List.of(file1, file1Archived, file2, file3));
129129
assertEquals(Set.of(file2, file3), Sets.newHashSet(deletable));
130130
}
131131

132+
private Iterable<FileStatus> callCleaner(BackupHFileCleaner cleaner, Iterable<FileStatus> files) {
133+
cleaner.preClean();
134+
Iterable<FileStatus> deletable = cleaner.getDeletableFiles(files);
135+
cleaner.postClean();
136+
return deletable;
137+
}
138+
132139
private FileStatus createFile(String fileName) throws IOException {
133140
Path file = new Path(root, fileName);
134141
fs.createNewFile(file);

hbase-server/src/main/java/org/apache/hadoop/hbase/master/cleaner/BaseFileCleanerDelegate.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public void init(Map<String, Object> params) {
4444

4545
/**
4646
* Should the master delete the file or keep it?
47+
* <p>
48+
* This method can be called concurrently by multiple threads. Implementations must be thread
49+
* safe.
50+
* </p>
4751
* @param fStat file status of the file to check
4852
* @return <tt>true</tt> if the file is deletable, <tt>false</tt> if not
4953
*/

hbase-server/src/main/java/org/apache/hadoop/hbase/master/cleaner/FileCleanerDelegate.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public interface FileCleanerDelegate extends Configurable, Stoppable {
3333

3434
/**
3535
* Determines which of the given files are safe to delete
36+
* <p>
37+
* This method can be called concurrently by multiple threads. Implementations must be thread
38+
* safe.
39+
* </p>
3640
* @param files files to check for deletion
3741
* @return files that are ok to delete according to this cleaner
3842
*/

0 commit comments

Comments
 (0)