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.util;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.NoSuchElementException;
25  import java.util.Random;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.classification.InterfaceAudience;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.fs.FileSystem;
32  import org.apache.hadoop.fs.Path;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.HTableDescriptor;
37  import org.apache.hadoop.hbase.RemoteExceptionHandler;
38  import org.apache.hadoop.hbase.TableNotDisabledException;
39  import org.apache.hadoop.hbase.catalog.MetaEditor;
40  import org.apache.hadoop.hbase.client.Delete;
41  import org.apache.hadoop.hbase.client.HBaseAdmin;
42  import org.apache.hadoop.hbase.client.HConnectable;
43  import org.apache.hadoop.hbase.client.HConnection;
44  import org.apache.hadoop.hbase.client.HConnectionManager;
45  import org.apache.hadoop.hbase.client.HTable;
46  import org.apache.hadoop.hbase.client.Result;
47  import org.apache.hadoop.hbase.client.ResultScanner;
48  import org.apache.hadoop.hbase.regionserver.HRegion;
49  import org.apache.hadoop.hbase.regionserver.wal.HLog;
50  import org.apache.hadoop.hbase.regionserver.wal.HLogFactory;
51  
52  /**
53   * A non-instantiable class that has a static method capable of compacting
54   * a table by merging adjacent regions.
55   */
56  @InterfaceAudience.Private
57  class HMerge {
58    // TODO: Where is this class used?  How does it relate to Merge in same package?
59    static final Log LOG = LogFactory.getLog(HMerge.class);
60    static final Random rand = new Random();
61  
62    /*
63     * Not instantiable
64     */
65    private HMerge() {
66      super();
67    }
68  
69    /**
70     * Scans the table and merges two adjacent regions if they are small. This
71     * only happens when a lot of rows are deleted.
72     *
73     * When merging the hbase:meta region, the HBase instance must be offline.
74     * When merging a normal table, the HBase instance must be online, but the
75     * table must be disabled.
76     *
77     * @param conf        - configuration object for HBase
78     * @param fs          - FileSystem where regions reside
79     * @param tableName   - Table to be compacted
80     * @throws IOException
81     */
82    public static void merge(Configuration conf, FileSystem fs,
83      final TableName tableName)
84    throws IOException {
85      merge(conf, fs, tableName, true);
86    }
87  
88    /**
89     * Scans the table and merges two adjacent regions if they are small. This
90     * only happens when a lot of rows are deleted.
91     *
92     * When merging the hbase:meta region, the HBase instance must be offline.
93     * When merging a normal table, the HBase instance must be online, but the
94     * table must be disabled.
95     *
96     * @param conf        - configuration object for HBase
97     * @param fs          - FileSystem where regions reside
98     * @param tableName   - Table to be compacted
99     * @param testMasterRunning True if we are to verify master is down before
100    * running merge
101    * @throws IOException
102    */
103   public static void merge(Configuration conf, FileSystem fs,
104     final TableName tableName, final boolean testMasterRunning)
105   throws IOException {
106     boolean masterIsRunning = false;
107     if (testMasterRunning) {
108       masterIsRunning = HConnectionManager
109           .execute(new HConnectable<Boolean>(conf) {
110             @Override
111             public Boolean connect(HConnection connection) throws IOException {
112               return connection.isMasterRunning();
113             }
114           });
115     }
116     if (tableName.equals(TableName.META_TABLE_NAME)) {
117       if (masterIsRunning) {
118         throw new IllegalStateException(
119             "Can not compact hbase:meta table if instance is on-line");
120       }
121       // TODO reenable new OfflineMerger(conf, fs).process();
122     } else {
123       if(!masterIsRunning) {
124         throw new IllegalStateException(
125             "HBase instance must be running to merge a normal table");
126       }
127       HBaseAdmin admin = new HBaseAdmin(conf);
128       try {
129         if (!admin.isTableDisabled(tableName)) {
130           throw new TableNotDisabledException(tableName);
131         }
132       } finally {
133         admin.close();
134       }
135       new OnlineMerger(conf, fs, tableName).process();
136     }
137   }
138 
139   private static abstract class Merger {
140     protected final Configuration conf;
141     protected final FileSystem fs;
142     protected final Path rootDir;
143     protected final HTableDescriptor htd;
144     protected final HLog hlog;
145     private final long maxFilesize;
146 
147 
148     protected Merger(Configuration conf, FileSystem fs, final TableName tableName)
149     throws IOException {
150       this.conf = conf;
151       this.fs = fs;
152       this.maxFilesize = conf.getLong(HConstants.HREGION_MAX_FILESIZE,
153           HConstants.DEFAULT_MAX_FILE_SIZE);
154 
155       this.rootDir = FSUtils.getRootDir(conf);
156       Path tabledir = FSUtils.getTableDir(this.rootDir, tableName);
157       this.htd = FSTableDescriptors.getTableDescriptorFromFs(this.fs, tabledir);
158       String logname = "merge_" + System.currentTimeMillis() + HConstants.HREGION_LOGDIR_NAME;
159 
160       this.hlog = HLogFactory.createHLog(fs, tabledir, logname, conf);
161     }
162 
163     void process() throws IOException {
164       try {
165         for (HRegionInfo[] regionsToMerge = next();
166             regionsToMerge != null;
167             regionsToMerge = next()) {
168           if (!merge(regionsToMerge)) {
169             return;
170           }
171         }
172       } finally {
173         try {
174           hlog.closeAndDelete();
175 
176         } catch(IOException e) {
177           LOG.error(e);
178         }
179       }
180     }
181 
182     protected boolean merge(final HRegionInfo[] info) throws IOException {
183       if (info.length < 2) {
184         LOG.info("only one region - nothing to merge");
185         return false;
186       }
187 
188       HRegion currentRegion = null;
189       long currentSize = 0;
190       HRegion nextRegion = null;
191       long nextSize = 0;
192       for (int i = 0; i < info.length - 1; i++) {
193         if (currentRegion == null) {
194           currentRegion = HRegion.openHRegion(conf, fs, this.rootDir, info[i], this.htd, hlog);
195           currentSize = currentRegion.getLargestHStoreSize();
196         }
197         nextRegion = HRegion.openHRegion(conf, fs, this.rootDir, info[i + 1], this.htd, hlog);
198         nextSize = nextRegion.getLargestHStoreSize();
199 
200         if ((currentSize + nextSize) <= (maxFilesize / 2)) {
201           // We merge two adjacent regions if their total size is less than
202           // one half of the desired maximum size
203           LOG.info("Merging regions " + currentRegion.getRegionNameAsString() +
204             " and " + nextRegion.getRegionNameAsString());
205           HRegion mergedRegion =
206             HRegion.mergeAdjacent(currentRegion, nextRegion);
207           updateMeta(currentRegion.getRegionName(), nextRegion.getRegionName(),
208               mergedRegion);
209           break;
210         }
211         LOG.info("not merging regions " + Bytes.toStringBinary(currentRegion.getRegionName())
212             + " and " + Bytes.toStringBinary(nextRegion.getRegionName()));
213         currentRegion.close();
214         currentRegion = nextRegion;
215         currentSize = nextSize;
216       }
217       if(currentRegion != null) {
218         currentRegion.close();
219       }
220       return true;
221     }
222 
223     protected abstract HRegionInfo[] next() throws IOException;
224 
225     protected abstract void updateMeta(final byte [] oldRegion1,
226       final byte [] oldRegion2, HRegion newRegion)
227     throws IOException;
228 
229   }
230 
231   /** Instantiated to compact a normal user table */
232   private static class OnlineMerger extends Merger {
233     private final TableName tableName;
234     private final HTable table;
235     private final ResultScanner metaScanner;
236     private HRegionInfo latestRegion;
237 
238     OnlineMerger(Configuration conf, FileSystem fs,
239       final TableName tableName)
240     throws IOException {
241       super(conf, fs, tableName);
242       this.tableName = tableName;
243       this.table = new HTable(conf, TableName.META_TABLE_NAME);
244       this.metaScanner = table.getScanner(HConstants.CATALOG_FAMILY,
245           HConstants.REGIONINFO_QUALIFIER);
246       this.latestRegion = null;
247     }
248 
249     private HRegionInfo nextRegion() throws IOException {
250       try {
251         Result results = getMetaRow();
252         if (results == null) {
253           return null;
254         }
255         HRegionInfo region = HRegionInfo.getHRegionInfo(results);
256         if (region == null) {
257           throw new NoSuchElementException("meta region entry missing " +
258               Bytes.toString(HConstants.CATALOG_FAMILY) + ":" +
259               Bytes.toString(HConstants.REGIONINFO_QUALIFIER));
260         }
261         if (!region.getTable().equals(this.tableName)) {
262           return null;
263         }
264         return region;
265       } catch (IOException e) {
266         e = RemoteExceptionHandler.checkIOException(e);
267         LOG.error("meta scanner error", e);
268         metaScanner.close();
269         throw e;
270       }
271     }
272 
273     /*
274      * Check current row has a HRegionInfo.  Skip to next row if HRI is empty.
275      * @return A Map of the row content else null if we are off the end.
276      * @throws IOException
277      */
278     private Result getMetaRow() throws IOException {
279       Result currentRow = metaScanner.next();
280       boolean foundResult = false;
281       while (currentRow != null) {
282         LOG.info("Row: <" + Bytes.toStringBinary(currentRow.getRow()) + ">");
283         byte[] regionInfoValue = currentRow.getValue(HConstants.CATALOG_FAMILY,
284             HConstants.REGIONINFO_QUALIFIER);
285         if (regionInfoValue == null || regionInfoValue.length == 0) {
286           currentRow = metaScanner.next();
287           continue;
288         }
289         HRegionInfo region = HRegionInfo.getHRegionInfo(currentRow);
290         if (!region.getTable().equals(this.tableName)) {
291           currentRow = metaScanner.next();
292           continue;
293         }
294         foundResult = true;
295         break;
296       }
297       return foundResult ? currentRow : null;
298     }
299 
300     @Override
301     protected HRegionInfo[] next() throws IOException {
302       List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
303       if(latestRegion == null) {
304         latestRegion = nextRegion();
305       }
306       if(latestRegion != null) {
307         regions.add(latestRegion);
308       }
309       latestRegion = nextRegion();
310       if(latestRegion != null) {
311         regions.add(latestRegion);
312       }
313       return regions.toArray(new HRegionInfo[regions.size()]);
314     }
315 
316     @Override
317     protected void updateMeta(final byte [] oldRegion1,
318         final byte [] oldRegion2,
319       HRegion newRegion)
320     throws IOException {
321       byte[][] regionsToDelete = {oldRegion1, oldRegion2};
322       for (int r = 0; r < regionsToDelete.length; r++) {
323         if(Bytes.equals(regionsToDelete[r], latestRegion.getRegionName())) {
324           latestRegion = null;
325         }
326         Delete delete = new Delete(regionsToDelete[r]);
327         table.delete(delete);
328         if(LOG.isDebugEnabled()) {
329           LOG.debug("updated columns in row: " + Bytes.toStringBinary(regionsToDelete[r]));
330         }
331       }
332       newRegion.getRegionInfo().setOffline(true);
333 
334       MetaEditor.addRegionToMeta(table, newRegion.getRegionInfo());
335 
336       if(LOG.isDebugEnabled()) {
337         LOG.debug("updated columns in row: "
338             + Bytes.toStringBinary(newRegion.getRegionName()));
339       }
340     }
341   }
342 }