Skip to content

Commit 0b47bb4

Browse files
author
Hernan Gelaf-Romer
committed
HBASE-29240: Backup ancestry trees should have the ability to be invalidated
1 parent a86f9d7 commit 0b47bb4

File tree

9 files changed

+312
-2
lines changed

9 files changed

+312
-2
lines changed

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupClientFactory.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,24 @@
1818
package org.apache.hadoop.hbase.backup;
1919

2020
import java.io.IOException;
21+
import java.util.List;
22+
import java.util.Objects;
2123
import org.apache.hadoop.conf.Configuration;
24+
import org.apache.hadoop.hbase.backup.impl.BackupAdminImpl;
25+
import org.apache.hadoop.hbase.backup.impl.BackupManager;
2226
import org.apache.hadoop.hbase.backup.impl.FullTableBackupClient;
2327
import org.apache.hadoop.hbase.backup.impl.IncrementalTableBackupClient;
28+
import org.apache.hadoop.hbase.backup.impl.InvalidAncestryException;
2429
import org.apache.hadoop.hbase.backup.impl.TableBackupClient;
2530
import org.apache.hadoop.hbase.client.Connection;
2631
import org.apache.yetus.audience.InterfaceAudience;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
2734

2835
@InterfaceAudience.Private
2936
public final class BackupClientFactory {
37+
private static final Logger LOG = LoggerFactory.getLogger(BackupClientFactory.class);
38+
3039
private BackupClientFactory() {
3140
}
3241

@@ -49,8 +58,42 @@ public static TableBackupClient create(Connection conn, String backupId, BackupR
4958
BackupType type = request.getBackupType();
5059
if (type == BackupType.FULL) {
5160
return new FullTableBackupClient(conn, backupId, request);
52-
} else {
53-
return new IncrementalTableBackupClient(conn, backupId, request);
5461
}
62+
63+
String latestFullBackup = getLatestFullBackupId(conn, request);
64+
65+
try (BackupAdmin admin = new BackupAdminImpl(conn)) {
66+
boolean isInvalidAncestry = admin.getBackupInfo(latestFullBackup).isInvalidAncestry();
67+
68+
if (!isInvalidAncestry) {
69+
return new IncrementalTableBackupClient(conn, backupId, request);
70+
}
71+
72+
if (request.getFailOnInvalidAncestry()) {
73+
throw new InvalidAncestryException(request);
74+
}
75+
76+
LOG.info("Ancestry is invalid for backupId {}, creating a full backup", latestFullBackup);
77+
return new FullTableBackupClient(conn, backupId, request);
78+
}
79+
}
80+
81+
private static String getLatestFullBackupId(Connection conn, BackupRequest request)
82+
throws IOException {
83+
try (BackupManager backupManager = new BackupManager(conn, conn.getConfiguration())) {
84+
// Sorted in desc order by time
85+
List<BackupInfo> backups = backupManager.getBackupHistory(true);
86+
87+
for (BackupInfo info : backups) {
88+
if (
89+
info.getType() == BackupType.FULL
90+
&& Objects.equals(info.getBackupRootDir(), request.getTargetRootDir())
91+
) {
92+
return info.getBackupId();
93+
}
94+
}
95+
}
96+
throw new RuntimeException(
97+
"Could not find a valid full backup for incremental request " + request);
5598
}
5699
}

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupInfo.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ public enum BackupPhase {
170170
*/
171171
private boolean noChecksumVerify;
172172

173+
private boolean isInvalidAncestry = false;
174+
173175
public BackupInfo() {
174176
backupTableInfoMap = new HashMap<>();
175177
}
@@ -203,6 +205,14 @@ public void setBandwidth(long bandwidth) {
203205
this.bandwidth = bandwidth;
204206
}
205207

208+
public void setInvalidAncestry(boolean invalidAncestry) {
209+
isInvalidAncestry = invalidAncestry;
210+
}
211+
212+
public boolean isInvalidAncestry() {
213+
return isInvalidAncestry;
214+
}
215+
206216
public void setNoChecksumVerify(boolean noChecksumVerify) {
207217
this.noChecksumVerify = noChecksumVerify;
208218
}
@@ -423,6 +433,7 @@ public BackupProtos.BackupInfo toProtosBackupInfo() {
423433
builder.setBackupType(BackupProtos.BackupType.valueOf(getType().name()));
424434
builder.setWorkersNumber(workers);
425435
builder.setBandwidth(bandwidth);
436+
builder.setIsInvalidAncestry(isInvalidAncestry);
426437
return builder.build();
427438
}
428439

@@ -518,6 +529,7 @@ public static BackupInfo fromProto(BackupProtos.BackupInfo proto) {
518529
context.setType(BackupType.valueOf(proto.getBackupType().name()));
519530
context.setWorkers(proto.getWorkersNumber());
520531
context.setBandwidth(proto.getBandwidth());
532+
context.setInvalidAncestry(proto.getIsInvalidAncestry());
521533
return context;
522534
}
523535

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.backup;
19+
20+
import java.io.IOException;
21+
import java.util.Optional;
22+
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
23+
import org.apache.hadoop.hbase.TableName;
24+
import org.apache.hadoop.hbase.backup.impl.BackupSystemTable;
25+
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
26+
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
27+
import org.apache.hadoop.hbase.coprocessor.MasterObserver;
28+
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
29+
import org.apache.yetus.audience.InterfaceAudience;
30+
31+
@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
32+
public class BackupMasterObserver implements MasterObserver, MasterCoprocessor {
33+
34+
private final Optional<MasterObserver> observer;
35+
36+
public BackupMasterObserver() {
37+
this.observer = Optional.of(this);
38+
}
39+
40+
@Override
41+
public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
42+
TableName tableName) throws IOException {
43+
BackupSystemTable table = new BackupSystemTable(ctx.getEnvironment().getConnection());
44+
try {
45+
table.startBackupExclusiveOperation();
46+
table.invalidateTableAncestry(tableName);
47+
} finally {
48+
table.finishBackupExclusiveOperation();
49+
}
50+
}
51+
52+
@Override
53+
public Optional<MasterObserver> getMasterObserver() {
54+
return observer;
55+
}
56+
}

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupRequest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public Builder() {
3535
request = new BackupRequest();
3636
}
3737

