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 org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.conf.Configuration;
25  import org.apache.hadoop.fs.FileSystem;
26  import org.apache.hadoop.fs.Path;
27  import org.apache.hadoop.hbase.HConstants;
28  import org.apache.hadoop.hbase.HRegionInfo;
29  import org.apache.hadoop.hbase.HTableDescriptor;
30  import org.apache.hadoop.hbase.KeyValue;
31  import org.apache.hadoop.hbase.RemoteExceptionHandler;
32  import org.apache.hadoop.hbase.TableNotDisabledException;
33  import org.apache.hadoop.hbase.client.Delete;
34  import org.apache.hadoop.hbase.client.HBaseAdmin;
35  import org.apache.hadoop.hbase.client.HConnection;
36  import org.apache.hadoop.hbase.client.HConnectionManager;
37  import org.apache.hadoop.hbase.client.HTable;
38  import org.apache.hadoop.hbase.client.Put;
39  import org.apache.hadoop.hbase.client.Result;
40  import org.apache.hadoop.hbase.client.ResultScanner;
41  import org.apache.hadoop.hbase.client.Scan;
42  import org.apache.hadoop.hbase.regionserver.HRegion;
43  import org.apache.hadoop.hbase.regionserver.InternalScanner;
44  import org.apache.hadoop.hbase.regionserver.wal.HLog;
45  
46  import java.io.IOException;
47  import java.util.ArrayList;
48  import java.util.List;
49  import java.util.NoSuchElementException;
50  import java.util.Random;
51  
52  /**
53   * A non-instantiable class that has a static method capable of compacting
54   * a table by merging adjacent regions.
55   */
56  class HMerge {
57    static final Log LOG = LogFactory.getLog(HMerge.class);
58    static final Random rand = new Random();
59  
60    /*
61     * Not instantiable
62     */
63    private HMerge() {
64      super();
65    }
66  
67    /**
68     * Scans the table and merges two adjacent regions if they are small. This
69     * only happens when a lot of rows are deleted.
70     *
71     * When merging the META region, the HBase instance must be offline.
72     * When merging a normal table, the HBase instance must be online, but the
73     * table must be disabled.
74     *
75     * @param conf        - configuration object for HBase
76     * @param fs          - FileSystem where regions reside
77     * @param tableName   - Table to be compacted
78     * @throws IOException
79     */
80    public static void merge(Configuration conf, FileSystem fs,
81      final byte [] tableName)
82    throws IOException {
83      merge(conf, fs, tableName, true);
84    }
85  
86    /**
87     * Scans the table and merges two adjacent regions if they are small. This
88     * only happens when a lot of rows are deleted.
89     *
90     * When merging the META region, the HBase instance must be offline.
91     * When merging a normal table, the HBase instance must be online, but the
92     * table must be disabled.
93     *
94     * @param conf        - configuration object for HBase
95     * @param fs          - FileSystem where regions reside
96     * @param tableName   - Table to be compacted
97     * @param testMasterRunning True if we are to verify master is down before
98     * running merge
99     * @throws IOException
100    */
101   public static void merge(Configuration conf, FileSystem fs,
102     final byte [] tableName, final boolean testMasterRunning)
103   throws IOException {
104     boolean masterIsRunning = false;
105     if (testMasterRunning) {
106       HConnection connection = HConnectionManager.getConnection(conf);
107       masterIsRunning = connection.isMasterRunning();
108     }
109     HConnectionManager.deleteConnection(conf, true);
110     if (Bytes.equals(tableName, HConstants.META_TABLE_NAME)) {
111       if (masterIsRunning) {
112         throw new IllegalStateException(
113             "Can not compact META table if instance is on-line");
114       }
115       new OfflineMerger(conf, fs).process();
116     } else {
117       if(!masterIsRunning) {
118         throw new IllegalStateException(
119             "HBase instance must be running to merge a normal table");
120       }
121       HBaseAdmin admin = new HBaseAdmin(conf);
122       if (!admin.isTableDisabled(tableName)) {
123         throw new TableNotDisabledException(tableName);
124       }
125       new OnlineMerger(conf, fs, tableName).process();
126     }
127   }
128 
129   private static abstract class Merger {
130     protected final Configuration conf;
131     protected final FileSystem fs;
132     protected final Path tabledir;
133     protected final HLog hlog;
134     private final long maxFilesize;
135 
136 
137     protected Merger(Configuration conf, FileSystem fs,
138       final byte [] tableName)
139     throws IOException {
140       this.conf = conf;
141       this.fs = fs;
142       this.maxFilesize = conf.getLong("hbase.hregion.max.filesize",
143           HConstants.DEFAULT_MAX_FILE_SIZE);
144 
145       this.tabledir = new Path(
146           fs.makeQualified(new Path(conf.get(HConstants.HBASE_DIR))),
147           Bytes.toString(tableName)
148       );
149       Path logdir = new Path(tabledir, "merge_" + System.currentTimeMillis() +
150           HConstants.HREGION_LOGDIR_NAME);
151       Path oldLogDir = new Path(tabledir, HConstants.HREGION_OLDLOGDIR_NAME);
152       this.hlog = new HLog(fs, logdir, oldLogDir, conf);
153     }
154 
155     void process() throws IOException {
156       try {
157         for (HRegionInfo[] regionsToMerge = next();
158             regionsToMerge != null;
159             regionsToMerge = next()) {
160           if (!merge(regionsToMerge)) {
161             return;
162           }
163         }
164       } finally {
165         try {
166           hlog.closeAndDelete();
167 
168         } catch(IOException e) {
169           LOG.error(e);
170         }
171       }
172     }
173 
174     protected boolean merge(final HRegionInfo[] info) throws IOException {
175       if (info.length < 2) {
176         LOG.info("only one region - nothing to merge");
177         return false;
178       }
179 
180       HRegion currentRegion = null;
181       long currentSize = 0;
182       HRegion nextRegion = null;
183       long nextSize = 0;
184       for (int i = 0; i < info.length - 1; i++) {
185         if (currentRegion == null) {
186           currentRegion =
187             HRegion.newHRegion(tabledir, hlog, fs, conf, info[i], null);
188           currentRegion.initialize();
189           currentSize = currentRegion.getLargestHStoreSize();
190         }
191         nextRegion =
192           HRegion.newHRegion(tabledir, hlog, fs, conf, info[i + 1], null);
193         nextRegion.initialize();
194         nextSize = nextRegion.getLargestHStoreSize();
195 
196         if ((currentSize + nextSize) <= (maxFilesize / 2)) {
197           // We merge two adjacent regions if their total size is less than
198           // one half of the desired maximum size
199           LOG.info("Merging regions " + currentRegion.getRegionNameAsString() +
200             " and " + nextRegion.getRegionNameAsString());
201           HRegion mergedRegion =
202             HRegion.mergeAdjacent(currentRegion, nextRegion);
203           updateMeta(currentRegion.getRegionName(), nextRegion.getRegionName(),
204               mergedRegion);
205           break;
206         }
207         LOG.info("not merging regions " + Bytes.toStringBinary(currentRegion.getRegionName())
208             + " and " + Bytes.toStringBinary(nextRegion.getRegionName()));
209         currentRegion.close();
210         currentRegion = nextRegion;
211         currentSize = nextSize;
212       }
213       if(currentRegion != null) {
214         currentRegion.close();
215       }
216       return true;
217     }
218 
219     protected abstract HRegionInfo[] next() throws IOException;
220 
221     protected abstract void updateMeta(final byte [] oldRegion1,
222       final byte [] oldRegion2, HRegion newRegion)
223     throws IOException;
224 
225   }
226 
227   /** Instantiated to compact a normal user table */
228   private static class OnlineMerger extends Merger {
229     private final byte [] tableName;
230     private final HTable table;
231     private final ResultScanner metaScanner;
232     private HRegionInfo latestRegion;
233 
234     OnlineMerger(Configuration conf, FileSystem fs,
235       final byte [] tableName)
236     throws IOException {
237       super(conf, fs, tableName);
238       this.tableName = tableName;
239       this.table = new HTable(conf, HConstants.META_TABLE_NAME);
240       this.metaScanner = table.getScanner(HConstants.CATALOG_FAMILY,
241           HConstants.REGIONINFO_QUALIFIER);
242       this.latestRegion = null;
243     }
244 
245     private HRegionInfo nextRegion() throws IOException {
246       try {
247         Result results = getMetaRow();
248         if (results == null) {
249           return null;
250         }
251         byte[] regionInfoValue = results.getValue(HConstants.CATALOG_FAMILY,
252             HConstants.REGIONINFO_QUALIFIER);
253         if (regionInfoValue == null || regionInfoValue.length == 0) {
254           throw new NoSuchElementException("meta region entry missing " +
255               Bytes.toString(HConstants.CATALOG_FAMILY) + ":" +
256               Bytes.toString(HConstants.REGIONINFO_QUALIFIER));
257         }
258         HRegionInfo region = Writables.getHRegionInfo(regionInfoValue);
259         if (!Bytes.equals(region.getTableDesc().getName(), this.tableName)) {
260           return null;
261         }
262         return region;
263       } catch (IOException e) {
264         e = RemoteExceptionHandler.checkIOException(e);
265         LOG.error("meta scanner error", e);
266         metaScanner.close();
267         throw e;
268       }
269     }
270 
271     /*
272      * Check current row has a HRegionInfo.  Skip to next row if HRI is empty.
273      * @return A Map of the row content else null if we are off the end.
274      * @throws IOException
275      */
276     private Result getMetaRow() throws IOException {
277       Result currentRow = metaScanner.next();
278       boolean foundResult = false;
279       while (currentRow != null) {
280         LOG.info("Row: <" + Bytes.toStringBinary(currentRow.getRow()) + ">");
281         byte[] regionInfoValue = currentRow.getValue(HConstants.CATALOG_FAMILY,
282             HConstants.REGIONINFO_QUALIFIER);
283         if (regionInfoValue == null || regionInfoValue.length == 0) {
284           currentRow = metaScanner.next();
285           continue;
286         }
287         foundResult = true;
288         break;
289       }
290       return foundResult ? currentRow : null;
291     }
292 
293     @Override
294     protected HRegionInfo[] next() throws IOException {
295       List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
296       if(latestRegion == null) {
297         latestRegion = nextRegion();
298       }
299       if(latestRegion != null) {
300         regions.add(latestRegion);
301       }
302       latestRegion = nextRegion();
303       if(latestRegion != null) {
304         regions.add(latestRegion);
305       }
306       return regions.toArray(new HRegionInfo[regions.size()]);
307     }
308 
309     @Override
310     protected void updateMeta(final byte [] oldRegion1,
311         final byte [] oldRegion2,
312       HRegion newRegion)
313     throws IOException {
314       byte[][] regionsToDelete = {oldRegion1, oldRegion2};
315       for (int r = 0; r < regionsToDelete.length; r++) {
316         if(Bytes.equals(regionsToDelete[r], latestRegion.getRegionName())) {
317           latestRegion = null;
318         }
319         Delete delete = new Delete(regionsToDelete[r]);
320         table.delete(delete);
321         if(LOG.isDebugEnabled()) {
322           LOG.debug("updated columns in row: " + Bytes.toStringBinary(regionsToDelete[r]));
323         }
324       }
325       newRegion.getRegionInfo().setOffline(true);
326 
327       Put put = new Put(newRegion.getRegionName());
328       put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
329         Writables.getBytes(newRegion.getRegionInfo()));
330       table.put(put);
331 
332       if(LOG.isDebugEnabled()) {
333         LOG.debug("updated columns in row: "
334             + Bytes.toStringBinary(newRegion.getRegionName()));
335       }
336     }
337   }
338 
339   /** Instantiated to compact the meta region */
340   private static class OfflineMerger extends Merger {
341     private final List<HRegionInfo> metaRegions = new ArrayList<HRegionInfo>();
342     private final HRegion root;
343 
344     OfflineMerger(Configuration conf, FileSystem fs)
345         throws IOException {
346       super(conf, fs, HConstants.META_TABLE_NAME);
347 
348       Path rootTableDir = HTableDescriptor.getTableDir(
349           fs.makeQualified(new Path(conf.get(HConstants.HBASE_DIR))),
350           HConstants.ROOT_TABLE_NAME);
351 
352       // Scan root region to find all the meta regions
353 
354       root = HRegion.newHRegion(rootTableDir, hlog, fs, conf,
355           HRegionInfo.ROOT_REGIONINFO, null);
356       root.initialize();
357 
358       Scan scan = new Scan();
359       scan.addColumn(HConstants.CATALOG_FAMILY,
360           HConstants.REGIONINFO_QUALIFIER);
361       InternalScanner rootScanner =
362         root.getScanner(scan);
363 
364       try {
365         List<KeyValue> results = new ArrayList<KeyValue>();
366         while(rootScanner.next(results)) {
367           for(KeyValue kv: results) {
368             HRegionInfo info = Writables.getHRegionInfoOrNull(kv.getValue());
369             if (info != null) {
370               metaRegions.add(info);
371             }
372           }
373         }
374       } finally {
375         rootScanner.close();
376         try {
377           root.close();
378 
379         } catch(IOException e) {
380           LOG.error(e);
381         }
382       }
383     }
384 
385     @Override
386     protected HRegionInfo[] next() {
387       HRegionInfo[] results = null;
388       if (metaRegions.size() > 0) {
389         results = metaRegions.toArray(new HRegionInfo[metaRegions.size()]);
390         metaRegions.clear();
391       }
392       return results;
393     }
394 
395     @Override
396     protected void updateMeta(final byte [] oldRegion1,
397       final byte [] oldRegion2, HRegion newRegion)
398     throws IOException {
399       byte[][] regionsToDelete = {oldRegion1, oldRegion2};
400       for(int r = 0; r < regionsToDelete.length; r++) {
401         Delete delete = new Delete(regionsToDelete[r]);
402         delete.deleteColumns(HConstants.CATALOG_FAMILY,
403             HConstants.REGIONINFO_QUALIFIER);
404         delete.deleteColumns(HConstants.CATALOG_FAMILY,
405             HConstants.SERVER_QUALIFIER);
406         delete.deleteColumns(HConstants.CATALOG_FAMILY,
407             HConstants.STARTCODE_QUALIFIER);
408         delete.deleteColumns(HConstants.CATALOG_FAMILY,
409             HConstants.SPLITA_QUALIFIER);
410         delete.deleteColumns(HConstants.CATALOG_FAMILY,
411             HConstants.SPLITB_QUALIFIER);
412         root.delete(delete, null, true);
413 
414         if(LOG.isDebugEnabled()) {
415           LOG.debug("updated columns in row: " + Bytes.toStringBinary(regionsToDelete[r]));
416         }
417       }
418       HRegionInfo newInfo = newRegion.getRegionInfo();
419       newInfo.setOffline(true);
420       Put put = new Put(newRegion.getRegionName());
421       put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
422           Writables.getBytes(newInfo));
423       root.put(put);
424       if(LOG.isDebugEnabled()) {
425         LOG.debug("updated columns in row: " + Bytes.toStringBinary(newRegion.getRegionName()));
426       }
427     }
428   }
429 }