Skip to content

Commit 82e36a2

Browse files
sidkhillonhgromerskhillon
authored
HBASE-29631 Fix race condition in IncrementalTableBackupClient when HFiles are archived during backup (#7346)
Co-authored-by: Hernan Romer <nanug33@gmail.com> Co-authored-by: skhillon <skhillon@hubspot.com> Signed-off-by: Ray Mattingly <rmattingly@apache.org>
1 parent a2a70d6 commit 82e36a2

2 files changed

Lines changed: 116 additions & 3 deletions

File tree

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/impl/IncrementalTableBackupClient.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ private void mergeSplitAndCopyBulkloadedHFiles(List<String> activeFiles,
200200
int numActiveFiles = activeFiles.size();
201201
updateFileLists(activeFiles, archiveFiles);
202202
if (activeFiles.size() < numActiveFiles) {
203+
// We've archived some files, delete bulkloads directory
204+
// and re-try
205+
deleteBulkLoadDirectory();
203206
continue;
204207
}
205208

@@ -242,7 +245,7 @@ private void mergeSplitAndCopyBulkloadedHFiles(List<String> files, TableName tn,
242245
incrementalCopyBulkloadHFiles(tgtFs, tn);
243246
}
244247

245-
private void updateFileLists(List<String> activeFiles, List<String> archiveFiles)
248+
public void updateFileLists(List<String> activeFiles, List<String> archiveFiles)
246249
throws IOException {
247250
List<String> newlyArchived = new ArrayList<>();
248251

@@ -252,9 +255,23 @@ private void updateFileLists(List<String> activeFiles, List<String> archiveFiles
252255
}
253256
}
254257

255-
if (newlyArchived.size() > 0) {
258+
if (!newlyArchived.isEmpty()) {
259+
String rootDir = CommonFSUtils.getRootDir(conf).toString();
260+
256261
activeFiles.removeAll(newlyArchived);
257-
archiveFiles.addAll(newlyArchived);
262+
for (String file : newlyArchived) {
263+
String archivedFile = file.substring(rootDir.length() + 1);
264+
Path archivedFilePath = new Path(HFileArchiveUtil.getArchivePath(conf), archivedFile);
265+
archivedFile = archivedFilePath.toString();
266+
267+
if (!fs.exists(archivedFilePath)) {
268+
throw new IOException(String.format(
269+
"File %s no longer exists, and no archived file %s exists for it", file, archivedFile));
270+
}
271+
272+
LOG.debug("Archived file {} has been updated", archivedFile);
273+
archiveFiles.add(archivedFile);
274+
}
258275
}
259276

260277
LOG.debug(newlyArchived.size() + " files have been archived.");

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

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import static org.junit.Assert.assertEquals;
2121
import static org.junit.Assert.assertFalse;
2222
import static org.junit.Assert.assertTrue;
23+
import static org.junit.Assert.fail;
2324

2425
import java.io.IOException;
2526
import java.nio.ByteBuffer;
27+
import java.util.ArrayList;
2628
import java.util.List;
2729
import java.util.Map;
2830
import org.apache.hadoop.fs.FileSystem;
@@ -38,6 +40,8 @@
3840
import org.apache.hadoop.hbase.testclassification.LargeTests;
3941
import org.apache.hadoop.hbase.tool.BulkLoadHFiles;
4042
import org.apache.hadoop.hbase.util.Bytes;
43+
import org.apache.hadoop.hbase.util.CommonFSUtils;
44+
import org.apache.hadoop.hbase.util.HFileArchiveUtil;
4145
import org.apache.hadoop.hbase.util.HFileTestUtil;
4246
import org.junit.ClassRule;
4347
import org.junit.Test;
@@ -147,6 +151,98 @@ private boolean containsRowWithKey(Table table, String rowKey) throws IOExceptio
147151
return result.containsColumn(famName, qualName);
148152
}
149153

154+
@Test
155+
public void testUpdateFileListsRaceCondition() throws Exception {
156+
try (BackupSystemTable systemTable = new BackupSystemTable(TEST_UTIL.getConnection())) {
157+
// Test the race condition where files are archived during incremental backup
158+
FileSystem fs = TEST_UTIL.getTestFileSystem();
159+
160+
String regionName = "region1";
161+
String columnFamily = "cf";
162+
String filename1 = "hfile1";
163+
String filename2 = "hfile2";
164+
165+
Path rootDir = CommonFSUtils.getRootDir(TEST_UTIL.getConfiguration());
166+
Path tableDir = CommonFSUtils.getTableDir(rootDir, table1);
167+
Path activeFile1 =
168+
new Path(tableDir, regionName + Path.SEPARATOR + columnFamily + Path.SEPARATOR + filename1);
169+
Path activeFile2 =
170+
new Path(tableDir, regionName + Path.SEPARATOR + columnFamily + Path.SEPARATOR + filename2);
171+
172+
fs.mkdirs(activeFile1.getParent());
173+
fs.create(activeFile1).close();
174+
fs.create(activeFile2).close();
175+
176+
List<String> activeFiles = new ArrayList<>();
177+
activeFiles.add(activeFile1.toString());
178+
activeFiles.add(activeFile2.toString());
179+
List<String> archiveFiles = new ArrayList<>();
180+
181+
Path archiveDir = HFileArchiveUtil.getStoreArchivePath(TEST_UTIL.getConfiguration(), table1,
182+
regionName, columnFamily);
183+
Path archivedFile1 = new Path(archiveDir, filename1);
184+
fs.mkdirs(archiveDir);
185+
assertTrue("File should be moved to archive", fs.rename(activeFile1, archivedFile1));
186+
187+
TestBackupBase.IncrementalTableBackupClientForTest client =
188+
new TestBackupBase.IncrementalTableBackupClientForTest(TEST_UTIL.getConnection(),
189+
"test_backup_id",
190+
createBackupRequest(BackupType.INCREMENTAL, List.of(table1), BACKUP_ROOT_DIR));
191+
192+
client.updateFileLists(activeFiles, archiveFiles);
193+
194+
assertEquals("Only one file should remain in active files", 1, activeFiles.size());
195+
assertEquals("File2 should still be in active files", activeFile2.toString(),
196+
activeFiles.get(0));
197+
assertEquals("One file should be added to archive files", 1, archiveFiles.size());
198+
assertEquals("Archived file should have correct path", archivedFile1.toString(),
199+
archiveFiles.get(0));
200+
systemTable.finishBackupExclusiveOperation();
201+
}
202+
203+
}
204+
205+
@Test
206+
public void testUpdateFileListsMissingArchivedFile() throws Exception {
207+
try (BackupSystemTable systemTable = new BackupSystemTable(TEST_UTIL.getConnection())) {
208+
// Test that IOException is thrown when file doesn't exist in archive location
209+
FileSystem fs = TEST_UTIL.getTestFileSystem();
210+
211+
String regionName = "region2";
212+
String columnFamily = "cf";
213+
String filename = "missing_file";
214+
215+
Path rootDir = CommonFSUtils.getRootDir(TEST_UTIL.getConfiguration());
216+
Path tableDir = CommonFSUtils.getTableDir(rootDir, table1);
217+
Path activeFile =
218+
new Path(tableDir, regionName + Path.SEPARATOR + columnFamily + Path.SEPARATOR + filename);
219+
220+
fs.mkdirs(activeFile.getParent());
221+
fs.create(activeFile).close();
222+
223+
List<String> activeFiles = new ArrayList<>();
224+
activeFiles.add(activeFile.toString());
225+
List<String> archiveFiles = new ArrayList<>();
226+
227+
// Delete the file but don't create it in archive location
228+
fs.delete(activeFile, false);
229+
230+
TestBackupBase.IncrementalTableBackupClientForTest client =
231+
new TestBackupBase.IncrementalTableBackupClientForTest(TEST_UTIL.getConnection(),
232+
"test_backup_id",
233+
createBackupRequest(BackupType.INCREMENTAL, List.of(table1), BACKUP_ROOT_DIR));
234+
235+
// This should throw IOException since file doesn't exist in archive
236+
try {
237+
client.updateFileLists(activeFiles, archiveFiles);
238+
fail("Expected IOException to be thrown");
239+
} catch (IOException e) {
240+
// Expected
241+
}
242+
systemTable.finishBackupExclusiveOperation();
243+
}
244+
}
245+
150246
private void performBulkLoad(String keyPrefix) throws IOException {
151247
FileSystem fs = TEST_UTIL.getTestFileSystem();
152248
Path baseDirectory = TEST_UTIL.getDataTestDirOnTestFS(TEST_NAME);

0 commit comments

Comments
 (0)