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.MetaScanner;
45 import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
46 import org.apache.hadoop.hbase.client.Result;
47 import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
48 import org.apache.hadoop.hbase.util.Bytes;
49 import org.apache.hadoop.hbase.util.Pair;
50 import org.apache.hadoop.hbase.util.PairOfSameType;
51 import org.apache.hadoop.hbase.util.Triple;
52
53
54
55
56
57 @InterfaceAudience.Private
58 public class CatalogJanitor extends Chore {
59 private static final Log LOG = LogFactory.getLog(CatalogJanitor.class.getName());
60 private final Server server;
61 private final MasterServices services;
62 private AtomicBoolean enabled = new AtomicBoolean(true);
63 private AtomicBoolean alreadyRunning = new AtomicBoolean(false);
64
65 CatalogJanitor(final Server server, final MasterServices services) {
66 super(server.getServerName() + "-CatalogJanitor",
67 server.getConfiguration().getInt("hbase.catalogjanitor.interval", 300000),
68 server);
69 this.server = server;
70 this.services = services;
71 }
72
73 @Override
74 protected boolean initialChore() {
75 try {
76 if (this.enabled.get()) scan();
77 } catch (IOException e) {
78 LOG.warn("Failed initial scan of catalog table", e);
79 return false;
80 }
81 return true;
82 }
83
84
85
86
87 public boolean setEnabled(final boolean enabled) {
88 return this.enabled.getAndSet(enabled);
89 }
90
91 boolean getEnabled() {
92 return this.enabled.get();
93 }
94
95 @Override
96 protected void chore() {
97 try {
98 if (this.enabled.get()) {
99 scan();
100 } else {
101 LOG.warn("CatalogJanitor disabled! Not running scan.");
102 }
103 } catch (IOException e) {
104 LOG.warn("Failed scan of catalog table", e);
105 }
106 }
107
108
109
110
111
112
113
114
115 Triple<Integer, Map<HRegionInfo, Result>, Map<HRegionInfo, Result>> getMergedRegionsAndSplitParents()
116 throws IOException {
117 return getMergedRegionsAndSplitParents(null);
118 }
119
120
121
122
123
124
125
126
127
128
129
130 Triple<Integer, Map<HRegionInfo, Result>, Map<HRegionInfo, Result>> getMergedRegionsAndSplitParents(
131 final byte[] tableName) throws IOException {
132 final boolean isTableSpecified = (tableName != null && tableName.length != 0);
133
134 final AtomicInteger count = new AtomicInteger(0);
135
136
137 final Map<HRegionInfo, Result> splitParents =
138 new TreeMap<HRegionInfo, Result>(new SplitParentFirstComparator());
139 final Map<HRegionInfo, Result> mergedRegions = new TreeMap<HRegionInfo, Result>();
140
141
142 MetaScannerVisitor visitor = new MetaScanner.MetaScannerVisitorBase() {
143 @Override
144 public boolean processRow(Result r) throws IOException {
145 if (r == null || r.isEmpty()) return true;
146 count.incrementAndGet();
147 HRegionInfo info = HRegionInfo.getHRegionInfo(r);
148 if (info == null) return true;
149 if (isTableSpecified
150 && Bytes.compareTo(info.getTableName(), tableName) > 0) {
151
152 return false;
153 }
154 if (info.isSplitParent()) splitParents.put(info, r);
155 if (r.getValue(HConstants.CATALOG_FAMILY, HConstants.MERGEA_QUALIFIER) != null) {
156 mergedRegions.put(info, r);
157 }
158
159 return true;
160 }
161 };
162
163
164
165 MetaScanner.metaScan(server.getConfiguration(), visitor, tableName);
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 Comparator<byte[]> rowEndKeyComparator = new Bytes.RowEndKeyComparator();
285 @Override
286 public int compare(HRegionInfo left, HRegionInfo right) {
287
288
289 if (left == null) return -1;
290 if (right == null) return 1;
291
292 int result = Bytes.compareTo(left.getTableName(),
293 right.getTableName());
294 if (result != 0) return result;
295
296 result = Bytes.compareTo(left.getStartKey(), right.getStartKey());
297 if (result != 0) return result;
298
299 result = rowEndKeyComparator.compare(left.getEndKey(), right.getEndKey());
300
301 return -result;
302 }
303 }
304
305
306
307
308
309
310
311
312
313
314 boolean cleanParent(final HRegionInfo parent, Result rowContent)
315 throws IOException {
316 boolean result = false;
317
318
319
320 if (rowContent.getValue(HConstants.CATALOG_FAMILY,
321 HConstants.MERGEA_QUALIFIER) != null) {
322
323 return result;
324 }
325
326 PairOfSameType<HRegionInfo> daughters = HRegionInfo.getDaughterRegions(rowContent);
327 Pair<Boolean, Boolean> a = checkDaughterInFs(parent, daughters.getFirst());
328 Pair<Boolean, Boolean> b = checkDaughterInFs(parent, daughters.getSecond());
329 if (hasNoReferences(a) && hasNoReferences(b)) {
330 LOG.debug("Deleting region " + parent.getRegionNameAsString() +
331 " because daughter splits no longer hold references");
332
333
334
335 if (this.services.getAssignmentManager() != null) {
336
337
338 this.services.getAssignmentManager().regionOffline(parent);
339 }
340 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
341 LOG.debug("Archiving parent region:" + parent);
342 HFileArchiver.archiveRegion(this.services.getConfiguration(), fs, parent);
343 MetaEditor.deleteRegion(this.server.getCatalogTracker(), parent);
344 result = true;
345 }
346 return result;
347 }
348
349
350
351
352
353
354
355 private boolean hasNoReferences(final Pair<Boolean, Boolean> p) {
356 return !p.getFirst() || !p.getSecond();
357 }
358
359
360
361
362
363
364
365
366
367
368
369 Pair<Boolean, Boolean> checkDaughterInFs(final HRegionInfo parent, final HRegionInfo daughter)
370 throws IOException {
371 if (daughter == null) {
372 return new Pair<Boolean, Boolean>(Boolean.FALSE, Boolean.FALSE);
373 }
374
375 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
376 Path rootdir = this.services.getMasterFileSystem().getRootDir();
377 Path tabledir = HTableDescriptor.getTableDir(rootdir, daughter.getTableName());
378
379 HRegionFileSystem regionFs = null;
380 try {
381 regionFs = HRegionFileSystem.openRegionFromFileSystem(
382 this.services.getConfiguration(), fs, tabledir, daughter, true);
383 } catch (IOException e) {
384 LOG.warn("Daughter region does not exist: " + daughter.getEncodedName());
385 return new Pair<Boolean, Boolean>(Boolean.FALSE, Boolean.FALSE);
386 }
387
388 boolean references = false;
389 HTableDescriptor parentDescriptor = getTableDescriptor(parent.getTableNameAsString());
390 for (HColumnDescriptor family: parentDescriptor.getFamilies()) {
391 if ((references = regionFs.hasReferences(family.getNameAsString()))) {
392 break;
393 }
394 }
395 return new Pair<Boolean, Boolean>(Boolean.TRUE, Boolean.valueOf(references));
396 }
397
398 private HTableDescriptor getTableDescriptor(final String tableName)
399 throws FileNotFoundException, IOException {
400 return this.services.getTableDescriptors().get(tableName);
401 }
402
403
404
405
406
407
408
409
410 public boolean cleanMergeQualifier(final HRegionInfo region)
411 throws IOException {
412
413
414 Pair<HRegionInfo, HRegionInfo> mergeRegions = MetaReader
415 .getRegionsFromMergeQualifier(this.services.getCatalogTracker(),
416 region.getRegionName());
417 if (mergeRegions == null
418 || (mergeRegions.getFirst() == null && mergeRegions.getSecond() == null)) {
419
420 return true;
421 }
422
423 if (mergeRegions.getFirst() == null || mergeRegions.getSecond() == null) {
424 LOG.error("Merged region " + region.getRegionNameAsString()
425 + " has only one merge qualifier in META.");
426 return false;
427 }
428 return cleanMergeRegion(region, mergeRegions.getFirst(),
429 mergeRegions.getSecond());
430 }
431 }