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.Connection;
41  import org.apache.hadoop.hbase.client.ConnectionFactory;
42  import org.apache.hadoop.hbase.client.Delete;
43  import org.apache.hadoop.hbase.client.HBaseAdmin;
44  import org.apache.hadoop.hbase.client.HConnection;
45  import org.apache.hadoop.hbase.client.Result;
46  import org.apache.hadoop.hbase.client.ResultScanner;
47  import org.apache.hadoop.hbase.client.Table;
48  import org.apache.hadoop.hbase.regionserver.HRegion;
49  import org.apache.hadoop.hbase.wal.WALFactory;
50  import org.apache.hadoop.ipc.RemoteException;
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     HConnection hConnection = null;
108     if (testMasterRunning) {
109       try {
110         hConnection = (HConnection) ConnectionFactory.createConnection(conf);
111         masterIsRunning = hConnection.isMasterRunning();
112       } finally {
113         if (hConnection != null) {
114           hConnection.close();
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 WALFactory walFactory;
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       final Configuration walConf = new Configuration(conf);
164       FSUtils.setRootDir(walConf, tabledir);
165       this.walFactory = new WALFactory(walConf, null, logname);
166     }
167 
168     void process() throws IOException {
169       try {
170         for (HRegionInfo[] regionsToMerge = next();
171             regionsToMerge != null;
172             regionsToMerge = next()) {
173           if (!merge(regionsToMerge)) {
174             return;
175           }
176         }
177       } finally {
178         try {
179           walFactory.close();
180         } catch(IOException e) {
181           LOG.error(e);
182         }
183       }
184     }
185 
186     protected boolean merge(final HRegionInfo[] info) throws IOException {
187       if (info.length < 2) {
188         LOG.info("only one region - nothing to merge");
189         return false;
190       }
191 
192       HRegion currentRegion = null;
193       long currentSize = 0;
194       HRegion nextRegion = null;
195       long nextSize = 0;
196       for (int i = 0; i < info.length - 1; i++) {
197         if (currentRegion == null) {
198           currentRegion = HRegion.openHRegion(conf, fs, this.rootDir, info[i], this.htd,
199               walFactory.getWAL(info[i].getEncodedNameAsBytes()));
200           currentSize = currentRegion.getLargestHStoreSize();
201         }
202         nextRegion = HRegion.openHRegion(conf, fs, this.rootDir, info[i + 1], this.htd,
203             walFactory.getWAL(info[i+1].getEncodedNameAsBytes()));
204         nextSize = nextRegion.getLargestHStoreSize();
205 
206         if ((currentSize + nextSize) <= (maxFilesize / 2)) {
207           // We merge two adjacent regions if their total size is less than
208           // one half of the desired maximum size
209           LOG.info("Merging regions " + currentRegion.getRegionInfo().getRegionNameAsString() +
210             " and " + nextRegion.getRegionInfo().getRegionNameAsString());
211           HRegion mergedRegion =
212             HRegion.mergeAdjacent(currentRegion, nextRegion);
213           updateMeta(currentRegion.getRegionInfo().getRegionName(),
214             nextRegion.getRegionInfo().getRegionName(), mergedRegion);
215           break;
216         }
217         LOG.info("not merging regions " +
218           Bytes.toStringBinary(currentRegion.getRegionInfo().getRegionName()) +
219             " and " + Bytes.toStringBinary(nextRegion.getRegionInfo().getRegionName()));
220         currentRegion.close();
221         currentRegion = nextRegion;
222         currentSize = nextSize;
223       }
224       if(currentRegion != null) {
225         currentRegion.close();
226       }
227       return true;
228     }
229 
230     protected abstract HRegionInfo[] next() throws IOException;
231 
232     protected abstract void updateMeta(final byte [] oldRegion1,
233       final byte [] oldRegion2, HRegion newRegion)
234     throws IOException;
235 
236   }
237 
238   /** Instantiated to compact a normal user table */
239   private static class OnlineMerger extends Merger {
240     private final TableName tableName;
241     private final Table table;
242     private final ResultScanner metaScanner;
243     private HRegionInfo latestRegion;
244 
245     OnlineMerger(Configuration conf, FileSystem fs,
246       final TableName tableName)
247     throws IOException {
248       super(conf, fs, tableName);
249       this.tableName = tableName;
250       Connection connection = ConnectionFactory.createConnection(conf);
251       this.table = connection.getTable(TableName.META_TABLE_NAME);
252       this.metaScanner = table.getScanner(HConstants.CATALOG_FAMILY,
253           HConstants.REGIONINFO_QUALIFIER);
254       this.latestRegion = null;
255     }
256 
257     private HRegionInfo nextRegion() throws IOException {
258       try {
259         Result results = getMetaRow();
260         if (results == null) {
261           return null;
262         }
263         HRegionInfo region = HRegionInfo.getHRegionInfo(results);
264         if (region == null) {
265           throw new NoSuchElementException("meta region entry missing " +
266               Bytes.toString(HConstants.CATALOG_FAMILY) + ":" +
267               Bytes.toString(HConstants.REGIONINFO_QUALIFIER));
268         }
269         if (!region.getTable().equals(this.tableName)) {
270           return null;
271         }
272         return region;
273       } catch (IOException e) {
274         e = e instanceof RemoteException ? ((RemoteException) e).unwrapRemoteException() : e;
275         LOG.error("meta scanner error", e);
276         metaScanner.close();
277         throw e;
278       }
279     }
280 
281     /*
282      * Check current row has a HRegionInfo.  Skip to next row if HRI is empty.
283      * @return A Map of the row content else null if we are off the end.
284      * @throws IOException
285      */
286     private Result getMetaRow() throws IOException {
287       Result currentRow = metaScanner.next();
288       boolean foundResult = false;
289       while (currentRow != null) {
290         LOG.info("Row: <" + Bytes.toStringBinary(currentRow.getRow()) + ">");
291         byte[] regionInfoValue = currentRow.getValue(HConstants.CATALOG_FAMILY,
292             HConstants.REGIONINFO_QUALIFIER);
293         if (regionInfoValue == null || regionInfoValue.length == 0) {
294           currentRow = metaScanner.next();
295           continue;
296         }
297         HRegionInfo region = HRegionInfo.getHRegionInfo(currentRow);
298         if (!region.getTable().equals(this.tableName)) {
299           currentRow = metaScanner.next();
300           continue;
301         }
302         foundResult = true;
303         break;
304       }
305       return foundResult ? currentRow : null;
306     }
307 
308     @Override
309     protected HRegionInfo[] next() throws IOException {
310       List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
311       if(latestRegion == null) {
312         latestRegion = nextRegion();
313       }
314       if(latestRegion != null) {
315         regions.add(latestRegion);
316       }
317       latestRegion = nextRegion();
318       if(latestRegion != null) {
319         regions.add(latestRegion);
320       }
321       return regions.toArray(new HRegionInfo[regions.size()]);
322     }
323 
324     @Override
325     protected void updateMeta(final byte [] oldRegion1,
326         final byte [] oldRegion2,
327       HRegion newRegion)
328     throws IOException {
329       byte[][] regionsToDelete = {oldRegion1, oldRegion2};
330       for (int r = 0; r < regionsToDelete.length; r++) {
331         if(Bytes.equals(regionsToDelete[r], latestRegion.getRegionName())) {
332           latestRegion = null;
333         }
334         Delete delete = new Delete(regionsToDelete[r]);
335         table.delete(delete);
336         if(LOG.isDebugEnabled()) {
337           LOG.debug("updated columns in row: " + Bytes.toStringBinary(regionsToDelete[r]));
338         }
339       }
340       newRegion.getRegionInfo().setOffline(true);
341 
342       MetaTableAccessor.addRegionToMeta(table, newRegion.getRegionInfo());
343 
344       if(LOG.isDebugEnabled()) {
345         LOG.debug("updated columns in row: "
346             + Bytes.toStringBinary(newRegion.getRegionInfo().getRegionName()));
347       }
348     }
349   }
350 }