1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
54
55
56 class HMerge {
57 static final Log LOG = LogFactory.getLog(HMerge.class);
58 static final Random rand = new Random();
59
60
61
62
63 private HMerge() {
64 super();
65 }
66
67
68
69
70
71
72
73
74
75
76
77
78
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
88
89
90
91
92
93
94
95
96
97
98
99
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
198
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
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
273
274
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
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
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 }