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