View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.master.normalizer;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.Comparator;
24  import java.util.List;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.hbase.HBaseIOException;
29  import org.apache.hadoop.hbase.HRegionInfo;
30  import org.apache.hadoop.hbase.RegionLoad;
31  import org.apache.hadoop.hbase.ServerName;
32  import org.apache.hadoop.hbase.TableName;
33  import org.apache.hadoop.hbase.classification.InterfaceAudience;
34  import org.apache.hadoop.hbase.client.Admin.MasterSwitchType;
35  import org.apache.hadoop.hbase.master.MasterRpcServices;
36  import org.apache.hadoop.hbase.master.MasterServices;
37  import org.apache.hadoop.hbase.protobuf.RequestConverter;
38  
39  import com.google.protobuf.ServiceException;
40  
41  /**
42   * Simple implementation of region normalizer.
43   *
44   * Logic in use:
45   *
46   *  <ol>
47   *  <li> get all regions of a given table
48   *  <li> get avg size S of each region (by total size of store files reported in RegionLoad)
49   *  <li> If biggest region is bigger than S * 2, it is kindly requested to split,
50   *    and normalization stops
51   *  <li> Otherwise, two smallest region R1 and its smallest neighbor R2 are kindly requested
52   *    to merge, if R1 + R1 &lt;  S, and normalization stops
53   *  <li> Otherwise, no action is performed
54   * </ol>
55   * <p>
56   * Region sizes are coarse and approximate on the order of megabytes. Additionally,
57   * "empty" regions (less than 1MB, with the previous note) are not merged away. This
58   * is by design to prevent normalization from undoing the pre-splitting of a table.
59   */
60  @InterfaceAudience.Private
61  public class SimpleRegionNormalizer implements RegionNormalizer {
62  
63    private static final Log LOG = LogFactory.getLog(SimpleRegionNormalizer.class);
64    private static final int MIN_REGION_COUNT = 3;
65    private MasterServices masterServices;
66    private MasterRpcServices masterRpcServices;
67  
68    /**
69     * Set the master service.
70     * @param masterServices inject instance of MasterServices
71     */
72    @Override
73    public void setMasterServices(MasterServices masterServices) {
74      this.masterServices = masterServices;
75    }
76  
77    // Comparator that gives higher priority to region Split plan
78    private Comparator<NormalizationPlan> planComparator =
79        new Comparator<NormalizationPlan>() {
80      @Override
81      public int compare(NormalizationPlan plan, NormalizationPlan plan2) {
82        if (plan instanceof SplitNormalizationPlan) {
83          return -1;
84        }
85        if (plan2 instanceof SplitNormalizationPlan) {
86          return 1;
87        }
88        return 0;
89      }
90    };
91  
92    @Override
93    public void setMasterRpcServices(MasterRpcServices masterRpcServices) {
94      this.masterRpcServices = masterRpcServices;
95    }
96  
97    /**
98     * Computes next most "urgent" normalization action on the table.
99     * Action may be either a split, or a merge, or no action.
100    *
101    * @param table table to normalize
102    * @return normalization plan to execute
103    */
104   @Override
105   public List<NormalizationPlan> computePlanForTable(TableName table) throws HBaseIOException {
106     if (table == null || table.isSystemTable()) {
107       LOG.debug("Normalization of system table " + table + " isn't allowed");
108       return null;
109     }
110 
111     List<NormalizationPlan> plans = new ArrayList<NormalizationPlan>();
112     List<HRegionInfo> tableRegions = masterServices.getAssignmentManager().getRegionStates().
113       getRegionsOfTable(table);
114 
115     //TODO: should we make min number of regions a config param?
116     if (tableRegions == null || tableRegions.size() < MIN_REGION_COUNT) {
117       int nrRegions = tableRegions == null ? 0 : tableRegions.size();
118       LOG.debug("Table " + table + " has " + nrRegions + " regions, required min number"
119         + " of regions for normalizer to run is " + MIN_REGION_COUNT + ", not running normalizer");
120       return null;
121     }
122 
123     LOG.debug("Computing normalization plan for table: " + table +
124       ", number of regions: " + tableRegions.size());
125 
126     long totalSizeMb = 0;
127 
128     for (int i = 0; i < tableRegions.size(); i++) {
129       HRegionInfo hri = tableRegions.get(i);
130       long regionSize = getRegionSize(hri);
131       totalSizeMb += regionSize;
132     }
133 
134     double avgRegionSize = totalSizeMb / (double) tableRegions.size();
135 
136     LOG.debug("Table " + table + ", total aggregated regions size: " + totalSizeMb);
137     LOG.debug("Table " + table + ", average region size: " + avgRegionSize);
138 
139     int candidateIdx = 0;
140     boolean splitEnabled = true, mergeEnabled = true;
141     try {
142       splitEnabled = masterRpcServices.isSplitOrMergeEnabled(null,
143         RequestConverter.buildIsSplitOrMergeEnabledRequest(MasterSwitchType.SPLIT)).getEnabled();
144     } catch (ServiceException se) {
145       LOG.debug("Unable to determine whether split is enabled", se);
146     }
147     try {
148       mergeEnabled = masterRpcServices.isSplitOrMergeEnabled(null,
149         RequestConverter.buildIsSplitOrMergeEnabledRequest(MasterSwitchType.MERGE)).getEnabled();
150     } catch (ServiceException se) {
151       LOG.debug("Unable to determine whether split is enabled", se);
152     }
153     while (candidateIdx < tableRegions.size()) {
154       HRegionInfo hri = tableRegions.get(candidateIdx);
155       long regionSize = getRegionSize(hri);
156       // if the region is > 2 times larger than average, we split it, split
157       // is more high priority normalization action than merge.
158       if (regionSize > 2 * avgRegionSize) {
159         if (splitEnabled) {
160           LOG.info("Table " + table + ", large region " + hri.getRegionNameAsString() + " has size "
161               + regionSize + ", more than twice avg size, splitting");
162           plans.add(new SplitNormalizationPlan(hri, null));
163         }
164       } else {
165         if (candidateIdx == tableRegions.size()-1) {
166           break;
167         }
168         if (mergeEnabled) {
169           HRegionInfo hri2 = tableRegions.get(candidateIdx+1);
170           long regionSize2 = getRegionSize(hri2);
171           if (regionSize + regionSize2 < avgRegionSize) {
172             LOG.info("Table " + table + ", small region size: " + regionSize
173               + " plus its neighbor size: " + regionSize2
174               + ", less than the avg size " + avgRegionSize + ", merging them");
175             plans.add(new MergeNormalizationPlan(hri, hri2));
176             candidateIdx++;
177           }
178         }
179       }
180       candidateIdx++;
181     }
182     if (plans.isEmpty()) {
183       LOG.debug("No normalization needed, regions look good for table: " + table);
184       return null;
185     }
186     Collections.sort(plans, planComparator);
187     return plans;
188   }
189 
190   private long getRegionSize(HRegionInfo hri) {
191     ServerName sn = masterServices.getAssignmentManager().getRegionStates().
192       getRegionServerOfRegion(hri);
193     RegionLoad regionLoad = masterServices.getServerManager().getLoad(sn).
194       getRegionsLoad().get(hri.getRegionName());
195     return regionLoad.getStorefileSizeMB();
196   }
197 }