3636import static org .apache .hadoop .hbase .replication .ReplicationUtils .OFFSET_UPDATE_INTERVAL_MS_KEY ;
3737import static org .apache .hadoop .hbase .replication .ReplicationUtils .OFFSET_UPDATE_SIZE_THRESHOLD_KEY ;
3838
39+ import com .google .errorprone .annotations .RestrictedApi ;
3940import java .io .IOException ;
4041import java .util .ArrayList ;
4142import java .util .HashMap ;
43+ import java .util .HashSet ;
4244import java .util .List ;
4345import java .util .Map ;
46+ import java .util .Set ;
4447import java .util .UUID ;
4548import java .util .stream .Collectors ;
49+ import org .apache .hadoop .fs .FileStatus ;
50+ import org .apache .hadoop .fs .FileSystem ;
51+ import org .apache .hadoop .fs .Path ;
52+ import org .apache .hadoop .hbase .HConstants ;
53+ import org .apache .hadoop .hbase .ServerName ;
4654import org .apache .hadoop .hbase .TableName ;
4755import org .apache .hadoop .hbase .backup .BackupCopyJob ;
4856import org .apache .hadoop .hbase .backup .BackupInfo ;
6068import org .apache .hadoop .hbase .client .TableDescriptorBuilder ;
6169import org .apache .hadoop .hbase .replication .ReplicationException ;
6270import org .apache .hadoop .hbase .replication .ReplicationPeerConfig ;
71+ import org .apache .hadoop .hbase .util .CommonFSUtils ;
6372import org .apache .hadoop .hbase .util .EnvironmentEdgeManager ;
73+ import org .apache .hadoop .hbase .wal .AbstractFSWALProvider ;
6474import org .apache .yetus .audience .InterfaceAudience ;
6575import org .slf4j .Logger ;
6676import org .slf4j .LoggerFactory ;
@@ -208,7 +218,12 @@ private void handleContinuousBackup(Admin admin) throws IOException {
208218 }
209219
210220 private void handleNonContinuousBackup (Admin admin ) throws IOException {
211- performLogRoll ();
221+ Map <String , Long > previousLogRollsByHost = backupManager .readRegionServerLastLogRollResult ();
222+ Map <String , Long > latestLogRollsByHost = performLogRoll ();
223+ Path walRootDir = CommonFSUtils .getWALRootDir (conf );
224+ newTimestamps = computeLogBoundaries (walRootDir .getFileSystem (conf ), walRootDir ,
225+ BackupUtils .getRolledHosts (previousLogRollsByHost , latestLogRollsByHost ), admin );
226+
212227 performBackupSnapshots (admin );
213228 backupManager .addIncrementalBackupTableSet (backupInfo .getTables ());
214229
@@ -219,15 +234,15 @@ private void handleNonContinuousBackup(Admin admin) throws IOException {
219234 updateBackupMetadata ();
220235 }
221236
222- private void performLogRoll () throws IOException {
237+ private Map < String , Long > performLogRoll () throws IOException {
223238 // We roll log here before we do the snapshot. It is possible there is duplicate data
224239 // in the log that is already in the snapshot. But if we do it after the snapshot, we
225240 // could have data loss.
226241 // A better approach is to do the roll log on each RS in the same global procedure as
227242 // the snapshot.
228243 LOG .info ("Execute roll log procedure for full backup ..." );
229244 BackupUtils .logRoll (conn , backupInfo .getBackupRootDir (), conf );
230- newTimestamps = backupManager .readRegionServerLastLogRollResult ();
245+ return backupManager .readRegionServerLastLogRollResult ();
231246 }
232247
233248 private void performBackupSnapshots (Admin admin ) throws IOException {
@@ -248,6 +263,64 @@ private void performSnapshots(Admin admin) throws IOException {
248263 }
249264 }
250265
266+ /**
267+ * Computes the per-host log boundaries stored by this full backup, using
268+ * {@link BackupUtils#computeLogBoundaries}. A host that took part in this backup's log roll gets
269+ * its roll result: WALs created after the roll are included in the next incremental backup, even
270+ * though some of their edits may already be in the snapshot, which is safe because deletes are
271+ * replayed along with the puts. For any other host, its archived WALs and the WALs of dead
272+ * servers are covered by the snapshot, so the next incremental backup does not replay them. The
273+ * WALs of a live server that did not take part in the roll (for example one that started during
274+ * it) are pending, because they can still receive edits that are not in the snapshot. The live
275+ * servers are read only after the WAL directories are listed: a region server creates its WAL
276+ * directory only after the master registers it, so every live server whose directory was listed
277+ * is found. It must run right after the log roll, before the snapshot is taken, so that a host
278+ * starting later gets no boundary and has all of its WALs included in the next incremental
279+ * backup.
280+ */
281+ @ RestrictedApi (
282+ explanation = "Package-private for test visibility only. Do not use outside tests." ,
283+ link = "" ,
284+ allowedOnPath = "(.*/src/test/.*|.*/org/apache/hadoop/hbase/backup/impl/FullTableBackupClient.java)" )
285+ static Map <String , Long > computeLogBoundaries (FileSystem fs , Path walRootDir ,
286+ Map <String , Long > rolledHosts , Admin admin ) throws IOException {
287+ Path logDir = new Path (walRootDir , HConstants .HREGION_LOGDIR_NAME );
288+ Path oldLogDir = new Path (walRootDir , HConstants .HREGION_OLDLOGDIR_NAME );
289+
290+ Map <ServerName , List <String >> logsByServer = new HashMap <>();
291+ for (FileStatus serverLogDir : fs .listStatus (logDir )) {
292+ ServerName serverName =
293+ AbstractFSWALProvider .getServerNameFromWALDirectoryName (serverLogDir .getPath ());
294+ if (serverName == null ) {
295+ continue ;
296+ }
297+ List <String > logs = logsByServer .computeIfAbsent (serverName , k -> new ArrayList <>());
298+ for (FileStatus log : fs .listStatus (serverLogDir .getPath ())) {
299+ if (!AbstractFSWALProvider .isMetaFile (log .getPath ())) {
300+ logs .add (log .getPath ().toString ());
301+ }
302+ }
303+ }
304+
305+ Set <ServerName > live = new HashSet <>(admin .getRegionServers ());
306+ List <String > coveredLogs = new ArrayList <>();
307+ List <String > pendingLogs = new ArrayList <>();
308+ for (Map .Entry <ServerName , List <String >> entry : logsByServer .entrySet ()) {
309+ if (live .contains (entry .getKey ())) {
310+ pendingLogs .addAll (entry .getValue ());
311+ } else {
312+ coveredLogs .addAll (entry .getValue ());
313+ }
314+ }
315+
316+ if (fs .exists (oldLogDir )) {
317+ BackupUtils .getFiles (fs , oldLogDir , coveredLogs ,
318+ path -> !AbstractFSWALProvider .isMetaFile (path ));
319+ }
320+
321+ return BackupUtils .computeLogBoundaries (rolledHosts , coveredLogs , pendingLogs );
322+ }
323+
251324 private void updateBackupMetadata () throws IOException {
252325 // The table list in backupInfo is good for both full backup and incremental backup.
253326 // For incremental backup, it contains the incremental backup table set.
0 commit comments