HBASE-24316 GCMulitpleMergedRegionsProcedure is not idempotent (#1660)

It addresses couple issues:
   1. Make sure deleteMergeQualifiers() does not delete the row if there is no columns with "merge" keyword.
   2. GCMulitpleMergedRegionsProcedure now acquire an exclusive lock on the child region.

Signed-off-by: stack <stack@apache.org>
This commit is contained in:
huaxiangsun 2020-05-05 23:04:48 -07:00 committed by GitHub
parent 07077a3950
commit 045c909bdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 3 deletions

View File

@ -1846,6 +1846,16 @@ public class MetaTableAccessor {
qualifiers.add(qualifier); qualifiers.add(qualifier);
delete.addColumns(getCatalogFamily(), qualifier, HConstants.LATEST_TIMESTAMP); delete.addColumns(getCatalogFamily(), qualifier, HConstants.LATEST_TIMESTAMP);
} }
// There will be race condition that a GCMultipleMergedRegionsProcedure is scheduled while
// the previous GCMultipleMergedRegionsProcedure is still going on, in this case, the second
// GCMultipleMergedRegionsProcedure could delete the merged region by accident!
if (qualifiers.isEmpty()) {
LOG.info("No merged qualifiers for region " + mergeRegion.getRegionNameAsString() +
" in meta table, they are cleaned up already, Skip.");
return;
}
deleteFromMetaTable(connection, delete); deleteFromMetaTable(connection, delete);
LOG.info("Deleted merge references in " + mergeRegion.getRegionNameAsString() + LOG.info("Deleted merge references in " + mergeRegion.getRegionNameAsString() +
", deleted qualifiers " + qualifiers.stream().map(Bytes::toStringBinary). ", deleted qualifiers " + qualifiers.stream().map(Bytes::toStringBinary).

View File

@ -63,6 +63,26 @@ public class GCMultipleMergedRegionsProcedure extends
super(); super();
} }
@Override protected boolean holdLock(MasterProcedureEnv env) {
return true;
}
@Override
protected LockState acquireLock(final MasterProcedureEnv env) {
// It now takes an exclusive lock on the merged child region to make sure
// that no two parallel running of two GCMultipleMergedRegionsProcedures on the
// region.
if (env.getProcedureScheduler().waitRegion(this, mergedChild)) {
return LockState.LOCK_EVENT_WAIT;
}
return LockState.LOCK_ACQUIRED;
}
@Override
protected void releaseLock(final MasterProcedureEnv env) {
env.getProcedureScheduler().wakeRegion(this, mergedChild);
}
@Override @Override
public TableOperationType getTableOperationType() { public TableOperationType getTableOperationType() {
return TableOperationType.MERGED_REGIONS_GC; return TableOperationType.MERGED_REGIONS_GC;

View File

@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HBaseTestingUtility;
@ -31,8 +32,12 @@ import org.apache.hadoop.hbase.MetaTableAccessor;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionInfoBuilder; import org.apache.hadoop.hbase.client.RegionInfoBuilder;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.master.assignment.GCRegionProcedure; import org.apache.hadoop.hbase.master.assignment.GCRegionProcedure;
import org.apache.hadoop.hbase.master.assignment.GCMultipleMergedRegionsProcedure;
import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
import org.apache.hadoop.hbase.testclassification.LargeTests; import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.util.Threads; import org.apache.hadoop.hbase.util.Threads;
@ -168,18 +173,44 @@ public class TestMetaFixer {
assertEquals(1, MetaFixer.calculateMerges(10, report.getOverlaps()).size()); assertEquals(1, MetaFixer.calculateMerges(10, report.getOverlaps()).size());
MetaFixer fixer = new MetaFixer(services); MetaFixer fixer = new MetaFixer(services);
fixer.fixOverlaps(report); fixer.fixOverlaps(report);
CatalogJanitor cj = services.getCatalogJanitor();
await(10, () -> { await(10, () -> {
try { try {
services.getCatalogJanitor().scan(); if (cj.scan() > 0) {
final CatalogJanitor.Report postReport = services.getCatalogJanitor().getLastReport(); // It submits GC once, then it will immediately kick off another GC to test if
return postReport.isEmpty(); // GCMultipleMergedRegionsProcedure is idempotent. If it is not, it will create
// a hole.
Map<RegionInfo, Result> mergedRegions = cj.getLastReport().mergedRegions;
for (Map.Entry<RegionInfo, Result> e : mergedRegions.entrySet()) {
List<RegionInfo> parents = MetaTableAccessor.getMergeRegions(e.getValue().rawCells());
if (parents != null) {
ProcedureExecutor<MasterProcedureEnv> pe = services.getMasterProcedureExecutor();
pe.submitProcedure(new GCMultipleMergedRegionsProcedure(pe.getEnvironment(),
e.getKey(), parents));
}
}
return true;
}
return false;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}); });
// Wait until all GCs settled down
await(10, () -> {
return services.getMasterProcedureExecutor().getActiveProcIds().isEmpty();
});
// No orphan regions on FS
hbckChore.chore(); hbckChore.chore();
assertEquals(0, hbckChore.getOrphanRegionsOnFS().size()); assertEquals(0, hbckChore.getOrphanRegionsOnFS().size());
// No holes reported.
cj.scan();
final CatalogJanitor.Report postReport = cj.getLastReport();
assertTrue(postReport.isEmpty());
} }
/** /**