diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
index 5419c169830..7eeca67bf5c 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
@@ -906,7 +906,10 @@ public interface Admin extends Abortable, Closeable {
* @param forcible true
if do a compulsory merge, otherwise we will only merge two
* adjacent regions
* @throws IOException if a remote or network exception occurs
+ * @deprecated since 2.3.0 and will be removed in 4.0.0. Multi-region merge feature is now
+ * supported. Use {@link #mergeRegionsAsync(byte[][], boolean)} instead.
*/
+ @Deprecated
default Future mergeRegionsAsync(byte[] nameOfRegionA, byte[] nameOfRegionB,
boolean forcible) throws IOException {
byte[][] nameofRegionsToMerge = new byte[2][];
@@ -916,11 +919,7 @@ public interface Admin extends Abortable, Closeable {
}
/**
- * Merge regions. Asynchronous operation.
- *
- * You may get a {@code DoNotRetryIOException} if you pass more than two regions in but the master
- * does not support merging more than two regions. At least till 2.2.0, we still only support
- * merging two regions.
+ * Merge multiple regions (>=2). Asynchronous operation.
* @param nameofRegionsToMerge encoded or full name of daughter regions
* @param forcible true
if do a compulsory merge, otherwise we will only merge
* adjacent regions
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index a53646d467c..8ff449499c0 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -513,18 +513,17 @@ public interface AsyncAdmin {
* @param nameOfRegionB encoded or full name of region b
* @param forcible true if do a compulsory merge, otherwise we will only merge two adjacent
* regions
+ * @deprecated since 2.3.0 and will be removed in 4.0.0.Use {@link #mergeRegions(List, boolean)}
+ * instead.
*/
+ @Deprecated
default CompletableFuture mergeRegions(byte[] nameOfRegionA, byte[] nameOfRegionB,
boolean forcible) {
return mergeRegions(Arrays.asList(nameOfRegionA, nameOfRegionB), forcible);
}
/**
- * Merge regions.
- *
- * You may get a {@code DoNotRetryIOException} if you pass more than two regions in but the master
- * does not support merging more than two regions. At least till 2.2.0, we still only support
- * merging two regions.
+ * Merge multiple regions (>=2).
* @param nameOfRegionsToMerge encoded or full name of daughter regions
* @param forcible true if do a compulsory merge, otherwise we will only merge two adjacent
* regions
diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb
index 5f4f16d9a30..b9b1957db28 100644
--- a/hbase-shell/src/main/ruby/hbase/admin.rb
+++ b/hbase-shell/src/main/ruby/hbase/admin.rb
@@ -535,14 +535,29 @@ module Hbase
end
#----------------------------------------------------------------------------------------------
- # Merge two regions
- def merge_region(region_a_name, region_b_name, force)
- @admin.mergeRegionsAsync(
- region_a_name.to_java_bytes,
- region_b_name.to_java_bytes,
- java.lang.Boolean.valueOf(force)
+ # Merge multiple regions
+ def merge_region(regions, force)
+ unless regions.is_a?(Array)
+ raise(ArgumentError, "Type of #{regions.inspect} is #{regions.class}, but expected Array")
+ end
+ region_array = Java::byte[][regions.length].new
+ i = 0
+ while i < regions.length
+ unless regions[i].is_a?(String)
+ raise(
+ ArgumentError,
+ "Type of #{regions[i].inspect} is #{regions[i].class}, but expected String"
+ )
+ end
+ region_array[i] = regions[i].to_java_bytes
+ i += 1
+ end
+ org.apache.hadoop.hbase.util.FutureUtils.get(
+ @admin.mergeRegionsAsync(
+ region_array,
+ java.lang.Boolean.valueOf(force)
+ )
)
- return nil
end
#----------------------------------------------------------------------------------------------
diff --git a/hbase-shell/src/main/ruby/shell/commands.rb b/hbase-shell/src/main/ruby/shell/commands.rb
index 4fdc8b544ab..a40f737e790 100644
--- a/hbase-shell/src/main/ruby/shell/commands.rb
+++ b/hbase-shell/src/main/ruby/shell/commands.rb
@@ -137,7 +137,11 @@ module Shell
raise "Table #{cause.message} should be disabled!"
end
if cause.is_a?(org.apache.hadoop.hbase.UnknownRegionException)
- raise "Unknown region #{args.first}!"
+ raise cause.message
+ end
+ if cause.is_a?(org.apache.hadoop.hbase.exceptions.MergeRegionException)
+ strs = cause.message.split("\n")
+ raise(strs[0]).to_s unless strs.empty?
end
if cause.is_a?(org.apache.hadoop.hbase.NamespaceNotFoundException)
s = /.*NamespaceNotFoundException: (?[^\n]+).*/.match(cause.message)
diff --git a/hbase-shell/src/main/ruby/shell/commands/merge_region.rb b/hbase-shell/src/main/ruby/shell/commands/merge_region.rb
index b4f6cae8205..ed172362771 100644
--- a/hbase-shell/src/main/ruby/shell/commands/merge_region.rb
+++ b/hbase-shell/src/main/ruby/shell/commands/merge_region.rb
@@ -22,7 +22,7 @@ module Shell
class MergeRegion < Command
def help
<<-EOF
-Merge two regions. Passing 'true' as the optional third parameter will force
+Merge multiple (2 or more) regions. Passing 'true' as the optional third parameter will force
a merge ('force' merges regardless else merge will fail unless passed
adjacent regions. 'force' is for expert use only).
@@ -31,18 +31,42 @@ region name is the hash suffix on region names: e.g. if the region name were
TestTable,0094429456,1289497600452.527db22f95c8a9e0116f0cc13c680396. then
the encoded region name portion is 527db22f95c8a9e0116f0cc13c680396
+You can either pass the list of regions as comma separated values or as an
+array of regions as shown:
+
Examples:
hbase> merge_region 'FULL_REGIONNAME', 'FULL_REGIONNAME'
- hbase> merge_region 'FULL_REGIONNAME', 'FULL_REGIONNAME', true
+ hbase> merge_region 'FULL_REGIONNAME', 'FULL_REGIONNAME', 'FULL_REGIONNAME', ...
+ hbase> merge_region 'FULL_REGIONNAME', 'FULL_REGIONNAME', 'FULL_REGIONNAME', ..., true
+
+ hbase> merge_region ['FULL_REGIONNAME', 'FULL_REGIONNAME']
+ hbase> merge_region ['FULL_REGIONNAME', 'FULL_REGIONNAME', 'FULL_REGIONNAME', ...]
+ hbase> merge_region ['FULL_REGIONNAME', 'FULL_REGIONNAME', 'FULL_REGIONNAME', ...], true
hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME'
- hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', true
+ hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', ...
+ hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', ..., true
+
+ hbase> merge_region ['ENCODED_REGIONNAME', 'ENCODED_REGIONNAME']
+ hbase> merge_region ['ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', ...]
+ hbase> merge_region ['ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', ...], true
EOF
end
- def command(region_a_name, region_b_name, force = 'false')
- admin.merge_region(region_a_name, region_b_name, force)
+ def command(*args)
+ args = args.flatten.compact
+ args_len = args.length
+ raise(ArgumentError, 'Must pass at least 2 regions to merge') unless args_len > 1
+ force = false
+ if(args_len > 2)
+ last = args[args_len-1]
+ if [true, false].include? last
+ force = last
+ args = args[0...-1]
+ end
+ end
+ admin.merge_region(args, force)
end
end
end
diff --git a/hbase-shell/src/test/ruby/hbase/admin_test.rb b/hbase-shell/src/test/ruby/hbase/admin_test.rb
index adc6124825c..050cd20c6be 100644
--- a/hbase-shell/src/test/ruby/hbase/admin_test.rb
+++ b/hbase-shell/src/test/ruby/hbase/admin_test.rb
@@ -595,15 +595,59 @@ module Hbase
command(:list_regions, @test_name)
end
- define_test 'merge two regions' do
+ define_test 'merge regions' do
@t_name = 'hbase_shell_merge'
+ @t_name2 = 'hbase_shell_merge_2'
drop_test_table(@t_name)
+ drop_test_table(@t_name2)
admin.create(@t_name, 'a', NUMREGIONS => 10, SPLITALGO => 'HexStringSplit')
- r1 = command(:locate_region, @t_name, '')
- r2 = command(:locate_region, @t_name, '1')
+ r1 = command(:locate_region, @t_name, '1')
+ r2 = command(:locate_region, @t_name, '2')
+ r3 = command(:locate_region, @t_name, '4')
+ r4 = command(:locate_region, @t_name, '5')
+ r5 = command(:locate_region, @t_name, '7')
+ r6 = command(:locate_region, @t_name, '8')
region1 = r1.getRegion.getRegionNameAsString
region2 = r2.getRegion.getRegionNameAsString
- command(:merge_region, region1, region2, true)
+ region3 = r3.getRegion.getRegionNameAsString
+ region4 = r4.getRegion.getRegionNameAsString
+ region5 = r5.getRegion.getRegionNameAsString
+ region6 = r6.getRegion.getRegionNameAsString
+ # only 1 region
+ assert_raise(ArgumentError) do
+ command(:merge_region, 'a')
+ end
+ # only 1 region with force=true
+ assert_raise(ArgumentError) do
+ command(:merge_region, 'a', true)
+ end
+ # non-existing region
+ assert_raise(RuntimeError) do
+ command(:merge_region, 'a','b')
+ end
+ # duplicate regions
+ assert_raise(RuntimeError) do
+ command(:merge_region, region1,region1,region1)
+ end
+ # 3 non-adjacent regions without forcible=true
+ assert_raise(RuntimeError) do
+ command(:merge_region, region1,region2,region4)
+ end
+ # 2 adjacent regions
+ command(:merge_region, region1,region2)
+ # 3 non-adjacent regions with forcible=true
+ command(:merge_region, region3,region5,region6, true)
+
+ admin.create(@t_name2, 'a', NUMREGIONS => 5, SPLITALGO => 'HexStringSplit')
+ r1 = command(:locate_region, @t_name2, '1')
+ r2 = command(:locate_region, @t_name2, '4')
+ r3 = command(:locate_region, @t_name2, '7')
+ region1 = r1.getRegion.getRegionNameAsString
+ region2 = r2.getRegion.getRegionNameAsString
+ region3 = r3.getRegion.getRegionNameAsString
+
+ # accept array of regions
+ command(:merge_region, [region1,region2,region3])
end
end