HBASE-24376 MergeNormalizer is merging non-adjacent regions and causing region overlaps/holes. (#1734)
Signed-off-by: Viraj Jasani <vjasani@apache.org> Signed-off-by: Jan Hentschel <jan.hentschel@ultratendency.com> Signed-off-by: Nick Dimiduk <ndimiduk@apache.org> Signed-off-by: stack <stack@apache.org>
This commit is contained in:
parent
a8724e8120
commit
80b64ef4dc
|
@ -170,6 +170,12 @@ public abstract class AbstractRegionNormalizer implements RegionNormalizer {
|
||||||
+ "number of regions: {}",
|
+ "number of regions: {}",
|
||||||
table, avgRegionSize, table, tableRegions.size());
|
table, avgRegionSize, table, tableRegions.size());
|
||||||
|
|
||||||
|
// The list of regionInfo from getRegionsOfTable() is ordered by regionName.
|
||||||
|
// regionName does not necessary guarantee the order by STARTKEY (let's say 'aa1', 'aa1!',
|
||||||
|
// in order by regionName, it will be 'aa1!' followed by 'aa1').
|
||||||
|
// This could result in normalizer merging non-adjacent regions into one and creates overlaps.
|
||||||
|
// In order to avoid that, sort the list by RegionInfo.COMPARATOR.
|
||||||
|
tableRegions.sort(RegionInfo.COMPARATOR);
|
||||||
final List<NormalizationPlan> plans = new ArrayList<>();
|
final List<NormalizationPlan> plans = new ArrayList<>();
|
||||||
for (int candidateIdx = 0; candidateIdx < tableRegions.size() - 1; candidateIdx++) {
|
for (int candidateIdx = 0; candidateIdx < tableRegions.size() - 1; candidateIdx++) {
|
||||||
final RegionInfo hri = tableRegions.get(candidateIdx);
|
final RegionInfo hri = tableRegions.get(candidateIdx);
|
||||||
|
|
|
@ -69,8 +69,11 @@ public class MergeNormalizationPlan implements NormalizationPlan {
|
||||||
public void execute(Admin admin) {
|
public void execute(Admin admin) {
|
||||||
LOG.info("Executing merging normalization plan: " + this);
|
LOG.info("Executing merging normalization plan: " + this);
|
||||||
try {
|
try {
|
||||||
|
// Do not use force=true as corner cases can happen, non adjacent regions,
|
||||||
|
// merge with a merged child region with no GC done yet, it is going to
|
||||||
|
// cause all different issues.
|
||||||
admin.mergeRegionsAsync(firstRegion.getEncodedNameAsBytes(),
|
admin.mergeRegionsAsync(firstRegion.getEncodedNameAsBytes(),
|
||||||
secondRegion.getEncodedNameAsBytes(), true);
|
secondRegion.getEncodedNameAsBytes(), false);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOG.error("Error during region merge: ", ex);
|
LOG.error("Error during region merge: ", ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.apache.hadoop.hbase.master.normalizer;
|
package org.apache.hadoop.hbase.master.normalizer;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -26,7 +27,6 @@ import java.util.List;
|
||||||
import org.apache.hadoop.hbase.HBaseClassTestRule;
|
import org.apache.hadoop.hbase.HBaseClassTestRule;
|
||||||
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.MetaTableAccessor;
|
import org.apache.hadoop.hbase.MetaTableAccessor;
|
||||||
import org.apache.hadoop.hbase.MiniHBaseCluster;
|
import org.apache.hadoop.hbase.MiniHBaseCluster;
|
||||||
import org.apache.hadoop.hbase.NamespaceDescriptor;
|
import org.apache.hadoop.hbase.NamespaceDescriptor;
|
||||||
|
@ -35,6 +35,8 @@ import org.apache.hadoop.hbase.client.Admin;
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
import org.apache.hadoop.hbase.client.Put;
|
||||||
import org.apache.hadoop.hbase.client.RegionInfo;
|
import org.apache.hadoop.hbase.client.RegionInfo;
|
||||||
import org.apache.hadoop.hbase.client.Table;
|
import org.apache.hadoop.hbase.client.Table;
|
||||||
|
import org.apache.hadoop.hbase.client.TableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
|
||||||
import org.apache.hadoop.hbase.master.HMaster;
|
import org.apache.hadoop.hbase.master.HMaster;
|
||||||
import org.apache.hadoop.hbase.master.TableNamespaceManager;
|
import org.apache.hadoop.hbase.master.TableNamespaceManager;
|
||||||
import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan.PlanType;
|
import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan.PlanType;
|
||||||
|
@ -93,7 +95,6 @@ public class TestSimpleRegionNormalizerOnCluster {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public void testRegionNormalizationSplitOnCluster() throws Exception {
|
public void testRegionNormalizationSplitOnCluster() throws Exception {
|
||||||
testRegionNormalizationSplitOnCluster(false);
|
testRegionNormalizationSplitOnCluster(false);
|
||||||
testRegionNormalizationSplitOnCluster(true);
|
testRegionNormalizationSplitOnCluster(true);
|
||||||
|
@ -141,9 +142,11 @@ public class TestSimpleRegionNormalizerOnCluster {
|
||||||
region.flush(true);
|
region.flush(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
HTableDescriptor htd = new HTableDescriptor(admin.getDescriptor(TABLENAME));
|
final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(TABLENAME))
|
||||||
htd.setNormalizationEnabled(true);
|
.setNormalizationEnabled(true)
|
||||||
admin.modifyTable(htd);
|
.build();
|
||||||
|
|
||||||
|
admin.modifyTable(td);
|
||||||
|
|
||||||
admin.flush(TABLENAME);
|
admin.flush(TABLENAME);
|
||||||
|
|
||||||
|
@ -179,8 +182,71 @@ public class TestSimpleRegionNormalizerOnCluster {
|
||||||
admin.deleteTable(TABLENAME);
|
admin.deleteTable(TABLENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test is to make sure that normalizer is only going to merge adjacent regions.
|
||||||
|
@Test
|
||||||
|
public void testNormalizerCannotMergeNonAdjacentRegions() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf(name.getMethodName());
|
||||||
|
MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
|
||||||
|
HMaster m = cluster.getMaster();
|
||||||
|
|
||||||
|
// create 5 regions with sizes to trigger merge of small regions
|
||||||
|
final byte[][] keys = {
|
||||||
|
Bytes.toBytes("aa"),
|
||||||
|
Bytes.toBytes("aa1"),
|
||||||
|
Bytes.toBytes("aa1!"),
|
||||||
|
Bytes.toBytes("aa2")
|
||||||
|
};
|
||||||
|
|
||||||
|
try (Table ht = TEST_UTIL.createTable(tableName, FAMILYNAME, keys)) {
|
||||||
|
// Need to get sorted list of regions here, the order is
|
||||||
|
// [, "aa"), ["aa", "aa1"), ["aa1", "aa1!"), ["aa1!", "aa2"), ["aa2", )
|
||||||
|
List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
|
||||||
|
generatedRegions.sort(Comparator.comparing(HRegion::getRegionInfo, RegionInfo.COMPARATOR));
|
||||||
|
|
||||||
|
// Region ["aa", "aa1") and ["aa1!", "aa2") are not adjacent, they are not supposed to
|
||||||
|
// merged.
|
||||||
|
HRegion region = generatedRegions.get(0);
|
||||||
|
generateTestData(region, 3);
|
||||||
|
region.flush(true);
|
||||||
|
|
||||||
|
region = generatedRegions.get(1);
|
||||||
|
generateTestData(region, 1);
|
||||||
|
region.flush(true);
|
||||||
|
|
||||||
|
region = generatedRegions.get(2);
|
||||||
|
generateTestData(region, 3);
|
||||||
|
region.flush(true);
|
||||||
|
|
||||||
|
region = generatedRegions.get(3);
|
||||||
|
generateTestData(region, 1);
|
||||||
|
region.flush(true);
|
||||||
|
|
||||||
|
region = generatedRegions.get(4);
|
||||||
|
generateTestData(region, 5);
|
||||||
|
region.flush(true);
|
||||||
|
|
||||||
|
final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(tableName))
|
||||||
|
.setNormalizationEnabled(true)
|
||||||
|
.build();
|
||||||
|
admin.modifyTable(td);
|
||||||
|
admin.flush(tableName);
|
||||||
|
|
||||||
|
assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName));
|
||||||
|
|
||||||
|
Thread.sleep(5000); // to let region load to update
|
||||||
|
|
||||||
|
// Compute the plan, no merge plan returned as they are not adjacent.
|
||||||
|
final List<NormalizationPlan> plans = m.getRegionNormalizer().computePlanForTable(tableName);
|
||||||
|
assertNull(plans);
|
||||||
|
} finally {
|
||||||
|
if (admin.tableExists(tableName)) {
|
||||||
|
admin.disableTable(tableName);
|
||||||
|
admin.deleteTable(tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public void testRegionNormalizationMergeOnCluster() throws Exception {
|
public void testRegionNormalizationMergeOnCluster() throws Exception {
|
||||||
final TableName tableName = TableName.valueOf(name.getMethodName());
|
final TableName tableName = TableName.valueOf(name.getMethodName());
|
||||||
MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
|
MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
|
||||||
|
@ -190,7 +256,7 @@ public class TestSimpleRegionNormalizerOnCluster {
|
||||||
try (Table ht = TEST_UTIL.createMultiRegionTable(tableName, FAMILYNAME, 5)) {
|
try (Table ht = TEST_UTIL.createMultiRegionTable(tableName, FAMILYNAME, 5)) {
|
||||||
// Need to get sorted list of regions here
|
// Need to get sorted list of regions here
|
||||||
List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
|
List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
|
||||||
Collections.sort(generatedRegions, Comparator.comparing(HRegion::getRegionInfo, RegionInfo.COMPARATOR));
|
generatedRegions.sort(Comparator.comparing(HRegion::getRegionInfo, RegionInfo.COMPARATOR));
|
||||||
|
|
||||||
HRegion region = generatedRegions.get(0);
|
HRegion region = generatedRegions.get(0);
|
||||||
generateTestData(region, 1);
|
generateTestData(region, 1);
|
||||||
|
@ -213,9 +279,11 @@ public class TestSimpleRegionNormalizerOnCluster {
|
||||||
region.flush(true);
|
region.flush(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
HTableDescriptor htd = new HTableDescriptor(admin.getDescriptor(tableName));
|
final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(tableName))
|
||||||
htd.setNormalizationEnabled(true);
|
.setNormalizationEnabled(true)
|
||||||
admin.modifyTable(htd);
|
.build();
|
||||||
|
|
||||||
|
admin.modifyTable(td);
|
||||||
|
|
||||||
admin.flush(tableName);
|
admin.flush(tableName);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue