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