Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
15ad34d
HBASE-28996: Implement Custom ReplicationEndpoint to Enable WAL Backu…
vinayakphegde Feb 18, 2025
fa979c6
HBASE-29025: Enhance the full backup command to support Continuous Ba…
vinayakphegde Mar 4, 2025
ec00c4e
HBASE-29210: Introduce Validation for PITR-Critical Backup Deletion (…
vinayakphegde Apr 10, 2025
16f2cca
HBASE-29261: Investigate flaw in backup deletion validation of PITR-c…
vinayakphegde May 20, 2025
4720892
HBASE-29133: Implement "pitr" Command for Point-in-Time Restore (#6717)
vinayakphegde May 30, 2025
48f50ef
HBASE-29255: Integrate backup WAL cleanup logic with the delete comma…
vinayakphegde Jun 11, 2025
e794511
HBASE-28990 Modify Incremental Backup for Continuous Backup (#6788)
ankitsol Jun 20, 2025
107b5a1
HBASE-29449 Update backup describe command for continuous backup (#7045)
ankitsol Jul 15, 2025
ea213c5
HBASE-29445 Add Option to Specify Custom Backup Location in PITR (#7153)
vinayakphegde Jul 16, 2025
f137d3e
HBASE-29375 Add Unit Tests for BackupAdminImpl and Improve Test Granu…
vinayakphegde Jul 29, 2025
973da3f
[HBASE-29520] Utilize Backed-up Bulkloaded Files in Incremental Backu…
ankitsol Sep 8, 2025
31254e7
HBASE-29656 Scan WALs to identify bulkload operations for incremental…
ankitsol Oct 27, 2025
cf9d0ac
HBASE-29825: Incremental backup is failing due to incorrect timezone …
kgeisz Feb 4, 2026
4f50b4e
HBASE-29891: Multi-table continuous incremental backup is failing bec…
kgeisz Feb 23, 2026
71467dd
Add MULTI_TABLE_HFILEOUTPUTFORMAT_CONF_DEFAULT
kgeisz Mar 11, 2026
b4b6e49
Add unit test coverage for WALPlayer changes
kgeisz Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions hbase-backup/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
Expand Down Expand Up @@ -102,15 +101,9 @@ protected int executeRestore(boolean check, TableName[] fromTables, TableName[]
return -5;
}

// TODO: Currently hardcoding keepOriginalSplits=false and restoreRootDir via tmp dir.
// These should come from user input (same issue exists in normal restore).
// Expose them as configurable options in future.
PointInTimeRestoreRequest pointInTimeRestoreRequest =
new PointInTimeRestoreRequest.Builder().withBackupRootDir(backupRootDir).withCheck(check)
.withFromTables(fromTables).withToTables(toTables).withOverwrite(isOverwrite)
.withToDateTime(endTime).withKeepOriginalSplits(false).withRestoreRootDir(
BackupUtils.getTmpRestoreOutputDir(FileSystem.get(conf), conf).toString())
.build();
PointInTimeRestoreRequest pointInTimeRestoreRequest = new PointInTimeRestoreRequest.Builder()
.withBackupRootDir(backupRootDir).withCheck(check).withFromTables(fromTables)
.withToTables(toTables).withOverwrite(isOverwrite).withToDateTime(endTime).build();

client.pointInTimeRestore(pointInTimeRestoreRequest);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,25 @@
public final class PointInTimeRestoreRequest {

private final String backupRootDir;
private final String restoreRootDir;
private final boolean check;
private final TableName[] fromTables;
private final TableName[] toTables;
private final boolean overwrite;
private final long toDateTime;
private final boolean isKeepOriginalSplits;

private PointInTimeRestoreRequest(Builder builder) {
this.backupRootDir = builder.backupRootDir;
this.restoreRootDir = builder.restoreRootDir;
this.check = builder.check;
this.fromTables = builder.fromTables;
this.toTables = builder.toTables;
this.overwrite = builder.overwrite;
this.toDateTime = builder.toDateTime;
this.isKeepOriginalSplits = builder.isKeepOriginalSplits;
}

public String getBackupRootDir() {
return backupRootDir;
}

public String getRestoreRootDir() {
return restoreRootDir;
}

public boolean isCheck() {
return check;
}
Expand All @@ -74,30 +66,19 @@ public long getToDateTime() {
return toDateTime;
}

public boolean isKeepOriginalSplits() {
return isKeepOriginalSplits;
}

public static class Builder {
private String backupRootDir;
private String restoreRootDir;
private boolean check = false;
private TableName[] fromTables;
private TableName[] toTables;
private boolean overwrite = false;
private long toDateTime;
private boolean isKeepOriginalSplits;

public Builder withBackupRootDir(String backupRootDir) {
this.backupRootDir = backupRootDir;
return this;
}

public Builder withRestoreRootDir(String restoreRootDir) {
this.restoreRootDir = restoreRootDir;
return this;
}

public Builder withCheck(boolean check) {
this.check = check;
return this;
Expand All @@ -123,11 +104,6 @@ public Builder withToDateTime(long dateTime) {
return this;
}

public Builder withKeepOriginalSplits(boolean isKeepOriginalSplits) {
this.isKeepOriginalSplits = isKeepOriginalSplits;
return this;
}

public PointInTimeRestoreRequest build() {
return new PointInTimeRestoreRequest(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.CONF_CONTINUOUS_BACKUP_PITR_WINDOW_DAYS;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.CONF_CONTINUOUS_BACKUP_WAL_DIR;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.DEFAULT_CONTINUOUS_BACKUP_PITR_WINDOW_DAYS;
import static org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2.MULTI_TABLE_HFILEOUTPUTFORMAT_CONF_KEY;
import static org.apache.hadoop.hbase.mapreduce.WALPlayer.IGNORE_EMPTY_FILES;

import java.io.IOException;
Expand All @@ -33,9 +34,7 @@
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.backup.BackupRestoreFactory;
import org.apache.hadoop.hbase.backup.PointInTimeRestoreRequest;
import org.apache.hadoop.hbase.backup.RestoreJob;
import org.apache.hadoop.hbase.backup.RestoreRequest;
import org.apache.hadoop.hbase.backup.util.BackupUtils;
import org.apache.hadoop.hbase.client.Connection;
Expand Down Expand Up @@ -411,6 +410,9 @@ private Tool initializeWalPlayer(long startTime, long endTime) {
conf.setLong(WALInputFormat.START_TIME_KEY, startTime);
conf.setLong(WALInputFormat.END_TIME_KEY, endTime);
conf.setBoolean(IGNORE_EMPTY_FILES, true);
// HFile output format defaults to false in HFileOutputFormat2, but we are explicitly setting
// it here just in case
conf.setBoolean(MULTI_TABLE_HFILEOUTPUTFORMAT_CONF_KEY, false);
Tool walPlayer = new WALPlayer();
walPlayer.setConf(conf);
return walPlayer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@
*/
package org.apache.hadoop.hbase.backup.impl;

import static org.apache.hadoop.hbase.backup.BackupInfo.withRoot;
import static org.apache.hadoop.hbase.backup.BackupInfo.withState;
import static org.apache.hadoop.hbase.backup.BackupInfo.withType;
import static org.apache.hadoop.hbase.backup.impl.BackupSystemTable.Order.NEW_TO_OLD;

import com.google.errorprone.annotations.RestrictedApi;
import java.io.IOException;
import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
package org.apache.hadoop.hbase.backup.impl;

import static org.apache.hadoop.hbase.backup.BackupInfo.withState;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.CONF_CONTINUOUS_BACKUP_PITR_WINDOW_DAYS;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.CONF_CONTINUOUS_BACKUP_WAL_DIR;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.CONTINUOUS_BACKUP_REPLICATION_PEER;
Expand Down Expand Up @@ -50,7 +49,6 @@
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_WORKERS_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME_DESC;
import static org.apache.hadoop.hbase.backup.impl.BackupSystemTable.Order.NEW_TO_OLD;
import static org.apache.hadoop.hbase.backup.replication.ContinuousBackupReplicationEndpoint.ONE_DAY_IN_MILLISECONDS;
import static org.apache.hadoop.hbase.backup.util.BackupUtils.DATE_FORMAT;

Expand Down Expand Up @@ -83,7 +81,7 @@
import org.apache.hadoop.hbase.backup.BackupRestoreConstants.BackupCommand;
import org.apache.hadoop.hbase.backup.BackupType;
import org.apache.hadoop.hbase.backup.HBackupFileSystem;
import org.apache.hadoop.hbase.backup.util.BackupFileSystemManager;
import org.apache.hadoop.hbase.backup.replication.BackupFileSystemManager;
import org.apache.hadoop.hbase.backup.util.BackupSet;
import org.apache.hadoop.hbase.backup.util.BackupUtils;
import org.apache.hadoop.hbase.client.Admin;
Expand Down Expand Up @@ -803,8 +801,7 @@ private List<TableName> getTablesDependentOnBackupForPITR(String backupId) throw
}

// Check if there is any other valid backup that can cover the PITR window
List<BackupInfo> allBackups =
backupSystemTable.getBackupHistory(withState(BackupInfo.BackupState.COMPLETE));
List<BackupInfo> allBackups = backupSystemTable.getBackupInfos(BackupState.COMPLETE);
boolean hasAnotherValidBackup =
canAnyOtherBackupCover(allBackups, targetBackup, table, coveredPitrWindow.get(),
continuousBackupStartTimes.get(table), maxAllowedPITRTime, currentTime);
Expand Down Expand Up @@ -896,8 +893,7 @@ private boolean canAnyOtherBackupCover(List<BackupInfo> allBackups, BackupInfo c

/**
* Cleans up Write-Ahead Logs (WALs) that are no longer required for PITR after a successful
* backup deletion. If no full backups are present, all WALs are deleted, tables are removed
* from continuous backup metadata, and the associated replication peer is disabled.
* backup deletion.
*/
private void cleanUpUnusedBackupWALs() throws IOException {
Configuration conf = getConf() != null ? getConf() : HBaseConfiguration.create();
Expand All @@ -908,8 +904,7 @@ private void cleanUpUnusedBackupWALs() throws IOException {
return;
}

try (Admin admin = conn.getAdmin();
BackupSystemTable sysTable = new BackupSystemTable(conn)) {
try (BackupSystemTable sysTable = new BackupSystemTable(conn)) {
// Get list of tables under continuous backup
Map<TableName, Long> continuousBackupTables = sysTable.getContinuousBackupTableSet();
if (continuousBackupTables.isEmpty()) {
Expand All @@ -920,15 +915,7 @@ private void cleanUpUnusedBackupWALs() throws IOException {
// Find the earliest timestamp after which WALs are still needed
long cutoffTimestamp = determineWALCleanupCutoffTime(sysTable);
if (cutoffTimestamp == 0) {
// No full backup exists. PITR cannot function without a base full backup.
// Clean up all WALs, remove tables from backup metadata, and disable the replication
// peer.
System.out
.println("No full backups found. Cleaning up all WALs and disabling replication peer.");

disableContinuousBackupReplicationPeer(admin);
removeAllTablesFromContinuousBackup(sysTable);
deleteAllBackupWALFiles(conf, backupWalDir);
System.err.println("ERROR: No valid full backup found. Skipping WAL cleanup.");
return;
}

Expand All @@ -947,8 +934,7 @@ private void cleanUpUnusedBackupWALs() throws IOException {
* @return cutoff timestamp or 0 if not found
*/
long determineWALCleanupCutoffTime(BackupSystemTable sysTable) throws IOException {
List<BackupInfo> backupInfos =
sysTable.getBackupHistory(withState(BackupInfo.BackupState.COMPLETE));
List<BackupInfo> backupInfos = sysTable.getBackupInfos(BackupState.COMPLETE);
Collections.reverse(backupInfos); // Start from oldest

for (BackupInfo backupInfo : backupInfos) {
Expand All @@ -959,16 +945,6 @@ long determineWALCleanupCutoffTime(BackupSystemTable sysTable) throws IOExceptio
return 0;
}

private void disableContinuousBackupReplicationPeer(Admin admin) throws IOException {
for (ReplicationPeerDescription peer : admin.listReplicationPeers()) {
if (peer.getPeerId().equals(CONTINUOUS_BACKUP_REPLICATION_PEER) && peer.isEnabled()) {
admin.disableReplicationPeer(CONTINUOUS_BACKUP_REPLICATION_PEER);
System.out.println("Disabled replication peer: " + CONTINUOUS_BACKUP_REPLICATION_PEER);
break;
}
}
}

/**
* Updates the start time for continuous backups if older than cutoff timestamp.
* @param sysTable Backup system table
Expand All @@ -991,49 +967,6 @@ void updateBackupTableStartTimes(BackupSystemTable sysTable, long cutoffTimestam
}
}

private void removeAllTablesFromContinuousBackup(BackupSystemTable sysTable)
throws IOException {
Map<TableName, Long> allTables = sysTable.getContinuousBackupTableSet();
if (!allTables.isEmpty()) {
sysTable.removeContinuousBackupTableSet(allTables.keySet());
System.out.println("Removed all tables from continuous backup metadata.");
}
}

private void deleteAllBackupWALFiles(Configuration conf, String backupWalDir)
throws IOException {
try {
BackupFileSystemManager manager =
new BackupFileSystemManager(CONTINUOUS_BACKUP_REPLICATION_PEER, conf, backupWalDir);
FileSystem fs = manager.getBackupFs();
Path walDir = manager.getWalsDir();
Path bulkloadDir = manager.getBulkLoadFilesDir();

// Delete contents under WAL directory
if (fs.exists(walDir)) {
FileStatus[] walContents = fs.listStatus(walDir);
for (FileStatus item : walContents) {
fs.delete(item.getPath(), true); // recursive delete of each child
}
System.out.println("Deleted all contents under WAL directory: " + walDir);
}

// Delete contents under bulk load directory
if (fs.exists(bulkloadDir)) {
FileStatus[] bulkContents = fs.listStatus(bulkloadDir);
for (FileStatus item : bulkContents) {
fs.delete(item.getPath(), true); // recursive delete of each child
}
System.out.println("Deleted all contents under Bulk Load directory: " + bulkloadDir);
}

} catch (IOException e) {
System.out.println("WARNING: Failed to delete contents under backup directories: "
+ backupWalDir + ". Error: " + e.getMessage());
throw e;
}
}

/**
* Cleans up old WAL and bulk-loaded files based on the determined cutoff timestamp.
*/
Expand Down Expand Up @@ -1072,15 +1005,13 @@ void deleteOldWALFiles(Configuration conf, String backupWalDir, long cutoffTime)
if (dayStart + ONE_DAY_IN_MILLISECONDS - 1 < cutoffTime) {
System.out.println("Deleting outdated WAL directory: " + dirPath);
fs.delete(dirPath, true);
Path bulkloadPath = new Path(bulkloadDir, dirName);
System.out.println("Deleting corresponding bulk-load directory: " + bulkloadPath);
fs.delete(bulkloadPath, true);
fs.delete(new Path(bulkloadDir, dirName), true);
}
} catch (ParseException e) {
System.out.println("WARNING: Failed to parse directory name '" + dirName
+ "'. Skipping. Error: " + e.getMessage());
} catch (IOException e) {
System.err.println("WARNING: Failed to delete directory '" + dirPath
System.out.println("WARNING: Failed to delete directory '" + dirPath
+ "'. Skipping. Error: " + e.getMessage());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,7 @@ private Delete createDeleteForIncrBackupTableSet(String backupRoot) {
private Delete createDeleteForContinuousBackupTableSet(Set<TableName> tables) {
Delete delete = new Delete(rowkey(CONTINUOUS_BACKUP_SET));
for (TableName tableName : tables) {
delete.addColumn(META_FAMILY, Bytes.toBytes(tableName.getNameAsString()));
delete.addColumns(META_FAMILY, Bytes.toBytes(tableName.getNameAsString()));
}
return delete;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
*/
package org.apache.hadoop.hbase.backup.impl;

import static org.apache.hadoop.hbase.backup.BackupInfo.withState;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -50,7 +48,7 @@ public DefaultPitrRestoreHandler(Connection conn, PointInTimeRestoreRequest requ
protected List<PitrBackupMetadata> getBackupMetadata(PointInTimeRestoreRequest request)
throws IOException {
try (BackupSystemTable table = new BackupSystemTable(conn)) {
return table.getBackupHistory(withState(BackupInfo.BackupState.COMPLETE)).stream()
return table.getBackupInfos(BackupInfo.BackupState.COMPLETE).stream()
.map(BackupInfoAdapter::new).collect(Collectors.toList());
}
}
Expand Down
Loading