Skip to content

Commit 80b64ef

Browse files
authored
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>
1 parent a8724e8 commit 80b64ef

File tree

3 files changed

+88
-11
lines changed

3 files changed

+88
-11
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/AbstractRegionNormalizer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ protected List<NormalizationPlan> getMergeNormalizationPlan(TableName table) {
170170
+ "number of regions: {}",
171171
table, avgRegionSize, table, tableRegions.size());
172172

173+
// The list of regionInfo from getRegionsOfTable() is ordered by regionName.
174+
// regionName does not necessary guarantee the order by STARTKEY (let's say 'aa1', 'aa1!',
175+
// in order by regionName, it will be 'aa1!' followed by 'aa1').
176+
// This could result in normalizer merging non-adjacent regions into one and creates overlaps.
177+
// In order to avoid that, sort the list by RegionInfo.COMPARATOR.
178+
tableRegions.sort(RegionInfo.COMPARATOR);
173179
final List<NormalizationPlan> plans = new ArrayList<>();
174180
for (int candidateIdx = 0; candidateIdx < tableRegions.size() - 1; candidateIdx++) {
175181
final RegionInfo hri = tableRegions.get(candidateIdx);

hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,11 @@ public String toString() {
6969
public void execute(Admin admin) {
7070
LOG.info("Executing merging normalization plan: " + this);
7171
try {
72+
// Do not use force=true as corner cases can happen, non adjacent regions,
73+
// merge with a merged child region with no GC done yet, it is going to
74+
// cause all different issues.
7275
admin.mergeRegionsAsync(firstRegion.getEncodedNameAsBytes(),
73-
secondRegion.getEncodedNameAsBytes(), true);
76+
secondRegion.getEncodedNameAsBytes(), false);
7477
} catch (IOException ex) {
7578
LOG.error("Error during region merge: ", ex);
7679
}

hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.hbase.master.normalizer;
1919

2020
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertNull;
2122

2223
import java.io.IOException;
2324
import java.util.Collections;
@@ -26,7 +27,6 @@
2627
import org.apache.hadoop.hbase.HBaseClassTestRule;
2728
import org.apache.hadoop.hbase.HBaseTestingUtility;
2829
import org.apache.hadoop.hbase.HConstants;
29-
import org.apache.hadoop.hbase.HTableDescriptor;
3030
import org.apache.hadoop.hbase.MetaTableAccessor;
3131
import org.apache.hadoop.hbase.MiniHBaseCluster;
3232
import org.apache.hadoop.hbase.NamespaceDescriptor;
@@ -35,6 +35,8 @@
3535
import org.apache.hadoop.hbase.client.Put;
3636
import org.apache.hadoop.hbase.client.RegionInfo;
3737
import org.apache.hadoop.hbase.client.Table;
38+
import org.apache.hadoop.hbase.client.TableDescriptor;
39+
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
3840
import org.apache.hadoop.hbase.master.HMaster;
3941
import org.apache.hadoop.hbase.master.TableNamespaceManager;
4042
import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan.PlanType;
@@ -93,7 +95,6 @@ public static void afterAllTests() throws Exception {
9395
}
9496

9597
@Test
96-
@SuppressWarnings("deprecation")
9798
public void testRegionNormalizationSplitOnCluster() throws Exception {
9899
testRegionNormalizationSplitOnCluster(false);
99100
testRegionNormalizationSplitOnCluster(true);
@@ -141,9 +142,11 @@ void testRegionNormalizationSplitOnCluster(boolean limitedByQuota) throws Except
141142
region.flush(true);
142143
}
143144

144-
HTableDescriptor htd = new HTableDescriptor(admin.getDescriptor(TABLENAME));
145-
htd.setNormalizationEnabled(true);
146-
admin.modifyTable(htd);
145+
final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(TABLENAME))
146+
.setNormalizationEnabled(true)
147+
.build();
148+
149+
admin.modifyTable(td);
147150

148151
admin.flush(TABLENAME);
149152

@@ -179,8 +182,71 @@ void testRegionNormalizationSplitOnCluster(boolean limitedByQuota) throws Except
179182
admin.deleteTable(TABLENAME);
180183
}
181184

