View Javadoc

1   /**
2    * Copyright 2009 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.util;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.NoSuchElementException;
26  import java.util.Random;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
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.HConstants;
34  import org.apache.hadoop.hbase.HRegionInfo;
35  import org.apache.hadoop.hbase.HTableDescriptor;
36  import org.apache.hadoop.hbase.KeyValue;
37  import org.apache.hadoop.hbase.RemoteExceptionHandler;
38  import org.apache.hadoop.hbase.TableNotDisabledException;
39  import org.apache.hadoop.hbase.client.Delete;
40  import org.apache.hadoop.hbase.client.HBaseAdmin;
41  import org.apache.hadoop.hbase.client.HConnection;
42  import org.apache.hadoop.hbase.client.HConnectionManager;
43  import org.apache.hadoop.hbase.client.HConnectionManager.HConnectable;
44  import org.apache.hadoop.hbase.client.HTable;
45  import org.apache.hadoop.hbase.client.Put;
46  import org.apache.hadoop.hbase.client.Result;
47  import org.apache.hadoop.hbase.client.ResultScanner;
48  import org.apache.hadoop.hbase.client.Scan;
49  import org.apache.hadoop.hbase.regionserver.HRegion;
50  import org.apache.hadoop.hbase.regionserver.InternalScanner;
51  import org.apache.hadoop.hbase.regionserver.wal.HLog;
52  
53  /**
54   * A non-instantiable class that has a static method capable of compacting
55   * a table by merging adjacent regions.
56   */
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 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 byte [] 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 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 byte [] 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 (Bytes.equals(tableName, HConstants.META_TABLE_NAME)) {
117       if (masterIsRunning) {
118         throw new IllegalStateException(
119             "Can not compact META table if instance is on-line");
120       }
121       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       if (!admin.isTableDisabled(tableName)) {
129         throw new TableNotDisabledException(tableName);
130       }
131       new OnlineMerger(conf, fs, tableName).process();
132     }
133   }
134 
135   private static abstract class Merger {
136     protected final Configuration conf;
137     protected final FileSystem fs;
138     protected final Path tabledir;
139     protected final HTableDescriptor htd;
140     protected final HLog hlog;
141     private final long maxFilesize;
142 
143 
144     protected Merger(Configuration conf, FileSystem fs, final byte [] tableName)
145     throws IOException {
146       this.conf = conf;
147       this.fs = fs;
148       this.maxFilesize = conf.getLong(HConstants.HREGION_MAX_FILESIZE,
149           HConstants.DEFAULT_MAX_FILE_SIZE);
150 
151       this.tabledir = new Path(
152           fs.makeQualified(new Path(conf.get(HConstants.HBASE_DIR))),
153           Bytes.toString(tableName)
154       );
155       this.htd = FSTableDescriptors.getTableDescriptor(this.fs, this.tabledir);
156       Path logdir = new Path(tabledir, "merge_" + System.currentTimeMillis() +
157           HConstants.HREGION_LOGDIR_NAME);
158       Path oldLogDir = new Path(tabledir, HConstants.HREGION_OLDLOGDIR_NAME);
159       this.hlog = new HLog(fs, logdir, oldLogDir, conf);
160     }
161 
162     void process() throws IOException {
163       try {
164         for (HRegionInfo[] regionsToMerge = next();
165             regionsToMerge != null;
166             regionsToMerge = next()) {
167           if (!merge(regionsToMerge)) {
168             return;
169           }
170         }
171       } finally {
172         try {
173           hlog.closeAndDelete();
174 
175         } catch(IOException e) {
176           LOG.error(e);
177         }
178       }
179     }
180 
181     protected boolean merge(final HRegionInfo[] info) throws IOException {
182       if (info.length < 2) {
183         LOG.info("only one region - nothing to merge");
184         return false;
185       }
186 
187       HRegion currentRegion = null;
188       long currentSize = 0;
189       HRegion nextRegion = null;
190       long nextSize = 0;
191       for (int i = 0; i < info.length - 1; i++) {
192         if (currentRegion == null) {
193           currentRegion = HRegion.newHRegion(tabledir, hlog, fs, conf, info[i],
194             this.htd, null);
195           currentRegion.initialize();
196           currentSize = currentRegion.getLargestHStoreSize();
197         }
198         nextRegion = HRegion.newHRegion(tabledir, hlog, fs, conf, info[i + 1],
199           this.htd, null);
200         nextRegion.initialize();
201         nextSize = nextRegion.getLargestHStoreSize();
202 
203         if ((currentSize + nextSize) <= (maxFilesize / 2)) {
204           // We merge two adjacent regions if their total size is less than
205           // one half of the desired maximum size
206           LOG.info("Merging regions " + currentRegion.getRegionNameAsString() +
207             " and " + nextRegion.getRegionNameAsString());
208           HRegion mergedRegion =
209             HRegion.mergeAdjacent(currentRegion, nextRegion);
210           updateMeta(currentRegion.getRegionName(), nextRegion.getRegionName(),
211               mergedRegion);
212           break;
213         }
214         LOG.info("not merging regions " + Bytes.toStringBinary(currentRegion.getRegionName())
215             + " and " + Bytes.toStringBinary(nextRegion.getRegionName()));
216         currentRegion.close();
217         currentRegion = nextRegion;
218         currentSize = nextSize;
219       }
220       if(currentRegion != null) {
221         currentRegion.close();
222       }
223       return true;
224     }
225 
226     protected abstract HRegionInfo[] next() throws IOException;
227 
228     protected abstract void updateMeta(final byte [] oldRegion1,
229       final byte [] oldRegion2, HRegion newRegion)
230     throws IOException;
231 
232   }
233 
234   /** Instantiated to compact a normal user table */
235   private static class OnlineMerger extends Merger {
236     private final byte [] tableName;
237     private final HTable table;
238     private final ResultScanner metaScanner;
239     private HRegionInfo latestRegion;
240 
241     OnlineMerger(Configuration conf, FileSystem fs,
242       final byte [] tableName)
243     throws IOException {
244       super(conf, fs, tableName);
245       this.tableName = tableName;
246       this.table = new HTable(conf, HConstants.META_TABLE_NAME);
247       this.metaScanner = table.getScanner(HConstants.CATALOG_FAMILY,
248           HConstants.REGIONINFO_QUALIFIER);
249       this.latestRegion = null;
250     }
251 
252     private HRegionInfo nextRegion() throws IOException {
253       try {
254         Result results = getMetaRow();
255         if (results == null) {
256           return null;
257         }
258         byte[] regionInfoValue = results.getValue(HConstants.CATALOG_FAMILY,
259             HConstants.REGIONINFO_QUALIFIER);
260         if (regionInfoValue == null || regionInfoValue.length == 0) {
261           throw new NoSuchElementException("meta region entry missing " +
262               Bytes.toString(HConstants.CATALOG_FAMILY) + ":" +
263               Bytes.toString(HConstants.REGIONINFO_QUALIFIER));
264         }
265         HRegionInfo region = Writables.getHRegionInfo(regionInfoValue);
266         if (!Bytes.equals(region.getTableName(), this.tableName)) {
267           return null;
268         }
269         return region;
270       } catch (IOException e) {
271         e = RemoteExceptionHandler.checkIOException(e);
272         LOG.error("meta scanner error", e);
273         metaScanner.close();
274         throw e;
275       }
276     }
277 
278     /*
279      * Check current row has a HRegionInfo.  Skip to next row if HRI is empty.
280      * @return A Map of the row content else null if we are off the end.
281      * @throws IOException
282      */
283     private Result getMetaRow() throws IOException {
284       Result currentRow = metaScanner.next();
285       boolean foundResult = false;
286       while (currentRow != null) {
287         LOG.info("Row: <" + Bytes.toStringBinary(currentRow.getRow()) + ">");
288         byte[] regionInfoValue = currentRow.getValue(HConstants.CATALOG_FAMILY,
289             HConstants.REGIONINFO_QUALIFIER);
290         if (regionInfoValue == null || regionInfoValue.length == 0) {
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       Put put = new Put(newRegion.getRegionName());
335       put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
336         Writables.getBytes(newRegion.getRegionInfo()));
337       table.put(put);
338 
339       if(LOG.isDebugEnabled()) {
340         LOG.debug("updated columns in row: "
341             + Bytes.toStringBinary(newRegion.getRegionName()));
342       }
343     }
344   }
345 
346   /** Instantiated to compact the meta region */
347   private static class OfflineMerger extends Merger {
348     private final List<HRegionInfo> metaRegions = new ArrayList<HRegionInfo>();
349     private final HRegion root;
350 
351     OfflineMerger(Configuration conf, FileSystem fs)
352         throws IOException {
353       super(conf, fs, HConstants.META_TABLE_NAME);
354 
355       Path rootTableDir = HTableDescriptor.getTableDir(
356           fs.makeQualified(new Path(conf.get(HConstants.HBASE_DIR))),
357           HConstants.ROOT_TABLE_NAME);
358 
359       // Scan root region to find all the meta regions
360 
361       root = HRegion.newHRegion(rootTableDir, hlog, fs, conf,
362           HRegionInfo.ROOT_REGIONINFO, HTableDescriptor.ROOT_TABLEDESC, null);
363       root.initialize();
364 
365       Scan scan = new Scan();
366       scan.addColumn(HConstants.CATALOG_FAMILY,
367           HConstants.REGIONINFO_QUALIFIER);
368       InternalScanner rootScanner =
369         root.getScanner(scan);
370 
371       try {
372         List<KeyValue> results = new ArrayList<KeyValue>();
373         boolean hasMore;
374         do {
375           hasMore = rootScanner.next(results);
376           for(KeyValue kv: results) {
377             HRegionInfo info = Writables.getHRegionInfoOrNull(kv.getValue());
378             if (info != null) {
379               metaRegions.add(info);
380             }
381           }
382         } while (hasMore);
383       } finally {
384         rootScanner.close();
385         try {
386           root.close();
387 
388         } catch(IOException e) {
389           LOG.error(e);
390         }
391       }
392     }
393 
394     @Override
395     protected HRegionInfo[] next() {
396       HRegionInfo[] results = null;
397       if (metaRegions.size() > 0) {
398         results = metaRegions.toArray(new HRegionInfo[metaRegions.size()]);
399         metaRegions.clear();
400       }
401       return results;
402     }
403 
404     @Override
405     protected void updateMeta(final byte [] oldRegion1,
406       final byte [] oldRegion2, HRegion newRegion)
407     throws IOException {
408       byte[][] regionsToDelete = {oldRegion1, oldRegion2};
409       for(int r = 0; r < regionsToDelete.length; r++) {
410         Delete delete = new Delete(regionsToDelete[r]);
411         delete.deleteColumns(HConstants.CATALOG_FAMILY,
412             HConstants.REGIONINFO_QUALIFIER);
413         delete.deleteColumns(HConstants.CATALOG_FAMILY,
414             HConstants.SERVER_QUALIFIER);
415         delete.deleteColumns(HConstants.CATALOG_FAMILY,
416             HConstants.STARTCODE_QUALIFIER);
417         delete.deleteColumns(HConstants.CATALOG_FAMILY,
418             HConstants.SPLITA_QUALIFIER);
419         delete.deleteColumns(HConstants.CATALOG_FAMILY,
420             HConstants.SPLITB_QUALIFIER);
421         root.delete(delete, null, true);
422 
423         if(LOG.isDebugEnabled()) {
424           LOG.debug("updated columns in row: " + Bytes.toStringBinary(regionsToDelete[r]));
425         }
426       }
427       HRegionInfo newInfo = newRegion.getRegionInfo();
428       newInfo.setOffline(true);
429       Put put = new Put(newRegion.getRegionName());
430       put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
431           Writables.getBytes(newInfo));
432       root.put(put);
433       if(LOG.isDebugEnabled()) {
434         LOG.debug("updated columns in row: " + Bytes.toStringBinary(newRegion.getRegionName()));
435       }
436     }
437   }
438 }