38+
public Builder withFailOnInvalidAncestry(boolean failOnInvalidAncestry) {
39+
request.failOnInvalidAncestry = failOnInvalidAncestry;
40+
return this;
41+
}
42+
3843
public Builder withBackupType(BackupType type) {
3944
request.setBackupType(type);
4045
return this;
@@ -89,10 +94,20 @@ public BackupRequest build() {
8994
private boolean noChecksumVerify = false;
9095
private String backupSetName;
9196
private String yarnPoolName;
97+
private boolean failOnInvalidAncestry = false;
9298

9399
private BackupRequest() {
94100
}
95101

102+
private BackupRequest setFailOnInvalidAncestry(boolean failOnInvalidAncestry) {
103+
this.failOnInvalidAncestry = failOnInvalidAncestry;
104+
return this;
105+
}
106+
107+
public boolean getFailOnInvalidAncestry() {
108+
return failOnInvalidAncestry;
109+
}
110+
96111
private BackupRequest setBackupType(BackupType type) {
97112
this.type = type;
98113
return this;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.hadoop.hbase.backup.BackupHFileCleaner;
3131
import org.apache.hadoop.hbase.backup.BackupInfo;
3232
import org.apache.hadoop.hbase.backup.BackupInfo.BackupState;
33+
import org.apache.hadoop.hbase.backup.BackupMasterObserver;
3334
import org.apache.hadoop.hbase.backup.BackupObserver;
3435
import org.apache.hadoop.hbase.backup.BackupRestoreConstants;
3536
import org.apache.hadoop.hbase.backup.BackupType;
@@ -125,6 +126,10 @@ public static void decorateMasterConfiguration(Configuration conf) {
125126
+ "Added master procedure manager: {}",
126127
cleanerClass, masterProcedureClass, BackupHFileCleaner.class.getName());
127128
}
129+
130+
String observers = conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY);
131+
conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
132+
(observers == null ? "" : observers + ",") + BackupMasterObserver.class.getName());
128133
}
129134

130135
/**

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,29 @@ public void close() {
277277
// do nothing
278278
}
279279

280+
public void invalidateTableAncestry(TableName toInvalidate) throws IOException {
281+
List<BackupInfo> fullTableBackups = getCompletedFullBackups();
282+
List<Put> invalidatePuts = new ArrayList<>(fullTableBackups.size());
283+
284+
for (BackupInfo backupInfo : fullTableBackups) {
285+
// to minimize the amount of mutations against the backup system table, we only
286+
// need to update full backups that have currently have a valid ancestry line
287+
if (
288+
backupInfo.getTables().contains(toInvalidate) && backupInfo.getType() == BackupType.FULL
289+
&& !backupInfo.isInvalidAncestry()
290+
) {
291+
backupInfo.setInvalidAncestry(true);
292+
invalidatePuts.add(createPutForBackupInfo(backupInfo));
293+
LOG.info("Invalidating ancestry for backup {} due to table {}", backupInfo.getBackupId(),
294+
toInvalidate);
295+
}
296+
}
297+
298+
try (BufferedMutator mutator = connection.getBufferedMutator(tableName)) {
299+
mutator.mutate(invalidatePuts);
300+
}
301+
}
302+
280303
/**
281304
* Updates status (state) of a backup session in backup system table table
282305
* @param info backup info
@@ -841,6 +864,24 @@ public ArrayList<BackupInfo> getBackupInfos(BackupState state) throws IOExceptio
841864
}
842865
}
843866

867+
private List<BackupInfo> getCompletedFullBackups() throws IOException {
868+
Scan scan = createScanForBackupHistory();
869+
List<BackupInfo> backups = new ArrayList<>();
870+
871+
try (Table table = connection.getTable(tableName)) {
872+
ResultScanner scanner = table.getScanner(scan);
873+
Result res;
874+
while ((res = scanner.next()) != null) {
875+
res.advance();
876+
BackupInfo context = cellToBackupInfo(res.current());
877+
if (context.getState() == BackupState.COMPLETE && context.getType() == BackupType.FULL) {
878+
backups.add(context);
879+
}
880+
}
881+
}
882+
return backups;
883+
}
884+
844885
/**
845886
* Write the current timestamps for each regionserver to backup system table after a successful
846887
* full or incremental backup. The saved timestamp is of the last log file that was backed up
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.backup.impl;
19+
20+
import org.apache.hadoop.hbase.HBaseIOException;
21+
import org.apache.hadoop.hbase.backup.BackupRequest;
22+
import org.apache.yetus.audience.InterfaceAudience;
23+
import org.apache.yetus.audience.InterfaceStability;
24+
25+
@InterfaceAudience.Public
26+
@InterfaceStability.Evolving
27+
public class InvalidAncestryException extends HBaseIOException {
28+
public InvalidAncestryException(BackupRequest request) {
29+
super("Could not take incremental backup %s because it has an invalid ancestry tree"
30+
.formatted(request));
31+
}
32+
}

0 commit comments

Comments
 (0)