185+
// This test is to make sure that normalizer is only going to merge adjacent regions.
186+
@Test
187+
public void testNormalizerCannotMergeNonAdjacentRegions() throws Exception {
188+
final TableName tableName = TableName.valueOf(name.getMethodName());
189+
MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
190+
HMaster m = cluster.getMaster();
191+
192+
// create 5 regions with sizes to trigger merge of small regions
193+
final byte[][] keys = {
194+
Bytes.toBytes("aa"),
195+
Bytes.toBytes("aa1"),
196+
Bytes.toBytes("aa1!"),
197+
Bytes.toBytes("aa2")
198+
};
199+
200+
try (Table ht = TEST_UTIL.createTable(tableName, FAMILYNAME, keys)) {
201+
// Need to get sorted list of regions here, the order is
202+
// [, "aa"), ["aa", "aa1"), ["aa1", "aa1!"), ["aa1!", "aa2"), ["aa2", )
203+
List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
204+
generatedRegions.sort(Comparator.comparing(HRegion::getRegionInfo, RegionInfo.COMPARATOR));
205+
206+
// Region ["aa", "aa1") and ["aa1!", "aa2") are not adjacent, they are not supposed to
207+
// merged.
208+
HRegion region = generatedRegions.get(0);
209+
generateTestData(region, 3);
210+
region.flush(true);
211+
212+
region = generatedRegions.get(1);
213+
generateTestData(region, 1);
214+
region.flush(true);
215+
216+
region = generatedRegions.get(2);
217+
generateTestData(region, 3);
218+
region.flush(true);
219+
220+
region = generatedRegions.get(3);
221+
generateTestData(region, 1);
222+
region.flush(true);
223+
224+
region = generatedRegions.get(4);
225+
generateTestData(region, 5);
226+
region.flush(true);
227+
228+
final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(tableName))
229+
.setNormalizationEnabled(true)
230+
.build();
231+
admin.modifyTable(td);
232+
admin.flush(tableName);
233+
234+
assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName));
235+
236+
Thread.sleep(5000); // to let region load to update
237+
238+
// Compute the plan, no merge plan returned as they are not adjacent.
239+
final List<NormalizationPlan> plans = m.getRegionNormalizer().computePlanForTable(tableName);
240+
assertNull(plans);
241+
} finally {
242+
if (admin.tableExists(tableName)) {
243+
admin.disableTable(tableName);
244+
admin.deleteTable(tableName);
245+
}
246+
}
247+
}
248+
182249
@Test
183-
@SuppressWarnings("deprecation")
184250
public void testRegionNormalizationMergeOnCluster() throws Exception {
185251
final TableName tableName = TableName.valueOf(name.getMethodName());
186252
MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
@@ -190,7 +256,7 @@ public void testRegionNormalizationMergeOnCluster() throws Exception {
190256
try (Table ht = TEST_UTIL.createMultiRegionTable(tableName, FAMILYNAME, 5)) {
191257
// Need to get sorted list of regions here
192258
List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
193-
Collections.sort(generatedRegions, Comparator.comparing(HRegion::getRegionInfo, RegionInfo.COMPARATOR));
259+
generatedRegions.sort(Comparator.comparing(HRegion::getRegionInfo, RegionInfo.COMPARATOR));
194260

195261
HRegion region = generatedRegions.get(0);
196262
generateTestData(region, 1);
@@ -213,9 +279,11 @@ public void testRegionNormalizationMergeOnCluster() throws Exception {
213279
region.flush(true);
214280
}
215281

216-
HTableDescriptor htd = new HTableDescriptor(admin.getDescriptor(tableName));
217-
htd.setNormalizationEnabled(true);
218-
admin.modifyTable(htd);
282+
final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(tableName))
283+
.setNormalizationEnabled(true)
284+
.build();
285+
286+
admin.modifyTable(td);
219287

220288
admin.flush(tableName);
221289

0 commit comments

Comments
 (0)