Skip to content

Commit 1ce11df

Browse files
author
Hernan Gelaf-Romer
committed
Log filtering in IncrementalBackupManager can lead to data loss
1 parent 7d0d73b commit 1ce11df

3 files changed

Lines changed: 372 additions & 10 deletions

File tree

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@
3838

3939
import java.io.IOException;
4040
import java.util.ArrayList;
41+
import java.util.Arrays;
4142
import java.util.HashMap;
4243
import java.util.List;
4344
import java.util.Map;
4445
import java.util.UUID;
4546
import java.util.stream.Collectors;
47+
import org.apache.hadoop.fs.FileStatus;
48+
import org.apache.hadoop.fs.FileSystem;
49+
import org.apache.hadoop.fs.Path;
50+
import org.apache.hadoop.hbase.HConstants;
4651
import org.apache.hadoop.hbase.TableName;
4752
import org.apache.hadoop.hbase.backup.BackupCopyJob;
4853
import org.apache.hadoop.hbase.backup.BackupInfo;
@@ -60,7 +65,9 @@
6065
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
6166
import org.apache.hadoop.hbase.replication.ReplicationException;
6267
import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
68+
import org.apache.hadoop.hbase.util.CommonFSUtils;
6369
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
70+
import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
6471
import org.apache.yetus.audience.InterfaceAudience;
6572
import org.slf4j.Logger;
6673
import org.slf4j.LoggerFactory;
@@ -208,14 +215,17 @@ private void handleContinuousBackup(Admin admin) throws IOException {
208215
}
209216

210217
private void handleNonContinuousBackup(Admin admin) throws IOException {
218+
Map<String, Long> previousLogRollsByHost = backupManager.readRegionServerLastLogRollResult();
211219
performLogRoll();
220+
Map<String, Long> latestLogRollsByHost = newTimestamps;
212221
performBackupSnapshots(admin);
213222
backupManager.addIncrementalBackupTableSet(backupInfo.getTables());
214223

215224
// set overall backup status: complete. Here we make sure to complete the backup.
216225
// After this checkpoint, even if entering cancel process, will let the backup finished
217226
backupInfo.setState(BackupState.COMPLETE);
218227

228+
adjustTimestampsForOfflineHosts(previousLogRollsByHost, latestLogRollsByHost);
219229
updateBackupMetadata();
220230
}
221231

@@ -248,6 +258,54 @@ private void performSnapshots(Admin admin) throws IOException {
248258
}
249259
}
250260

