001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.backup.impl; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.fs.FileStatus; 029import org.apache.hadoop.fs.FileSystem; 030import org.apache.hadoop.fs.Path; 031import org.apache.hadoop.fs.PathFilter; 032import org.apache.hadoop.hbase.HConstants; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.backup.util.BackupUtils; 035import org.apache.hadoop.hbase.client.Connection; 036import org.apache.hadoop.hbase.procedure2.store.wal.WALProcedureStore; 037import org.apache.hadoop.hbase.util.CommonFSUtils; 038import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 039import org.apache.yetus.audience.InterfaceAudience; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * After a full backup was created, the incremental backup will only store the changes made after 045 * the last full or incremental backup. Creating the backup copies the logfiles in .logs and 046 * .oldlogs since the last backup timestamp. 047 */ 048@InterfaceAudience.Private 049public class IncrementalBackupManager extends BackupManager { 050 public static final Logger LOG = LoggerFactory.getLogger(IncrementalBackupManager.class); 051 052 public IncrementalBackupManager(Connection conn, Configuration conf) throws IOException { 053 super(conn, conf); 054 } 055 056 /** 057 * Obtain the list of logs that need to be copied out for this incremental backup. The list is set 058 * in BackupInfo. 059 * @return The new map of RS log time stamps for this incremental backup, as computed by 060 * {@link BackupUtils#computeLogBoundaries}: the included logs are covered, the logs held 061 * back for a later backup (for example, logs still being split) are pending, and a host 062 * that still has logs keeps its previous boundary if this backup gives it no new one. 063 * @throws IOException exception 064 */ 065 public Map<String, Long> getIncrBackupLogFileMap() throws IOException { 066 List<String> logList; 067 Map<String, Long> previousTimestampMins = 068 BackupUtils.getRSLogTimestampMins(readLogTimestampMap()); 069 070 // get all new log files from .logs and .oldlogs after last TS and before new timestamp 071 if (previousTimestampMins.isEmpty()) { 072 throw new IOException("Cannot read any previous back up timestamps from backup system table. " 073 + "In order to create an incremental backup, at least one full backup is needed."); 074 } 075 076 Map<String, Long> previousLogRollByHost = readRegionServerLastLogRollResult(); 077 LOG.info("Execute roll log procedure for incremental backup ..."); 078 BackupUtils.logRoll(conn, backupInfo.getBackupRootDir(), conf); 079 080 Map<String, Long> rolledHosts = 081 BackupUtils.getRolledHosts(previousLogRollByHost, readRegionServerLastLogRollResult()); 082 083 LogFileSelection selection = getLogFilesForNewBackup(previousTimestampMins, rolledHosts, conf); 084 logList = excludeProcV2WALs(selection.included()); 085 backupInfo.setIncrBackupFileList(logList); 086 087 Map<String, Long> newTimestamps = BackupUtils.computeLogBoundaries(rolledHosts, 088 previousTimestampMins, selection.hostsWithLogs(), logList, selection.heldBack()); 089 LOG.debug("Log boundaries for incremental backup {}: {}", backupInfo.getBackupId(), 090 newTimestamps); 091 return newTimestamps; 092 } 093 094 private record LogFileSelection(List<String> included, List<String> heldBack, 095 Set<String> hostsWithLogs) { 096 } 097 098 private List<String> excludeProcV2WALs(List<String> logList) { 099 List<String> list = new ArrayList<>(); 100 for (int i = 0; i < logList.size(); i++) { 101 Path p = new Path(logList.get(i)); 102 String name = p.getName(); 103 104 if (name.startsWith(WALProcedureStore.LOG_PREFIX)) { 105 continue; 106 } 107 108 list.add(logList.get(i)); 109 } 110 return list; 111 } 112 113 /** 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. 117 * @param olderTimestamps the timestamp for each region server of the last backup. 118 * @param newestTimestamps the timestamp for each region server that the backup should lead to. 119 * @param conf the Hadoop and Hbase configuration 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 122 * @throws IOException exception 123 */ 124 private LogFileSelection getLogFilesForNewBackup(Map<String, Long> olderTimestamps, 125 Map<String, Long> newestTimestamps, Configuration conf) throws IOException { 126 LOG.debug("In getLogFilesForNewBackup()\n" + "olderTimestamps: " + olderTimestamps 127 + "\n newestTimestamps: " + newestTimestamps); 128 129 long prevBackupStartTs = Collections.min(olderTimestamps.values()); 130 Path walRootDir = CommonFSUtils.getWALRootDir(conf); 131 Path logDir = new Path(walRootDir, HConstants.HREGION_LOGDIR_NAME); 132 Path oldLogDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME); 133 FileSystem fs = walRootDir.getFileSystem(conf); 134 NewestLogFilter pathFilter = new NewestLogFilter(); 135 136 List<String> resultLogFiles = new ArrayList<>(); 137 List<String> newestLogs = new ArrayList<>(); 138 Set<String> hostsWithLogs = new HashSet<>(); 139 140 /* 141 * The old region servers and timestamps info we kept in backup system table may be out of sync 142 * if new region server is added or existing one lost. We'll deal with it here when processing 143 * the logs. If data in backup system table has more hosts, just ignore it. If the .logs 144 * directory includes more hosts, the additional hosts will not have old timestamps to compare 145 * with. We'll just use all the logs in that directory. We always write up-to-date region server 146 * and timestamp info to backup system table at the end of successful backup. 147 */ 148 FileStatus[] rss; 149 Path p; 150 String host; 151 Long oldTimeStamp; 152 String currentLogFile; 153 long currentLogTS; 154 155 // Get the files in .logs. 156 rss = fs.listStatus(logDir); 157 for (FileStatus rs : rss) { 158 p = rs.getPath(); 159 host = BackupUtils.parseHostNameFromLogFile(p); 160 if (host == null) { 161 continue; 162 } 163 hostsWithLogs.add(host); 164 FileStatus[] logs; 165 oldTimeStamp = olderTimestamps.get(host); 166 // It is possible that there is no old timestamp in backup system table for this host if 167 // this region server is newly added after our last backup. 168 if (oldTimeStamp == null) { 169 logs = fs.listStatus(p); 170 } else { 171 pathFilter.setLastBackupTS(oldTimeStamp); 172 logs = fs.listStatus(p, pathFilter); 173 } 174 for (FileStatus log : logs) { 175 LOG.debug("currentLogFile: " + log.getPath().toString()); 176 if (AbstractFSWALProvider.isMetaFile(log.getPath())) { 177 if (LOG.isDebugEnabled()) { 178 LOG.debug("Skip {} log file: {}", TableName.META_TABLE_NAME, log.getPath().getName()); 179 } 180 continue; 181 } 182 currentLogFile = log.getPath().toString(); 183 resultLogFiles.add(currentLogFile); 184 currentLogTS = BackupUtils.getCreationTime(log.getPath()); 185 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. 191 Long ts = newestTimestamps.get(host); 192 if (ts == null) { 193 LOG.debug("Holding back log {} of host {}, which did not take part in the log roll", 194 log.getPath(), host); 195 } 196 if (ts == null || currentLogTS > ts) { 197 newestLogs.add(currentLogFile); 198 } 199 } 200 } 201 202 // Include the .oldlogs files too. 203 List<String> oldlogs = BackupUtils.getFiles(fs, oldLogDir, new ArrayList<>(), path -> true); 204 for (String oldlog : oldlogs) { 205 p = new Path(oldlog); 206 currentLogFile = oldlog; 207 if (AbstractFSWALProvider.isMetaFile(p)) { 208 if (LOG.isDebugEnabled()) { 209 LOG.debug("Skip .meta log file: " + currentLogFile); 210 } 211 continue; 212 } 213 host = BackupUtils.parseHostFromOldLog(p); 214 if (host == null) { 215 continue; 216 } 217 hostsWithLogs.add(host); 218 currentLogTS = BackupUtils.getCreationTime(p); 219 oldTimeStamp = olderTimestamps.get(host); 220 /* 221 * It is possible that there is no old timestamp in backup system table for this host. At the 222 * time of our last backup operation, this rs did not exist. The reason can be one of the two: 223 * 1. The rs already left/crashed. Its logs were moved to .oldlogs. 2. The rs was added after 224 * our last backup. 225 */ 226 if (oldTimeStamp == null) { 227 if (currentLogTS < prevBackupStartTs) { 228 // This log file is really old, its region server was before our last backup. 229 continue; 230 } else { 231 resultLogFiles.add(currentLogFile); 232 } 233 } else if (currentLogTS > oldTimeStamp) { 234 resultLogFiles.add(currentLogFile); 235 } 236 237 Long newTimestamp = newestTimestamps.get(host); 238 if (newTimestamp != null && currentLogTS > newTimestamp) { 239 newestLogs.add(currentLogFile); 240 } 241 } 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. 244 resultLogFiles.removeAll(newestLogs); 245 return new LogFileSelection(resultLogFiles, newestLogs, hostsWithLogs); 246 } 247 248 static class NewestLogFilter implements PathFilter { 249 private Long lastBackupTS = 0L; 250 251 public NewestLogFilter() { 252 } 253 254 protected void setLastBackupTS(Long ts) { 255 this.lastBackupTS = ts; 256 } 257 258 @Override 259 public boolean accept(Path path) { 260 // skip meta table log -- ts.meta file 261 if (AbstractFSWALProvider.isMetaFile(path)) { 262 if (LOG.isDebugEnabled()) { 263 LOG.debug("Skip .meta log file: " + path.getName()); 264 } 265 return false; 266 } 267 long timestamp; 268 try { 269 timestamp = BackupUtils.getCreationTime(path); 270 return timestamp > lastBackupTS; 271 } catch (Exception e) { 272 LOG.warn("Cannot read timestamp of log file " + path); 273 return false; 274 } 275 } 276 } 277}