Repository: hbase Updated Branches: refs/heads/branch-1 eb9b234ce -> 84675ef61
HBASE-13103 [ergonomics] add region size balancing as a feature of master Project: http://git-wip-us.apache.org/repos/asf/hbase/repo Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/84675ef6 Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/84675ef6 Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/84675ef6 Branch: refs/heads/branch-1 Commit: 84675ef6159692b0a8da219df5abcf111fe46845 Parents: eb9b234 Author: Mikhail Antonov <anto...@apache.org> Authored: Mon Jun 22 22:47:54 2015 -0700 Committer: Mikhail Antonov <anto...@apache.org> Committed: Mon Jun 22 22:48:19 2015 -0700 ---------------------------------------------------------------------- .../apache/hadoop/hbase/HTableDescriptor.java | 39 ++- .../org/apache/hadoop/hbase/HConstants.java | 8 + .../src/main/resources/hbase-default.xml | 20 ++ .../org/apache/hadoop/hbase/master/HMaster.java | 65 ++++- .../normalizer/EmptyNormalizationPlan.java | 48 ++++ .../normalizer/MergeNormalizationPlan.java | 73 ++++++ .../master/normalizer/NormalizationPlan.java | 35 +++ .../master/normalizer/RegionNormalizer.java | 51 ++++ .../normalizer/RegionNormalizerChore.java | 54 +++++ .../normalizer/RegionNormalizerFactory.java | 48 ++++ .../normalizer/SimpleRegionNormalizer.java | 176 ++++++++++++++ .../normalizer/SplitNormalizationPlan.java | 81 +++++++ .../normalizer/TestSimpleRegionNormalizer.java | 239 +++++++++++++++++++ .../TestSimpleRegionNormalizerOnCluster.java | 217 +++++++++++++++++ 14 files changed, 1147 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java ---------------------------------------------------------------------- diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java index 7f61bf2..a6d443c 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java @@ -201,6 +201,16 @@ public class HTableDescriptor implements WritableComparable<HTableDescriptor> { private static final ImmutableBytesWritable REGION_MEMSTORE_REPLICATION_KEY = new ImmutableBytesWritable(Bytes.toBytes(REGION_MEMSTORE_REPLICATION)); + /** + * <em>INTERNAL</em> Used by shell/rest interface to access this metadata + * attribute which denotes if the table should be treated by region normalizer. + * + * @see #isNormalizationEnabled() + */ + public static final String NORMALIZATION_ENABLED = "NORMALIZATION_ENABLED"; + private static final ImmutableBytesWritable NORMALIZATION_ENABLED_KEY = + new ImmutableBytesWritable(Bytes.toBytes(NORMALIZATION_ENABLED)); + /** Default durability for HTD is USE_DEFAULT, which defaults to HBase-global default value */ private static final Durability DEFAULT_DURABLITY = Durability.USE_DEFAULT; @@ -228,6 +238,11 @@ public class HTableDescriptor implements WritableComparable<HTableDescriptor> { public static final boolean DEFAULT_COMPACTION_ENABLED = true; /** + * Constant that denotes whether the table is normalized by default. + */ + public static final boolean DEFAULT_NORMALIZATION_ENABLED = false; + + /** * Constant that denotes the maximum default size of the memstore after which * the contents are flushed to the store files */ @@ -381,7 +396,7 @@ public class HTableDescriptor implements WritableComparable<HTableDescriptor> { */ private void setMetaFlags(final TableName name) { setMetaRegion(isRootRegion() || - name.equals(TableName.META_TABLE_NAME)); + name.equals(TableName.META_TABLE_NAME)); } /** @@ -405,7 +420,7 @@ public class HTableDescriptor implements WritableComparable<HTableDescriptor> { */ protected void setRootRegion(boolean isRoot) { // TODO: Make the value a boolean rather than String of boolean. - setValue(IS_ROOT_KEY, isRoot? TRUE: FALSE); + setValue(IS_ROOT_KEY, isRoot ? TRUE : FALSE); } /** @@ -630,6 +645,26 @@ public class HTableDescriptor implements WritableComparable<HTableDescriptor> { } /** + * Check if normalization enable flag of the table is true. If flag is + * false then no region normalizer won't attempt to normalize this table. + * + * @return true if region normalization is enabled for this table + */ + public boolean isNormalizationEnabled() { + return isSomething(NORMALIZATION_ENABLED_KEY, DEFAULT_NORMALIZATION_ENABLED); + } + + /** + * Setting the table normalization enable flag. + * + * @param isEnable True if enable normalization. + */ + public HTableDescriptor setNormalizationEnabled(final boolean isEnable) { + setValue(NORMALIZATION_ENABLED_KEY, isEnable ? TRUE : FALSE); + return this; + } + + /** * Sets the {@link Durability} setting for the table. This defaults to Durability.USE_DEFAULT. * @param durability enum value */ http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java ---------------------------------------------------------------------- diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java index 99cfcdc..e349912 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java @@ -124,6 +124,14 @@ public final class HConstants { /** Config for pluggable load balancers */ public static final String HBASE_MASTER_LOADBALANCER_CLASS = "hbase.master.loadbalancer.class"; + /** Config for pluggable region normalizer */ + public static final String HBASE_MASTER_NORMALIZER_CLASS = + "hbase.master.normalizer.class"; + + /** Config for enabling/disabling pluggable region normalizer */ + public static final String HBASE_NORMALIZER_ENABLED = + "hbase.normalizer.enabled"; + /** Cluster is standalone or pseudo-distributed */ public static final boolean CLUSTER_IS_LOCAL = false; http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-common/src/main/resources/hbase-default.xml ---------------------------------------------------------------------- diff --git a/hbase-common/src/main/resources/hbase-default.xml b/hbase-common/src/main/resources/hbase-default.xml index f708dbc..7cec2ad 100644 --- a/hbase-common/src/main/resources/hbase-default.xml +++ b/hbase-common/src/main/resources/hbase-default.xml @@ -591,6 +591,17 @@ possible configurations would overwhelm and obscure the important. <description>Period at which the region balancer runs in the Master.</description> </property> <property> + <name>hbase.normalizer.enabled</name> + <value>false</value> + <description>If set to true, Master will try to keep region size + within each table approximately the same.</description> + </property> + <property> + <name>hbase.normalizer.period</name> + <value>1800000</value> + <description>Period at which the region normalizer runs in the Master.</description> + </property> + <property> <name>hbase.regions.slop</name> <value>0.2</value> <description>Rebalance if any regionserver has average + (average * slop) regions.</description> @@ -1434,6 +1445,15 @@ possible configurations would overwhelm and obscure the important. <value>dr.stack</value> </property> <property> + <name>hbase.master.normalizer.class</name> + <value>org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer</value> + <description> + Class used to execute the region normalization when the period occurs. + See the class comment for more on how it works + http://hbase.apache.org/devapidocs/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.html + </description> + </property> + <property> <name>hbase.regionserver.handler.abort.on.error.percent</name> <value>0.5</value> <description>The percent of region server RPC threads failed to abort RS. http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java index 27b70b6..b282676 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java @@ -104,6 +104,9 @@ import org.apache.hadoop.hbase.master.procedure.ModifyTableProcedure; import org.apache.hadoop.hbase.master.procedure.ProcedurePrepareLatch; import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait; import org.apache.hadoop.hbase.master.procedure.TruncateTableProcedure; +import org.apache.hadoop.hbase.master.normalizer.RegionNormalizer; +import org.apache.hadoop.hbase.master.normalizer.RegionNormalizerChore; +import org.apache.hadoop.hbase.master.normalizer.RegionNormalizerFactory; import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; import org.apache.hadoop.hbase.monitoring.MemoryBoundedLogMessageBuffer; import org.apache.hadoop.hbase.monitoring.MonitoredTask; @@ -272,7 +275,10 @@ public class HMaster extends HRegionServer implements MasterServices, Server { private volatile boolean serverCrashProcessingEnabled = false; LoadBalancer balancer; + RegionNormalizer normalizer; + private boolean normalizerEnabled = false; private BalancerChore balancerChore; + private RegionNormalizerChore normalizerChore; private ClusterStatusChore clusterStatusChore; private ClusterStatusPublisher clusterStatusPublisherChore = null; @@ -543,6 +549,9 @@ public class HMaster extends HRegionServer implements MasterServices, Server { void initializeZKBasedSystemTrackers() throws IOException, InterruptedException, KeeperException, CoordinatedStateException { this.balancer = LoadBalancerFactory.getLoadBalancer(conf); + this.normalizer = RegionNormalizerFactory.getRegionNormalizer(conf); + this.normalizer.setMasterServices(this); + this.normalizerEnabled = conf.getBoolean(HConstants.HBASE_NORMALIZER_ENABLED, false); this.loadBalancerTracker = new LoadBalancerTracker(zooKeeper, this); this.loadBalancerTracker.start(); this.assignmentManager = new AssignmentManager(this, serverManager, @@ -735,6 +744,8 @@ public class HMaster extends HRegionServer implements MasterServices, Server { getChoreService().scheduleChore(clusterStatusChore); this.balancerChore = new BalancerChore(this); getChoreService().scheduleChore(balancerChore); + this.normalizerChore = new RegionNormalizerChore(this); + getChoreService().scheduleChore(normalizerChore); this.catalogJanitorChore = new CatalogJanitor(this, this); getChoreService().scheduleChore(catalogJanitorChore); @@ -1148,6 +1159,9 @@ public class HMaster extends HRegionServer implements MasterServices, Server { if (this.balancerChore != null) { this.balancerChore.cancel(true); } + if (this.normalizerChore != null) { + this.normalizerChore.cancel(true); + } if (this.clusterStatusChore != null) { this.clusterStatusChore.cancel(true); } @@ -1281,6 +1295,47 @@ public class HMaster extends HRegionServer implements MasterServices, Server { } /** + * Perform normalization of cluster (invoked by {@link RegionNormalizerChore}). + * + * @return true if normalization step was performed successfully, false otherwise + * (specifically, if HMaster hasn't been initialized properly or normalization + * is globally disabled) + * @throws IOException, CoordinatedStateException + */ + public boolean normalizeRegions() throws IOException, CoordinatedStateException { + if (!this.initialized) { + LOG.debug("Master has not been initialized, don't run region normalizer."); + return false; + } + + if (!this.normalizerEnabled) { + LOG.debug("Region normalization is disabled, don't run region normalizer."); + return false; + } + + synchronized (this.normalizer) { + // Don't run the normalizer concurrently + List<TableName> allEnabledTables = new ArrayList<>( + this.assignmentManager.getTableStateManager().getTablesInStates( + ZooKeeperProtos.Table.State.ENABLED)); + + Collections.shuffle(allEnabledTables); + + for(TableName table : allEnabledTables) { + if (table.isSystemTable() || !getTableDescriptors().get(table).isNormalizationEnabled()) { + LOG.debug("Skipping normalization for table: " + table + ", as it's either system" + + " table or doesn't have auto normalization turned on"); + continue; + } + this.normalizer.computePlanForTable(table).execute(clusterConnection.getAdmin()); + } + } + // If Region did not generate any plans, it means the cluster is already balanced. + // Return true indicating a success. + return true; + } + + /** * @return Client info for use as prefix on an audit log string; who did an action */ String getClientIdAuditPrefix() { @@ -1302,7 +1357,7 @@ public class HMaster extends HRegionServer implements MasterServices, Server { final HRegionInfo region_b, final boolean forcible) throws IOException { checkInitialized(); this.service.submit(new DispatchMergingRegionHandler(this, - this.catalogJanitorChore, region_a, region_b, forcible)); + this.catalogJanitorChore, region_a, region_b, forcible)); } void move(final byte[] encodedRegionName, @@ -1556,7 +1611,7 @@ public class HMaster extends HRegionServer implements MasterServices, Server { HConstants.DEFAULT_ZK_SESSION_TIMEOUT); // If we're a backup master, stall until a primary to writes his address if (conf.getBoolean(HConstants.MASTER_TYPE_BACKUP, - HConstants.DEFAULT_MASTER_TYPE_BACKUP)) { + HConstants.DEFAULT_MASTER_TYPE_BACKUP)) { LOG.debug("HMaster started in backup mode. " + "Stalling until master znode is written."); // This will only be a minute or so while the cluster starts up, @@ -1578,12 +1633,12 @@ public class HMaster extends HRegionServer implements MasterServices, Server { LOG.fatal("Failed to become active master", t); // HBASE-5680: Likely hadoop23 vs hadoop 20.x/1.x incompatibility if (t instanceof NoClassDefFoundError && - t.getMessage() - .contains("org/apache/hadoop/hdfs/protocol/HdfsConstants$SafeModeAction")) { + t.getMessage() + .contains("org/apache/hadoop/hdfs/protocol/HdfsConstants$SafeModeAction")) { // improved error message for this special case abort("HBase is having a problem with its Hadoop jars. You may need to " + "recompile HBase against Hadoop version " - + org.apache.hadoop.util.VersionInfo.getVersion() + + org.apache.hadoop.util.VersionInfo.getVersion() + " or change your hadoop jars to start properly", t); } else { abort("Unhandled exception. Starting shutdown.", t); http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java new file mode 100644 index 0000000..a36dd07 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java @@ -0,0 +1,48 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.client.Admin; + +/** + * Plan which signifies that no normalization is required, + * or normalization of this table isn't allowed, this is singleton. + */ +@InterfaceAudience.Private +public final class EmptyNormalizationPlan implements NormalizationPlan { + private static final EmptyNormalizationPlan instance = new EmptyNormalizationPlan(); + + private EmptyNormalizationPlan() { + } + + /** + * @return singleton instance + */ + public static EmptyNormalizationPlan getInstance(){ + return instance; + } + + /** + * No-op for empty plan. + */ + @Override + public void execute(Admin admin) { + } +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java new file mode 100644 index 0000000..08a58a5 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java @@ -0,0 +1,73 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.client.Admin; + +import java.io.IOException; + +/** + * Normalization plan to merge regions (smallest region in the table with its smallest neighbor). + */ +@InterfaceAudience.Private +public class MergeNormalizationPlan implements NormalizationPlan { + private static final Log LOG = LogFactory.getLog(MergeNormalizationPlan.class.getName()); + + private final HRegionInfo firstRegion; + private final HRegionInfo secondRegion; + + public MergeNormalizationPlan(HRegionInfo firstRegion, HRegionInfo secondRegion) { + this.firstRegion = firstRegion; + this.secondRegion = secondRegion; + } + + HRegionInfo getFirstRegion() { + return firstRegion; + } + + HRegionInfo getSecondRegion() { + return secondRegion; + } + + @Override + public String toString() { + return "MergeNormalizationPlan{" + + "firstRegion=" + firstRegion + + ", secondRegion=" + secondRegion + + '}'; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute(Admin admin) { + LOG.info("Executing merging normalization plan: " + this); + try { + admin.mergeRegions(firstRegion.getEncodedNameAsBytes(), + secondRegion.getEncodedNameAsBytes(), true); + } catch (IOException ex) { + LOG.error("Error during region merge: ", ex); + } + } +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java new file mode 100644 index 0000000..96eed8c --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java @@ -0,0 +1,35 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.client.Admin; + +/** + * Interface for normalization plan. + */ +@InterfaceAudience.Private +public interface NormalizationPlan { + + /** + * Executes normalization plan on cluster (does actual splitting/merging work). + * @param admin instance of Admin + */ + void execute(Admin admin); +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java new file mode 100644 index 0000000..19abcf2 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java @@ -0,0 +1,51 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.hadoop.hbase.HBaseIOException; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.master.MasterServices; + +/** + * Performs "normalization" of regions on the cluster, making sure that suboptimal + * choice of split keys doesn't leave cluster in a situation when some regions are + * substantially larger than others for considerable amount of time. + * + * Users who want to use this feature could either use default {@link SimpleRegionNormalizer} + * or plug in their own implementation. Please note that overly aggressive normalization rules + * (attempting to make all regions perfectly equal in size) could potentially lead to + * "split/merge storms". + */ +@InterfaceAudience.Private +public interface RegionNormalizer { + /** + * Set the master service. Must be called before first call to + * {@link #computePlanForTable(TableName)}. + * @param masterServices master services to use + */ + void setMasterServices(MasterServices masterServices); + + /** + * Computes next optimal normalization plan. + * @param table table to normalize + * @return Next (perhaps most urgent) normalization action to perform + */ + NormalizationPlan computePlanForTable(TableName table) throws HBaseIOException; +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerChore.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerChore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerChore.java new file mode 100644 index 0000000..1506892 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerChore.java @@ -0,0 +1,54 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.CoordinatedStateException; +import org.apache.hadoop.hbase.ScheduledChore; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.master.HMaster; + +import java.io.IOException; + +/** + * Chore that will call {@link org.apache.hadoop.hbase.master.HMaster#normalizeRegions()} + * when needed. + */ +@InterfaceAudience.Private +public class RegionNormalizerChore extends ScheduledChore { + private static final Log LOG = LogFactory.getLog(RegionNormalizerChore.class); + + private final HMaster master; + + public RegionNormalizerChore(HMaster master) { + super(master.getServerName() + "-RegionNormalizerChore", master, + master.getConfiguration().getInt("hbase.normalizer.period", 1800000)); + this.master = master; + } + + @Override + protected void chore() { + try { + master.normalizeRegions(); + } catch (IOException | CoordinatedStateException e) { + LOG.error("Failed to normalize regions.", e); + } + } +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerFactory.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerFactory.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerFactory.java new file mode 100644 index 0000000..6df0c77 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizerFactory.java @@ -0,0 +1,48 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.util.ReflectionUtils; + +/** + * Factory to create instance of {@link RegionNormalizer} as configured. + */ +@InterfaceAudience.Private +public final class RegionNormalizerFactory { + + private RegionNormalizerFactory() { + } + + /** + * Create a region normalizer from the given conf. + * @param conf configuration + * @return {@link RegionNormalizer} implementation + */ + public static RegionNormalizer getRegionNormalizer(Configuration conf) { + + // Create instance of Region Normalizer + Class<? extends RegionNormalizer> balancerKlass = + conf.getClass(HConstants.HBASE_MASTER_NORMALIZER_CLASS, SimpleRegionNormalizer.class, + RegionNormalizer.class); + return ReflectionUtils.newInstance(balancerKlass, conf); + } +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java new file mode 100644 index 0000000..bf58691 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java @@ -0,0 +1,176 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.HBaseIOException; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.RegionLoad; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.master.MasterServices; +import org.apache.hadoop.hbase.util.Pair; + +import java.util.List; + +/** + * Simple implementation of region normalizer. + * + * Logic in use: + * + * - get all regions of a given table + * - get avg size S of each region (by total size of store files reported in RegionLoad) + * - If biggest region is bigger than S * 2, it is kindly requested to split, + * and normalization stops + * - Otherwise, two smallest region R1 and its smallest neighbor R2 are kindly requested + * to merge, if R1 + R1 < S, and normalization stops + * - Otherwise, no action is performed + */ +@InterfaceAudience.Private +public class SimpleRegionNormalizer implements RegionNormalizer { + private static final Log LOG = LogFactory.getLog(SimpleRegionNormalizer.class); + private MasterServices masterServices; + + /** + * Set the master service. + * @param masterServices inject instance of MasterServices + */ + @Override + public void setMasterServices(MasterServices masterServices) { + this.masterServices = masterServices; + } + + /** + * Computes next most "urgent" normalization action on the table. + * Action may be either a split, or a merge, or no action. + * + * @param table table to normalize + * @return normalization plan to execute + */ + @Override + public NormalizationPlan computePlanForTable(TableName table) + throws HBaseIOException { + if (table == null || table.isSystemTable()) { + LOG.debug("Normalization of table " + table + " isn't allowed"); + return EmptyNormalizationPlan.getInstance(); + } + + List<HRegionInfo> tableRegions = masterServices.getAssignmentManager().getRegionStates(). + getRegionsOfTable(table); + + //TODO: should we make min number of regions a config param? + if (tableRegions == null || tableRegions.size() < 3) { + LOG.debug("Table " + table + " has " + tableRegions.size() + " regions, required min number" + + " of regions for normalizer to run is 3, not running normalizer"); + return EmptyNormalizationPlan.getInstance(); + } + + LOG.debug("Computing normalization plan for table: " + table + + ", number of regions: " + tableRegions.size()); + + long totalSizeMb = 0; + Pair<HRegionInfo, Long> largestRegion = new Pair<>(); + + // A is a smallest region, B is it's smallest neighbor + Pair<HRegionInfo, Long> smallestRegion = new Pair<>(); + Pair<HRegionInfo, Long> smallestNeighborOfSmallestRegion; + int smallestRegionIndex = 0; + + for (int i = 0; i < tableRegions.size(); i++) { + HRegionInfo hri = tableRegions.get(i); + long regionSize = getRegionSize(hri); + totalSizeMb += regionSize; + + if (largestRegion.getFirst() == null || regionSize > largestRegion.getSecond()) { + largestRegion.setFirst(hri); + largestRegion.setSecond(regionSize); + } + + if (smallestRegion.getFirst() == null || regionSize < smallestRegion.getSecond()) { + smallestRegion.setFirst(hri); + smallestRegion.setSecond(regionSize); + smallestRegionIndex = i; + } + } + + // now get smallest neighbor of smallest region + long leftNeighborSize = -1; + long rightNeighborSize = -1; + + if (smallestRegionIndex > 0) { + leftNeighborSize = getRegionSize(tableRegions.get(smallestRegionIndex - 1)); + } + + if (smallestRegionIndex < tableRegions.size() - 1) { + rightNeighborSize = getRegionSize(tableRegions.get(smallestRegionIndex + 1)); + } + + if (leftNeighborSize == -1) { + smallestNeighborOfSmallestRegion = + new Pair<>(tableRegions.get(smallestRegionIndex + 1), rightNeighborSize); + } else if (rightNeighborSize == -1) { + smallestNeighborOfSmallestRegion = + new Pair<>(tableRegions.get(smallestRegionIndex - 1), leftNeighborSize); + } else { + if (leftNeighborSize < rightNeighborSize) { + smallestNeighborOfSmallestRegion = + new Pair<>(tableRegions.get(smallestRegionIndex - 1), leftNeighborSize); + } else { + smallestNeighborOfSmallestRegion = + new Pair<>(tableRegions.get(smallestRegionIndex + 1), rightNeighborSize); + } + } + + double avgRegionSize = totalSizeMb / (double) tableRegions.size(); + + LOG.debug("Table " + table + ", total aggregated regions size: " + totalSizeMb); + LOG.debug("Table " + table + ", average region size: " + avgRegionSize); + + // now; if the largest region is >2 times large than average, we split it, split + // is more high priority normalization action than merge. + if (largestRegion.getSecond() > 2 * avgRegionSize) { + LOG.debug("Table " + table + ", largest region " + + largestRegion.getFirst().getRegionName() + " has size " + + largestRegion.getSecond() + ", more than 2 times than avg size, splitting"); + return new SplitNormalizationPlan(largestRegion.getFirst(), null); + } else { + if ((smallestRegion.getSecond() + smallestNeighborOfSmallestRegion.getSecond() + < avgRegionSize)) { + LOG.debug("Table " + table + ", smallest region size: " + smallestRegion.getSecond() + + " and its smallest neighbor size: " + smallestNeighborOfSmallestRegion.getSecond() + + ", less than half the avg size, merging them"); + return new MergeNormalizationPlan(smallestRegion.getFirst(), + smallestNeighborOfSmallestRegion.getFirst()); + } else { + LOG.debug("No normalization needed, regions look good for table: " + table); + return EmptyNormalizationPlan.getInstance(); + } + } + } + + private long getRegionSize(HRegionInfo hri) { + ServerName sn = masterServices.getAssignmentManager().getRegionStates(). + getRegionServerOfRegion(hri); + RegionLoad regionLoad = masterServices.getServerManager().getLoad(sn). + getRegionsLoad().get(hri.getRegionName()); + return regionLoad.getStorefileSizeMB(); + } +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java new file mode 100644 index 0000000..c96988a --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java @@ -0,0 +1,81 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.client.Admin; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Normalization plan to split region. + */ +@InterfaceAudience.Private +public class SplitNormalizationPlan implements NormalizationPlan { + private static final Log LOG = LogFactory.getLog(SplitNormalizationPlan.class.getName()); + + private HRegionInfo regionInfo; + private byte[] splitPoint; + + public SplitNormalizationPlan(HRegionInfo regionInfo, byte[] splitPoint) { + this.regionInfo = regionInfo; + this.splitPoint = splitPoint; + } + + public HRegionInfo getRegionInfo() { + return regionInfo; + } + + public void setRegionInfo(HRegionInfo regionInfo) { + this.regionInfo = regionInfo; + } + + public byte[] getSplitPoint() { + return splitPoint; + } + + public void setSplitPoint(byte[] splitPoint) { + this.splitPoint = splitPoint; + } + + @Override + public String toString() { + return "SplitNormalizationPlan{" + + "regionInfo=" + regionInfo + + ", splitPoint=" + Arrays.toString(splitPoint) + + '}'; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute(Admin admin) { + LOG.info("Executing splitting normalization plan: " + this); + try { + admin.splitRegion(regionInfo.getRegionName()); + } catch (IOException ex) { + LOG.error("Error during region split: ", ex); + } + } +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizer.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizer.java new file mode 100644 index 0000000..a87fa29 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizer.java @@ -0,0 +1,239 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.HBaseIOException; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.RegionLoad; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.master.MasterServices; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.when; + +/** + * Tests logic of {@link SimpleRegionNormalizer}. + */ +@Category(SmallTests.class) +public class TestSimpleRegionNormalizer { + private static final Log LOG = LogFactory.getLog(TestSimpleRegionNormalizer.class); + + private static RegionNormalizer normalizer; + + // mocks + private static MasterServices masterServices; + + @BeforeClass + public static void beforeAllTests() throws Exception { + normalizer = new SimpleRegionNormalizer(); + } + + @Test + public void testNoNormalizationForMetaTable() throws HBaseIOException { + TableName testTable = TableName.META_TABLE_NAME; + List<HRegionInfo> hris = new ArrayList<>(); + Map<byte[], Integer> regionSizes = new HashMap<>(); + + setupMocksForNormalizer(regionSizes, hris); + NormalizationPlan plan = normalizer.computePlanForTable(testTable); + assertTrue(plan instanceof EmptyNormalizationPlan); + } + + @Test + public void testNoNormalizationIfTooFewRegions() throws HBaseIOException { + TableName testTable = TableName.valueOf("testSplitOfSmallRegion"); + List<HRegionInfo> hris = new ArrayList<>(); + Map<byte[], Integer> regionSizes = new HashMap<>(); + + HRegionInfo hri1 = new HRegionInfo(testTable, Bytes.toBytes("aaa"), Bytes.toBytes("bbb")); + hris.add(hri1); + regionSizes.put(hri1.getRegionName(), 10); + + HRegionInfo hri2 = new HRegionInfo(testTable, Bytes.toBytes("bbb"), Bytes.toBytes("ccc")); + hris.add(hri2); + regionSizes.put(hri2.getRegionName(), 15); + + setupMocksForNormalizer(regionSizes, hris); + NormalizationPlan plan = normalizer.computePlanForTable(testTable); + assertTrue((plan instanceof EmptyNormalizationPlan)); + } + + @Test + public void testNoNormalizationOnNormalizedCluster() throws HBaseIOException { + TableName testTable = TableName.valueOf("testSplitOfSmallRegion"); + List<HRegionInfo> hris = new ArrayList<>(); + Map<byte[], Integer> regionSizes = new HashMap<>(); + + HRegionInfo hri1 = new HRegionInfo(testTable, Bytes.toBytes("aaa"), Bytes.toBytes("bbb")); + hris.add(hri1); + regionSizes.put(hri1.getRegionName(), 10); + + HRegionInfo hri2 = new HRegionInfo(testTable, Bytes.toBytes("bbb"), Bytes.toBytes("ccc")); + hris.add(hri2); + regionSizes.put(hri2.getRegionName(), 15); + + HRegionInfo hri3 = new HRegionInfo(testTable, Bytes.toBytes("ccc"), Bytes.toBytes("ddd")); + hris.add(hri3); + regionSizes.put(hri3.getRegionName(), 8); + + HRegionInfo hri4 = new HRegionInfo(testTable, Bytes.toBytes("ddd"), Bytes.toBytes("eee")); + hris.add(hri4); + regionSizes.put(hri4.getRegionName(), 10); + + + setupMocksForNormalizer(regionSizes, hris); + NormalizationPlan plan = normalizer.computePlanForTable(testTable); + assertTrue(plan instanceof EmptyNormalizationPlan); + } + + @Test + public void testMergeOfSmallRegions() throws HBaseIOException { + TableName testTable = TableName.valueOf("testMergeOfSmallRegions"); + List<HRegionInfo> hris = new ArrayList<>(); + Map<byte[], Integer> regionSizes = new HashMap<>(); + + HRegionInfo hri1 = new HRegionInfo(testTable, Bytes.toBytes("aaa"), Bytes.toBytes("bbb")); + hris.add(hri1); + regionSizes.put(hri1.getRegionName(), 15); + + HRegionInfo hri2 = new HRegionInfo(testTable, Bytes.toBytes("bbb"), Bytes.toBytes("ccc")); + hris.add(hri2); + regionSizes.put(hri2.getRegionName(), 5); + + HRegionInfo hri3 = new HRegionInfo(testTable, Bytes.toBytes("ccc"), Bytes.toBytes("ddd")); + hris.add(hri3); + regionSizes.put(hri3.getRegionName(), 5); + + HRegionInfo hri4 = new HRegionInfo(testTable, Bytes.toBytes("ddd"), Bytes.toBytes("eee")); + hris.add(hri4); + regionSizes.put(hri4.getRegionName(), 15); + + HRegionInfo hri5 = new HRegionInfo(testTable, Bytes.toBytes("ddd"), Bytes.toBytes("eee")); + hris.add(hri4); + regionSizes.put(hri5.getRegionName(), 16); + + setupMocksForNormalizer(regionSizes, hris); + NormalizationPlan plan = normalizer.computePlanForTable(testTable); + + assertTrue(plan instanceof MergeNormalizationPlan); + assertEquals(hri2, ((MergeNormalizationPlan) plan).getFirstRegion()); + assertEquals(hri3, ((MergeNormalizationPlan) plan).getSecondRegion()); + } + + @Test + public void testMergeOfSmallNonAdjacentRegions() throws HBaseIOException { + TableName testTable = TableName.valueOf("testMergeOfSmallRegions"); + List<HRegionInfo> hris = new ArrayList<>(); + Map<byte[], Integer> regionSizes = new HashMap<>(); + + HRegionInfo hri1 = new HRegionInfo(testTable, Bytes.toBytes("aaa"), Bytes.toBytes("bbb")); + hris.add(hri1); + regionSizes.put(hri1.getRegionName(), 15); + + HRegionInfo hri2 = new HRegionInfo(testTable, Bytes.toBytes("bbb"), Bytes.toBytes("ccc")); + hris.add(hri2); + regionSizes.put(hri2.getRegionName(), 5); + + HRegionInfo hri3 = new HRegionInfo(testTable, Bytes.toBytes("ccc"), Bytes.toBytes("ddd")); + hris.add(hri3); + regionSizes.put(hri3.getRegionName(), 16); + + HRegionInfo hri4 = new HRegionInfo(testTable, Bytes.toBytes("ddd"), Bytes.toBytes("eee")); + hris.add(hri4); + regionSizes.put(hri4.getRegionName(), 15); + + HRegionInfo hri5 = new HRegionInfo(testTable, Bytes.toBytes("ddd"), Bytes.toBytes("eee")); + hris.add(hri4); + regionSizes.put(hri5.getRegionName(), 5); + + setupMocksForNormalizer(regionSizes, hris); + NormalizationPlan plan = normalizer.computePlanForTable(testTable); + + assertTrue(plan instanceof EmptyNormalizationPlan); + } + + @Test + public void testSplitOfLargeRegion() throws HBaseIOException { + TableName testTable = TableName.valueOf("testSplitOfLargeRegion"); + List<HRegionInfo> hris = new ArrayList<>(); + Map<byte[], Integer> regionSizes = new HashMap<>(); + + HRegionInfo hri1 = new HRegionInfo(testTable, Bytes.toBytes("aaa"), Bytes.toBytes("bbb")); + hris.add(hri1); + regionSizes.put(hri1.getRegionName(), 8); + + HRegionInfo hri2 = new HRegionInfo(testTable, Bytes.toBytes("bbb"), Bytes.toBytes("ccc")); + hris.add(hri2); + regionSizes.put(hri2.getRegionName(), 6); + + HRegionInfo hri3 = new HRegionInfo(testTable, Bytes.toBytes("ccc"), Bytes.toBytes("ddd")); + hris.add(hri3); + regionSizes.put(hri3.getRegionName(), 10); + + HRegionInfo hri4 = new HRegionInfo(testTable, Bytes.toBytes("ddd"), Bytes.toBytes("eee")); + hris.add(hri4); + regionSizes.put(hri4.getRegionName(), 30); + + setupMocksForNormalizer(regionSizes, hris); + NormalizationPlan plan = normalizer.computePlanForTable(testTable); + + assertTrue(plan instanceof SplitNormalizationPlan); + assertEquals(hri4, ((SplitNormalizationPlan) plan).getRegionInfo()); + } + + protected void setupMocksForNormalizer(Map<byte[], Integer> regionSizes, + List<HRegionInfo> hris) { + masterServices = Mockito.mock(MasterServices.class, RETURNS_DEEP_STUBS); + + // for simplicity all regions are assumed to be on one server; doesn't matter to us + ServerName sn = ServerName.valueOf("localhost", -1, 1L); + when(masterServices.getAssignmentManager().getRegionStates(). + getRegionsOfTable(any(TableName.class))).thenReturn(hris); + when(masterServices.getAssignmentManager().getRegionStates(). + getRegionServerOfRegion(any(HRegionInfo.class))).thenReturn(sn); + + for (Map.Entry<byte[], Integer> region : regionSizes.entrySet()) { + RegionLoad regionLoad = Mockito.mock(RegionLoad.class); + when(regionLoad.getName()).thenReturn(region.getKey()); + when(regionLoad.getStorefileSizeMB()).thenReturn(region.getValue()); + + when(masterServices.getServerManager().getLoad(sn). + getRegionsLoad().get(region.getKey())).thenReturn(regionLoad); + } + + normalizer.setMasterServices(masterServices); + } +} http://git-wip-us.apache.org/repos/asf/hbase/blob/84675ef6/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java new file mode 100644 index 0000000..07d09fe --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java @@ -0,0 +1,217 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.normalizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MetaTableAccessor; +import org.apache.hadoop.hbase.MiniHBaseCluster; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.master.HMaster; +import org.apache.hadoop.hbase.regionserver.HRegion; +import org.apache.hadoop.hbase.regionserver.Region; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.test.LoadTestKVGenerator; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Testing {@link SimpleRegionNormalizer} on minicluster. + */ +@Category(MediumTests.class) +public class TestSimpleRegionNormalizerOnCluster { + private static final Log LOG = LogFactory.getLog(TestSimpleRegionNormalizerOnCluster.class); + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static final byte[] FAMILYNAME = Bytes.toBytes("fam"); + private static Admin admin; + + @BeforeClass + public static void beforeAllTests() throws Exception { + // we will retry operations when PleaseHoldException is thrown + TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3); + TEST_UTIL.getConfiguration().setBoolean(HConstants.HBASE_NORMALIZER_ENABLED, true); + + // Start a cluster of two regionservers. + TEST_UTIL.startMiniCluster(1); + admin = TEST_UTIL.getHBaseAdmin(); + } + + @AfterClass + public static void afterAllTests() throws Exception { + TEST_UTIL.shutdownMiniCluster(); + } + + @Test(timeout = 60000) + @SuppressWarnings("deprecation") + public void testRegionNormalizationSplitOnCluster() throws Exception { + final TableName TABLENAME = + TableName.valueOf("testRegionNormalizationSplitOnCluster"); + MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); + HMaster m = cluster.getMaster(); + + try (HTable ht = TEST_UTIL.createMultiRegionTable(TABLENAME, FAMILYNAME, 5)) { + // Need to get sorted list of regions here + List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(TABLENAME); + Collections.sort(generatedRegions, new Comparator<HRegion>() { + @Override + public int compare(HRegion o1, HRegion o2) { + return o1.getRegionInfo().compareTo(o2.getRegionInfo()); + } + }); + + HRegion region = generatedRegions.get(0); + generateTestData(region, 1); + region.flush(true); + + region = generatedRegions.get(1); + generateTestData(region, 1); + region.flush(true); + + region = generatedRegions.get(2); + generateTestData(region, 2); + region.flush(true); + + region = generatedRegions.get(3); + generateTestData(region, 2); + region.flush(true); + + region = generatedRegions.get(4); + generateTestData(region, 5); + region.flush(true); + } + + HTableDescriptor htd = admin.getTableDescriptor(TABLENAME); + htd.setNormalizationEnabled(true); + admin.modifyTable(TABLENAME, htd); + + admin.flush(TABLENAME); + + System.out.println(admin.getTableDescriptor(TABLENAME)); + + assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME)); + + // Now trigger a split and stop when the split is in progress + Thread.sleep(5000); // to let region load to update + m.normalizeRegions(); + + while (MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME) < 6) { + LOG.info("Waiting for normalization split to complete"); + Thread.sleep(100); + } + + assertEquals(6, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME)); + + admin.disableTable(TABLENAME); + admin.deleteTable(TABLENAME); + } + + @Test(timeout = 60000) + @SuppressWarnings("deprecation") + public void testRegionNormalizationMergeOnCluster() throws Exception { + final TableName TABLENAME = + TableName.valueOf("testRegionNormalizationMergeOnCluster"); + MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); + HMaster m = cluster.getMaster(); + + // create 5 regions with sizes to trigger merge of small regions + try (HTable ht = TEST_UTIL.createMultiRegionTable(TABLENAME, FAMILYNAME, 5)) { + // Need to get sorted list of regions here + List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(TABLENAME); + Collections.sort(generatedRegions, new Comparator<HRegion>() { + @Override + public int compare(HRegion o1, HRegion o2) { + return o1.getRegionInfo().compareTo(o2.getRegionInfo()); + } + }); + + HRegion region = generatedRegions.get(0); + generateTestData(region, 1); + 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, 3); + region.flush(true); + + region = generatedRegions.get(4); + generateTestData(region, 5); + region.flush(true); + } + + HTableDescriptor htd = admin.getTableDescriptor(TABLENAME); + htd.setNormalizationEnabled(true); + admin.modifyTable(TABLENAME, htd); + + admin.flush(TABLENAME); + + assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME)); + + // Now trigger a merge and stop when the merge is in progress + Thread.sleep(5000); // to let region load to update + m.normalizeRegions(); + + while (MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME) > 4) { + LOG.info("Waiting for normalization merge to complete"); + Thread.sleep(100); + } + + assertEquals(4, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME)); + + admin.disableTable(TABLENAME); + admin.deleteTable(TABLENAME); + } + + private void generateTestData(Region region, int numRows) throws IOException { + // generating 1Mb values + LoadTestKVGenerator dataGenerator = new LoadTestKVGenerator(1024 * 1024, 1024 * 1024); + for (int i = 0; i < numRows; ++i) { + byte[] key = Bytes.add(region.getRegionInfo().getStartKey(), Bytes.toBytes(i)); + for (int j = 0; j < 1; ++j) { + Put put = new Put(key); + byte[] col = Bytes.toBytes(String.valueOf(j)); + byte[] value = dataGenerator.generateRandomSizeValue(key, col); + put.add(FAMILYNAME, col, value); + region.put(put); + } + } + } +}