1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.master;
20
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.util.Comparator;
24 import java.util.HashSet;
25 import java.util.Map;
26 import java.util.TreeMap;
27 import java.util.concurrent.atomic.AtomicBoolean;
28 import java.util.concurrent.atomic.AtomicInteger;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.hadoop.classification.InterfaceAudience;
33 import org.apache.hadoop.fs.FileSystem;
34 import org.apache.hadoop.fs.Path;
35 import org.apache.hadoop.hbase.Chore;
36 import org.apache.hadoop.hbase.HColumnDescriptor;
37 import org.apache.hadoop.hbase.HConstants;
38 import org.apache.hadoop.hbase.HRegionInfo;
39 import org.apache.hadoop.hbase.HTableDescriptor;
40 import org.apache.hadoop.hbase.Server;
41 import org.apache.hadoop.hbase.backup.HFileArchiver;
42 import org.apache.hadoop.hbase.catalog.MetaEditor;
43 import org.apache.hadoop.hbase.catalog.MetaReader;
44 import org.apache.hadoop.hbase.client.Result;
45 import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
46 import org.apache.hadoop.hbase.util.Bytes;
47 import org.apache.hadoop.hbase.util.Pair;
48 import org.apache.hadoop.hbase.util.PairOfSameType;
49 import org.apache.hadoop.hbase.util.Triple;
50
51
52
53
54
55 @InterfaceAudience.Private
56 public class CatalogJanitor extends Chore {
57 private static final Log LOG = LogFactory.getLog(CatalogJanitor.class.getName());
58 private final Server server;
59 private final MasterServices services;
60 private AtomicBoolean enabled = new AtomicBoolean(true);
61 private AtomicBoolean alreadyRunning = new AtomicBoolean(false);
62
63 CatalogJanitor(final Server server, final MasterServices services) {
64 super(server.getServerName() + "-CatalogJanitor",
65 server.getConfiguration().getInt("hbase.catalogjanitor.interval", 300000),
66 server);
67 this.server = server;
68 this.services = services;
69 }
70
71 @Override
72 protected boolean initialChore() {
73 try {
74 if (this.enabled.get()) scan();
75 } catch (IOException e) {
76 LOG.warn("Failed initial scan of catalog table", e);
77 return false;
78 }
79 return true;
80 }
81
82
83
84
85 public boolean setEnabled(final boolean enabled) {
86 return this.enabled.getAndSet(enabled);
87 }
88
89 boolean getEnabled() {
90 return this.enabled.get();
91 }
92
93 @Override
94 protected void chore() {
95 try {
96 if (this.enabled.get()) {
97 scan();
98 } else {
99 LOG.warn("CatalogJanitor disabled! Not running scan.");
100 }
101 } catch (IOException e) {
102 LOG.warn("Failed scan of catalog table", e);
103 }
104 }
105
106
107
108
109
110
111
112
113 Triple<Integer, Map<HRegionInfo, Result>, Map<HRegionInfo, Result>> getMergedRegionsAndSplitParents()
114 throws IOException {
115 return getMergedRegionsAndSplitParents(null);
116 }
117
118
119
120
121
122
123
124
125
126
127
128 Triple<Integer, Map<HRegionInfo, Result>, Map<HRegionInfo, Result>> getMergedRegionsAndSplitParents(
129 final byte[] tableName) throws IOException {
130 final boolean isTableSpecified = (tableName != null && tableName.length != 0);
131
132 final AtomicInteger count = new AtomicInteger(0);
133
134
135 final Map<HRegionInfo, Result> splitParents =
136 new TreeMap<HRegionInfo, Result>(new SplitParentFirstComparator());
137 final Map<HRegionInfo, Result> mergedRegions = new TreeMap<HRegionInfo, Result>();
138
139 MetaReader.Visitor visitor = new MetaReader.Visitor() {
140 @Override
141 public boolean visit(Result r) throws IOException {
142 if (r == null || r.isEmpty()) return true;
143 count.incrementAndGet();
144 HRegionInfo info = HRegionInfo.getHRegionInfo(r);
145 if (info == null) return true;
146 if (isTableSpecified
147 && Bytes.compareTo(info.getTableName(), tableName) > 0) {
148
149 return false;
150 }
151 if (info.isSplitParent()) splitParents.put(info, r);
152 if (r.getValue(HConstants.CATALOG_FAMILY, HConstants.MERGEA_QUALIFIER) != null) {
153 mergedRegions.put(info, r);
154 }
155
156 return true;
157 }
158 };
159
160 byte[] startRow = (!isTableSpecified) ? HConstants.EMPTY_START_ROW
161 : HRegionInfo.createRegionName(tableName, HConstants.EMPTY_START_ROW,
162 HConstants.ZEROES, false);
163
164
165 MetaReader.fullScan(this.server.getCatalogTracker(), visitor, startRow);
166
167 return new Triple<Integer, Map<HRegionInfo, Result>, Map<HRegionInfo, Result>>(
168 count.get(), mergedRegions, splitParents);
169 }
170
171
172
173
174
175
176
177
178
179
180
181 boolean cleanMergeRegion(final HRegionInfo mergedRegion,
182 final HRegionInfo regionA, final HRegionInfo regionB) throws IOException {
183 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
184 Path rootdir = this.services.getMasterFileSystem().getRootDir();
185 Path tabledir = HTableDescriptor.getTableDir(rootdir,
186 mergedRegion.getTableName());
187 HTableDescriptor htd = getTableDescriptor(mergedRegion
188 .getTableNameAsString());
189 HRegionFileSystem regionFs = null;
190 try {
191 regionFs = HRegionFileSystem.openRegionFromFileSystem(
192 this.services.getConfiguration(), fs, tabledir, mergedRegion, true);
193 } catch (IOException e) {
194 LOG.warn("Merged region does not exist: " + mergedRegion.getEncodedName());
195 }
196 if (regionFs == null || !regionFs.hasReferences(htd)) {
197 LOG.debug("Deleting region " + regionA.getRegionNameAsString() + " and "
198 + regionB.getRegionNameAsString()
199 + " from fs because merged region no longer holds references");
200 HFileArchiver.archiveRegion(this.services.getConfiguration(), fs, regionA);
201 HFileArchiver.archiveRegion(this.services.getConfiguration(), fs, regionB);
202 MetaEditor.deleteMergeQualifiers(server.getCatalogTracker(), mergedRegion);
203 return true;
204 }
205 return false;
206 }
207
208
209
210
211
212
213
214 int scan() throws IOException {
215 try {
216 if (!alreadyRunning.compareAndSet(false, true)) {
217 return 0;
218 }
219 Triple<Integer, Map<HRegionInfo, Result>, Map<HRegionInfo, Result>> scanTriple =
220 getMergedRegionsAndSplitParents();
221 int count = scanTriple.getFirst();
222
223
224
225 int mergeCleaned = 0;
226 Map<HRegionInfo, Result> mergedRegions = scanTriple.getSecond();
227 for (Map.Entry<HRegionInfo, Result> e : mergedRegions.entrySet()) {
228 HRegionInfo regionA = HRegionInfo.getHRegionInfo(e.getValue(),
229 HConstants.MERGEA_QUALIFIER);
230 HRegionInfo regionB = HRegionInfo.getHRegionInfo(e.getValue(),
231 HConstants.MERGEB_QUALIFIER);
232 if (regionA == null || regionB == null) {
233 LOG.warn("Unexpected references regionA="
234 + (regionA == null ? "null" : regionA.getRegionNameAsString())
235 + ",regionB="
236 + (regionB == null ? "null" : regionB.getRegionNameAsString())
237 + " in merged region " + e.getKey().getRegionNameAsString());
238 } else {
239 if (cleanMergeRegion(e.getKey(), regionA, regionB)) {
240 mergeCleaned++;
241 }
242 }
243 }
244
245
246
247 Map<HRegionInfo, Result> splitParents = scanTriple.getThird();
248
249
250 int splitCleaned = 0;
251
252 HashSet<String> parentNotCleaned = new HashSet<String>();
253 for (Map.Entry<HRegionInfo, Result> e : splitParents.entrySet()) {
254 if (!parentNotCleaned.contains(e.getKey().getEncodedName()) &&
255 cleanParent(e.getKey(), e.getValue())) {
256 splitCleaned++;
257 } else {
258
259 PairOfSameType<HRegionInfo> daughters = HRegionInfo.getDaughterRegions(e.getValue());
260 parentNotCleaned.add(daughters.getFirst().getEncodedName());
261 parentNotCleaned.add(daughters.getSecond().getEncodedName());
262 }
263 }
264 if ((mergeCleaned + splitCleaned) != 0) {
265 LOG.info("Scanned " + count + " catalog row(s), gc'd " + mergeCleaned
266 + " unreferenced merged region(s) and " + splitCleaned
267 + " unreferenced parent region(s)");
268 } else if (LOG.isDebugEnabled()) {
269 LOG.debug("Scanned " + count + " catalog row(s), gc'd " + mergeCleaned
270 + " unreferenced merged region(s) and " + splitCleaned
271 + " unreferenced parent region(s)");
272 }
273 return mergeCleaned + splitCleaned;
274 } finally {
275 alreadyRunning.set(false);
276 }
277 }
278
279
280
281
282
283 static class SplitParentFirstComparator implements Comparator<HRegionInfo> {
284 @Override
285 public int compare(HRegionInfo left, HRegionInfo right) {
286
287
288 if (left == null) return -1;
289 if (right == null) return 1;
290
291 int result = Bytes.compareTo(left.getTableName(),
292 right.getTableName());
293 if (result != 0) return result;
294
295 result = Bytes.compareTo(left.getStartKey(), right.getStartKey());
296 if (result != 0) return result;
297
298 result = Bytes.compareTo(left.getEndKey(), right.getEndKey());
299 if (result != 0) {
300 if (left.getStartKey().length != 0
301 && left.getEndKey().length == 0) {
302 return -1;
303 }
304 if (right.getStartKey().length != 0
305 && right.getEndKey().length == 0) {
306 return 1;
307 }
308 return -result;
309 }
310 return result;
311 }
312 }
313
314
315
316
317
318
319
320
321
322
323 boolean cleanParent(final HRegionInfo parent, Result rowContent)
324 throws IOException {
325 boolean result = false;
326
327
328
329 if (rowContent.getValue(HConstants.CATALOG_FAMILY,
330 HConstants.MERGEA_QUALIFIER) != null) {
331
332 return result;
333 }
334
335 PairOfSameType<HRegionInfo> daughters = HRegionInfo.getDaughterRegions(rowContent);
336 Pair<Boolean, Boolean> a = checkDaughterInFs(parent, daughters.getFirst());
337 Pair<Boolean, Boolean> b = checkDaughterInFs(parent, daughters.getSecond());
338 if (hasNoReferences(a) && hasNoReferences(b)) {
339 LOG.debug("Deleting region " + parent.getRegionNameAsString() +
340 " because daughter splits no longer hold references");
341
342 removeDaughtersFromParent(parent);
343
344
345
346 if (this.services.getAssignmentManager() != null) {
347
348
349 this.services.getAssignmentManager().regionOffline(parent);
350 }
351 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
352 LOG.debug("Archiving parent region:" + parent);
353 HFileArchiver.archiveRegion(this.services.getConfiguration(), fs, parent);
354 MetaEditor.deleteRegion(this.server.getCatalogTracker(), parent);
355 result = true;
356 }
357 return result;
358 }
359
360
361
362
363
364
365
366 private boolean hasNoReferences(final Pair<Boolean, Boolean> p) {
367 return !p.getFirst() || !p.getSecond();
368 }
369
370
371
372
373
374
375 private void removeDaughtersFromParent(final HRegionInfo parent)
376 throws IOException {
377 MetaEditor.deleteDaughtersReferencesInParent(this.server.getCatalogTracker(), parent);
378 }
379
380
381
382
383
384
385
386
387
388
389
390 Pair<Boolean, Boolean> checkDaughterInFs(final HRegionInfo parent, final HRegionInfo daughter)
391 throws IOException {
392 if (daughter == null) {
393 return new Pair<Boolean, Boolean>(Boolean.FALSE, Boolean.FALSE);
394 }
395
396 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
397 Path rootdir = this.services.getMasterFileSystem().getRootDir();
398 Path tabledir = HTableDescriptor.getTableDir(rootdir, daughter.getTableName());
399
400 HRegionFileSystem regionFs = null;
401 try {
402 regionFs = HRegionFileSystem.openRegionFromFileSystem(
403 this.services.getConfiguration(), fs, tabledir, daughter, true);
404 } catch (IOException e) {
405 LOG.warn("Daughter region does not exist: " + daughter.getEncodedName());
406 return new Pair<Boolean, Boolean>(Boolean.FALSE, Boolean.FALSE);
407 }
408
409 boolean references = false;
410 HTableDescriptor parentDescriptor = getTableDescriptor(parent.getTableNameAsString());
411 for (HColumnDescriptor family: parentDescriptor.getFamilies()) {
412 if ((references = regionFs.hasReferences(family.getNameAsString()))) {
413 break;
414 }
415 }
416 return new Pair<Boolean, Boolean>(Boolean.TRUE, Boolean.valueOf(references));
417 }
418
419 private HTableDescriptor getTableDescriptor(final String tableName)
420 throws FileNotFoundException, IOException {
421 return this.services.getTableDescriptors().get(tableName);
422 }
423
424
425
426
427
428
429
430
431 public boolean cleanMergeQualifier(final HRegionInfo region)
432 throws IOException {
433
434
435 Pair<HRegionInfo, HRegionInfo> mergeRegions = MetaReader
436 .getRegionsFromMergeQualifier(this.services.getCatalogTracker(),
437 region.getRegionName());
438 if (mergeRegions == null
439 || (mergeRegions.getFirst() == null && mergeRegions.getSecond() == null)) {
440
441 return true;
442 }
443
444 if (mergeRegions.getFirst() == null || mergeRegions.getSecond() == null) {
445 LOG.error("Merged region " + region.getRegionNameAsString()
446 + " has only one merge qualifier in META.");
447 return false;
448 }
449 return cleanMergeRegion(region, mergeRegions.getFirst(),
450 mergeRegions.getSecond());
451 }
452 }