2020import java .io .IOException ;
2121import java .util .ArrayList ;
2222import java .util .Collections ;
23+ import java .util .HashSet ;
2324import java .util .List ;
2425import java .util .Map ;
26+ import java .util .Set ;
2527import org .apache .hadoop .conf .Configuration ;
2628import org .apache .hadoop .fs .FileStatus ;
2729import org .apache .hadoop .fs .FileSystem ;
@@ -54,12 +56,14 @@ public IncrementalBackupManager(Connection conn, Configuration conf) throws IOEx
5456 /**
5557 * Obtain the list of logs that need to be copied out for this incremental backup. The list is set
5658 * in BackupInfo.
57- * @return The new HashMap of RS log time stamps after the log roll for this incremental backup.
59+ * @return The new map of RS log time stamps for this incremental backup, as computed by
60+ * {@link BackupUtils#computeLogBoundaries}: the included logs are covered, the logs held
61+ * back for a later backup (for example, logs still being split) are pending, and a host
62+ * that still has logs keeps its previous boundary if this backup gives it no new one.
5863 * @throws IOException exception
5964 */
6065 public Map <String , Long > getIncrBackupLogFileMap () throws IOException {
6166 List <String > logList ;
62- Map <String , Long > newTimestamps ;
6367 Map <String , Long > previousTimestampMins =
6468 BackupUtils .getRSLogTimestampMins (readLogTimestampMap ());
6569
@@ -69,18 +73,28 @@ public Map<String, Long> getIncrBackupLogFileMap() throws IOException {
6973 + "In order to create an incremental backup, at least one full backup is needed." );
7074 }
7175
76+ Map <String , Long > previousLogRollByHost = readRegionServerLastLogRollResult ();
7277 LOG .info ("Execute roll log procedure for incremental backup ..." );
7378 BackupUtils .logRoll (conn , backupInfo .getBackupRootDir (), conf );
7479
75- newTimestamps = readRegionServerLastLogRollResult ();
80+ Map <String , Long > rolledHosts =
81+ BackupUtils .getRolledHosts (previousLogRollByHost , readRegionServerLastLogRollResult ());
7682
77- logList = getLogFilesForNewBackup (previousTimestampMins , newTimestamps , conf );
78- logList = excludeProcV2WALs (logList );
83+ LogFileSelection selection = getLogFilesForNewBackup (previousTimestampMins , rolledHosts , conf );
84+ logList = excludeProcV2WALs (selection . included () );
7985 backupInfo .setIncrBackupFileList (logList );
8086
87+ Map <String , Long > newTimestamps = BackupUtils .computeLogBoundaries (rolledHosts ,
88+ previousTimestampMins , selection .hostsWithLogs (), logList , selection .heldBack ());
89+ LOG .debug ("Log boundaries for incremental backup {}: {}" , backupInfo .getBackupId (),
90+ newTimestamps );
8191 return newTimestamps ;
8292 }
8393
94+ private record LogFileSelection (List <String > included , List <String > heldBack ,
95+ Set <String > hostsWithLogs ) {
96+ }
97+
8498 private List <String > excludeProcV2WALs (List <String > logList ) {
8599 List <String > list = new ArrayList <>();
86100 for (int i = 0 ; i < logList .size (); i ++) {
@@ -97,15 +111,17 @@ private List<String> excludeProcV2WALs(List<String> logList) {
97111 }
98112
99113 /**
100- * For each region server: get all log files newer than the last timestamps but not newer than the
101- * newest timestamps.
114+ * Gather all log files that either: 1) are newer than the older timestamps, but not newer than
115+ * the newest timestamps, or 2) are archived logs whose host name does not occur in the newest
116+ * timestamps.
102117 * @param olderTimestamps the timestamp for each region server of the last backup.
103118 * @param newestTimestamps the timestamp for each region server that the backup should lead to.
104119 * @param conf the Hadoop and Hbase configuration
105- * @return a list of log files to be backed up
120+ * @return the log files to be backed up, the log files held back for a later backup, and the
121+ * hosts that have any log files, including ones not backed up
106122 * @throws IOException exception
107123 */
108- private List < String > getLogFilesForNewBackup (Map <String , Long > olderTimestamps ,
124+ private LogFileSelection getLogFilesForNewBackup (Map <String , Long > olderTimestamps ,
109125 Map <String , Long > newestTimestamps , Configuration conf ) throws IOException {
110126 LOG .debug ("In getLogFilesForNewBackup()\n " + "olderTimestamps: " + olderTimestamps
111127 + "\n newestTimestamps: " + newestTimestamps );
@@ -119,6 +135,7 @@ private List<String> getLogFilesForNewBackup(Map<String, Long> olderTimestamps,
119135
120136 List <String > resultLogFiles = new ArrayList <>();
121137 List <String > newestLogs = new ArrayList <>();
138+ Set <String > hostsWithLogs = new HashSet <>();
122139
123140 /*
124141 * The old region servers and timestamps info we kept in backup system table may be out of sync
@@ -143,6 +160,7 @@ private List<String> getLogFilesForNewBackup(Map<String, Long> olderTimestamps,
143160 if (host == null ) {
144161 continue ;
145162 }
163+ hostsWithLogs .add (host );
146164 FileStatus [] logs ;
147165 oldTimeStamp = olderTimestamps .get (host );
148166 // It is possible that there is no old timestamp in backup system table for this host if
@@ -165,17 +183,15 @@ private List<String> getLogFilesForNewBackup(Map<String, Long> olderTimestamps,
165183 resultLogFiles .add (currentLogFile );
166184 currentLogTS = BackupUtils .getCreationTime (log .getPath ());
167185
168- // If newestTimestamps.get(host) is null, means that
169- // either RS (host) has been restarted recently with different port number
170- // or RS is down (was decommisioned). In any case, we treat this
171- // log file as eligible for inclusion into incremental backup log list
186+ // A host without a roll result did not take part in this backup's log roll: it is offline
187+ // (for example a dead server whose logs are still being split), or it started after the
188+ // roll. Its logs are held back, like logs newer than the roll result of a host that took
189+ // part. BackupUtils#computeLogBoundaries keeps the boundary of the host below held-back
190+ // logs, so a later backup includes them.
172191 Long ts = newestTimestamps .get (host );
173192 if (ts == null ) {
174- LOG .warn ("ORPHAN log found: " + log + " host=" + host );
175- LOG .debug ("Known hosts (from newestTimestamps):" );
176- for (String s : newestTimestamps .keySet ()) {
177- LOG .debug (s );
178- }
193+ LOG .debug ("Holding back log {} of host {}, which did not take part in the log roll" ,
194+ log .getPath (), host );
179195 }
180196 if (ts == null || currentLogTS > ts ) {
181197 newestLogs .add (currentLogFile );
@@ -198,6 +214,7 @@ private List<String> getLogFilesForNewBackup(Map<String, Long> olderTimestamps,
198214 if (host == null ) {
199215 continue ;
200216 }
217+ hostsWithLogs .add (host );
201218 currentLogTS = BackupUtils .getCreationTime (p );
202219 oldTimeStamp = olderTimestamps .get (host );
203220 /*
@@ -217,18 +234,15 @@ private List<String> getLogFilesForNewBackup(Map<String, Long> olderTimestamps,
217234 resultLogFiles .add (currentLogFile );
218235 }
219236
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.
224237 Long newTimestamp = newestTimestamps .get (host );
225- if (newTimestamp == null || currentLogTS > newTimestamp ) {
238+ if (newTimestamp != null && currentLogTS > newTimestamp ) {
226239 newestLogs .add (currentLogFile );
227240 }
228241 }
229- // remove newest log per host because they are still in use
242+ // Held-back logs are newer than the roll result of their host, or belong to a host that did not
243+ // take part in the log roll. A later backup includes them.
230244 resultLogFiles .removeAll (newestLogs );
231- return resultLogFiles ;
245+ return new LogFileSelection ( resultLogFiles , newestLogs , hostsWithLogs ) ;
232246 }
233247
234248 static class NewestLogFilter implements PathFilter {
0 commit comments