261+
/**
262+
* Scan all WAL files to find offline/decommissioned hosts and record their max WAL timestamps in
263+
* newTimestamps. This ensures subsequent incremental backups won't re-include WALs already covered
264+
* by this full backup's snapshot.
265+
*/
266+
private void adjustTimestampsForOfflineHosts(Map<String, Long> previousLogRollsByHost,
267+
Map<String, Long> latestLogRollsByHost) throws IOException {
268+
Path walRootDir = CommonFSUtils.getWALRootDir(conf);
269+
Path logDir = new Path(walRootDir, HConstants.HREGION_LOGDIR_NAME);
270+
Path oldLogDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME);
271+
FileSystem fs = walRootDir.getFileSystem(conf);
272+
273+
List<FileStatus> allLogs = new ArrayList<>();
274+
for (FileStatus hostLogDir : fs.listStatus(logDir)) {
275+
String host = BackupUtils.parseHostNameFromLogFile(hostLogDir.getPath());
276+
if (host == null) {
277+
continue;
278+
}
279+
allLogs.addAll(Arrays.asList(fs.listStatus(hostLogDir.getPath())));
280+
}
281+
allLogs.addAll(Arrays.asList(fs.listStatus(oldLogDir)));
282+
283+
newTimestamps = new HashMap<>();
284+
285+
for (FileStatus log : allLogs) {
286+
if (AbstractFSWALProvider.isMetaFile(log.getPath())) {
287+
continue;
288+
}
289+
String host = BackupUtils.parseHostNameFromLogFile(log.getPath());
290+
if (host == null) {
291+
continue;
292+
}
293+
long timestamp = BackupUtils.getCreationTime(log.getPath());
294+
Long previousLogRoll = previousLogRollsByHost.get(host);
295+
Long latestLogRoll = latestLogRollsByHost.get(host);
296+
boolean isInactive = latestLogRoll == null || latestLogRoll.equals(previousLogRoll);
297+
298+
if (isInactive) {
299+
long currentTs = newTimestamps.getOrDefault(host, 0L);
300+
if (timestamp > currentTs) {
301+
newTimestamps.put(host, timestamp);
302+
}
303+
} else {
304+
newTimestamps.put(host, latestLogRoll);
305+
}
306+
}
307+
}
308+
251309
private void updateBackupMetadata() throws IOException {
252310
// The table list in backupInfo is good for both full backup and incremental backup.
253311
// For incremental backup, it contains the incremental backup table set.

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

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.IOException;
2121
import java.util.ArrayList;
2222
import java.util.Collections;
23+
import java.util.HashMap;
2324
import java.util.List;
2425
import java.util.Map;
2526
import org.apache.hadoop.conf.Configuration;
@@ -59,7 +60,6 @@ public IncrementalBackupManager(Connection conn, Configuration conf) throws IOEx
5960
*/
6061
public Map<String, Long> getIncrBackupLogFileMap() throws IOException {
6162
List<String> logList;
62-
Map<String, Long> newTimestamps;
6363
Map<String, Long> previousTimestampMins =
6464
BackupUtils.getRSLogTimestampMins(readLogTimestampMap());
6565

@@ -72,12 +72,46 @@ public Map<String, Long> getIncrBackupLogFileMap() throws IOException {
7272
LOG.info("Execute roll log procedure for incremental backup ...");
7373
BackupUtils.logRoll(conn, backupInfo.getBackupRootDir(), conf);
7474

75-
newTimestamps = readRegionServerLastLogRollResult();
75+
Map<String, Long> newTimestamps = new HashMap<>();
76+
Map<String, Long> latestLogRollByHost = readRegionServerLastLogRollResult();
77+
for (Map.Entry<String, Long> entry : latestLogRollByHost.entrySet()) {
78+
String host = entry.getKey();
79+
long latestLogRoll = entry.getValue();
80+
Long earliestTimestampToIncludeInBackup = previousTimestampMins.get(host);
81+
82+
boolean isInactive = earliestTimestampToIncludeInBackup != null
83+
&& earliestTimestampToIncludeInBackup >= latestLogRoll;
84+
85+
if (isInactive) {
86+
LOG.debug(
87+
"Skipping inactive host {} from newTimestamps (boundary={} >= latestLogRoll={})", host,
88+
earliestTimestampToIncludeInBackup, latestLogRoll);
89+
} else {
90+
newTimestamps.put(host, latestLogRoll);
91+
}
92+
}
7693

7794
logList = getLogFilesForNewBackup(previousTimestampMins, newTimestamps, conf);
7895
logList = excludeProcV2WALs(logList);
7996
backupInfo.setIncrBackupFileList(logList);
8097

98+
// Update boundaries based on WALs that will be backed up
99+
for (String logFile : logList) {
100+
Path logPath = new Path(logFile);
101+
String logHost = BackupUtils.parseHostFromOldLog(logPath);
102+
if (logHost == null) {
103+
logHost = BackupUtils.parseHostNameFromLogFile(logPath.getParent());
104+
}
105+
if (logHost != null) {
106+
long logTs = BackupUtils.getCreationTime(logPath);
107+
Long latestTimestampToIncludeInBackup = newTimestamps.get(logHost);
108+
if (latestTimestampToIncludeInBackup == null || logTs > latestTimestampToIncludeInBackup) {
109+
LOG.info("Updating backup boundary for inactive host {}: timestamp={}", logHost, logTs);
110+
newTimestamps.put(logHost, logTs);
111+
}
112+
}
113+
}
114+
81115
return newTimestamps;
82116
}
83117

@@ -217,14 +251,6 @@ private List<String> getLogFilesForNewBackup(Map<String, Long> olderTimestamps,
217251
resultLogFiles.add(currentLogFile);
218252
}
219253

220-
// It is possible that a host in .oldlogs is an obsolete region server
221-
// so newestTimestamps.get(host) here can be null.
222-
// Even if these logs belong to a obsolete region server, we still need
223-
// to include they to avoid loss of edits for backup.
224-
Long newTimestamp = newestTimestamps.get(host);
225-
if (newTimestamp == null || currentLogTS > newTimestamp) {
226-
newestLogs.add(currentLogFile);
227-
}
228254
}
229255
// remove newest log per host because they are still in use
230256
resultLogFiles.removeAll(newestLogs);

0 commit comments

Comments
 (0)