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