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.util; 019 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Map; 023import org.apache.hadoop.fs.Path; 024import org.apache.hadoop.hbase.net.Address; 025import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 026import org.apache.yetus.audience.InterfaceAudience; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030/** 031 * Tracks time boundaries for WAL file cleanup during backup operations. Maintains the oldest 032 * timestamp per RegionServer included in any backup, enabling safe determination of which WAL files 033 * can be deleted without compromising backup integrity. 034 */ 035@InterfaceAudience.Private 036public class BackupBoundaries { 037 private static final Logger LOG = LoggerFactory.getLogger(BackupBoundaries.class); 038 private static final BackupBoundaries EMPTY_BOUNDARIES = 039 new BackupBoundaries(Collections.emptyMap(), Long.MAX_VALUE); 040 041 // This map tracks, for every RegionServer, the least recent (= oldest / lowest timestamp) 042 // inclusion in any backup. In other words, it is the timestamp boundary up to which all backup 043 // roots have included the WAL in their backup. 044 private final Map<Address, Long> boundaries; 045 046 // The minimum WAL roll timestamp from the most recent backup of each backup root, used as a 047 // fallback cleanup boundary for RegionServers without explicit backup boundaries (e.g., servers 048 // that joined after backups began) 049 private final long oldestStartCode; 050 051 private BackupBoundaries(Map<Address, Long> boundaries, long oldestStartCode) { 052 this.boundaries = boundaries; 053 this.oldestStartCode = oldestStartCode; 054 } 055 056 public boolean isDeletable(Path walLogPath) { 057 try { 058 String hostname = BackupUtils.parseHostNameFromLogFile(walLogPath); 059 060 if (hostname == null) { 061 LOG.warn( 062 "Cannot parse hostname from RegionServer WAL file: {}. Ignoring cleanup of this log", 063 walLogPath); 064 return false; 065 } 066 067 Address address = Address.fromString(hostname); 068 long pathTs = AbstractFSWALProvider.getTimestamp(walLogPath.getName()); 069 070 if (!boundaries.containsKey(address)) { 071 boolean isDeletable = pathTs <= oldestStartCode; 072 if (LOG.isDebugEnabled()) { 073 LOG.debug( 074 "Boundary for {} not found. isDeletable = {} based on oldestStartCode = {} and WAL ts of {}", 075 walLogPath, isDeletable, oldestStartCode, pathTs); 076 } 077 return isDeletable; 078 } 079 080 long backupTs = boundaries.get(address); 081 if (pathTs <= backupTs) { 082 if (LOG.isDebugEnabled()) { 083 LOG.debug( 084 "WAL cleanup time-boundary found for server {}: {}. Ok to delete older file: {}", 085 address.getHostName(), pathTs, walLogPath); 086 } 087 return true; 088 } 089 090 if (LOG.isDebugEnabled()) { 091 LOG.debug("WAL cleanup time-boundary found for server {}: {}. Keeping younger file: {}", 092 address.getHostName(), backupTs, walLogPath); 093 } 094 095 return false; 096 } catch (Exception e) { 097 LOG.warn("Error occurred while filtering file: {}. Ignoring cleanup of this log", walLogPath, 098 e); 099 return false; 100 } 101 } 102 103 public Map<Address, Long> getBoundaries() { 104 return boundaries; 105 } 106 107 public long getOldestStartCode() { 108 return oldestStartCode; 109 } 110 111 public static BackupBoundariesBuilder builder(long tsCleanupBuffer) { 112 return new BackupBoundariesBuilder(tsCleanupBuffer); 113 } 114 115 public static class BackupBoundariesBuilder { 116 private final Map<Address, Long> boundaries = new HashMap<>(); 117 private final long tsCleanupBuffer; 118 119 private long oldestStartCode = Long.MAX_VALUE; 120 121 private BackupBoundariesBuilder(long tsCleanupBuffer) { 122 this.tsCleanupBuffer = tsCleanupBuffer; 123 } 124 125 public BackupBoundariesBuilder addBackupTimestamps(String host, long hostLogRollTs, 126 long backupStartCode) { 127 Address address = Address.fromString(host); 128 Long storedTs = boundaries.get(address); 129 if (storedTs == null || hostLogRollTs < storedTs) { 130 boundaries.put(address, hostLogRollTs); 131 } 132 133 if (oldestStartCode > backupStartCode) { 134 oldestStartCode = backupStartCode; 135 } 136 137 return this; 138 } 139 140 public BackupBoundaries build() { 141 if (boundaries.isEmpty()) { 142 return EMPTY_BOUNDARIES; 143 } 144 145 oldestStartCode -= tsCleanupBuffer; 146 return new BackupBoundaries(boundaries, oldestStartCode); 147 } 148 } 149}