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.master;
21
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.util.Comparator;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.TreeMap;
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.FileStatus;
33 import org.apache.hadoop.fs.FileSystem;
34 import org.apache.hadoop.fs.Path;
35 import org.apache.hadoop.fs.PathFilter;
36 import org.apache.hadoop.hbase.Chore;
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.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.Store;
48 import org.apache.hadoop.hbase.regionserver.StoreFile;
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.Writables;
53
54
55
56
57
58 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 boolean enabled = true;
63
64 CatalogJanitor(final Server server, final MasterServices services) {
65 super(server.getServerName() + "-CatalogJanitor",
66 server.getConfiguration().getInt("hbase.catalogjanitor.interval", 300000),
67 server);
68 this.server = server;
69 this.services = services;
70 }
71
72 @Override
73 protected boolean initialChore() {
74 try {
75 if (this.enabled) scan();
76 } catch (IOException e) {
77 LOG.warn("Failed initial scan of catalog table", e);
78 return false;
79 }
80 return true;
81 }
82
83
84
85
86 public void setEnabled(final boolean enabled) {
87 this.enabled = enabled;
88 }
89
90 @Override
91 protected void chore() {
92 try {
93 scan();
94 } catch (IOException e) {
95 LOG.warn("Failed scan of catalog table", e);
96 }
97 }
98
99
100
101
102
103 Pair<Integer, Map<HRegionInfo, Result>> getSplitParents() throws IOException {
104
105 final AtomicInteger count = new AtomicInteger(0);
106
107
108 final Map<HRegionInfo, Result> splitParents =
109 new TreeMap<HRegionInfo, Result>(new SplitParentFirstComparator());
110
111
112 MetaScannerVisitor visitor = new MetaScanner.BlockingMetaScannerVisitor(server.getConfiguration()) {
113 @Override
114 public boolean processRowInternal(Result r) throws IOException {
115 if (r == null || r.isEmpty()) return true;
116 count.incrementAndGet();
117 HRegionInfo info = getHRegionInfo(r);
118 if (info == null) return true;
119 if (info.isSplitParent()) splitParents.put(info, r);
120
121 return true;
122 }
123 };
124
125
126 MetaScanner.metaScan(server.getConfiguration(), visitor);
127
128 return new Pair<Integer, Map<HRegionInfo, Result>>(count.get(), splitParents);
129 }
130
131
132
133
134
135
136 int scan() throws IOException {
137 Pair<Integer, Map<HRegionInfo, Result>> pair = getSplitParents();
138 int count = pair.getFirst();
139 Map<HRegionInfo, Result> splitParents = pair.getSecond();
140
141
142 int cleaned = 0;
143 HashSet<String> parentNotCleaned = new HashSet<String>();
144 for (Map.Entry<HRegionInfo, Result> e : splitParents.entrySet()) {
145 if (!parentNotCleaned.contains(e.getKey().getEncodedName()) && cleanParent(e.getKey(), e.getValue())) {
146 cleaned++;
147 } else {
148
149 parentNotCleaned.add(getDaughterRegionInfo(
150 e.getValue(), HConstants.SPLITA_QUALIFIER).getEncodedName());
151 parentNotCleaned.add(getDaughterRegionInfo(
152 e.getValue(), HConstants.SPLITB_QUALIFIER).getEncodedName());
153 }
154 }
155 if (cleaned != 0) {
156 LOG.info("Scanned " + count + " catalog row(s) and gc'd " + cleaned +
157 " unreferenced parent region(s)");
158 } else if (LOG.isDebugEnabled()) {
159 LOG.debug("Scanned " + count + " catalog row(s) and gc'd " + cleaned +
160 " unreferenced parent region(s)");
161 }
162 return cleaned;
163 }
164
165
166
167
168
169 static class SplitParentFirstComparator implements Comparator<HRegionInfo> {
170 Comparator<byte[]> rowEndKeyComparator = new Bytes.RowEndKeyComparator();
171 @Override
172 public int compare(HRegionInfo left, HRegionInfo right) {
173
174
175 if (left == null) return -1;
176 if (right == null) return 1;
177
178 int result = Bytes.compareTo(left.getTableName(),
179 right.getTableName());
180 if (result != 0) return result;
181
182 result = Bytes.compareTo(left.getStartKey(), right.getStartKey());
183 if (result != 0) return result;
184
185 result = rowEndKeyComparator.compare(left.getEndKey(), right.getEndKey());
186
187 return -result;
188 }
189 }
190
191
192
193
194
195
196
197
198 static HRegionInfo getHRegionInfo(final Result result)
199 throws IOException {
200 byte [] bytes =
201 result.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
202 if (bytes == null) {
203 LOG.warn("REGIONINFO_QUALIFIER is empty in " + result);
204 return null;
205 }
206 return Writables.getHRegionInfo(bytes);
207 }
208
209
210
211
212
213
214
215
216
217
218
219 boolean cleanParent(final HRegionInfo parent, Result rowContent)
220 throws IOException {
221 boolean result = false;
222
223 HRegionInfo a_region = getDaughterRegionInfo(rowContent, HConstants.SPLITA_QUALIFIER);
224 HRegionInfo b_region = getDaughterRegionInfo(rowContent, HConstants.SPLITB_QUALIFIER);
225 Pair<Boolean, Boolean> a =
226 checkDaughterInFs(parent, a_region, HConstants.SPLITA_QUALIFIER);
227 Pair<Boolean, Boolean> b =
228 checkDaughterInFs(parent, b_region, HConstants.SPLITB_QUALIFIER);
229 if (hasNoReferences(a) && hasNoReferences(b)) {
230 LOG.debug("Deleting region " + parent.getRegionNameAsString() +
231 " because daughter splits no longer hold references");
232
233
234
235 if (this.services.getAssignmentManager() != null) {
236
237
238 this.services.getAssignmentManager().regionOffline(parent);
239 }
240 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
241 HFileArchiver.archiveRegion(this.services.getConfiguration(), fs, parent);
242 MetaEditor.deleteRegion(this.server.getCatalogTracker(), parent);
243 result = true;
244 }
245 return result;
246 }
247
248
249
250
251
252
253
254 private boolean hasNoReferences(final Pair<Boolean, Boolean> p) {
255 return !p.getFirst() || !p.getSecond();
256 }
257
258
259
260
261
262
263
264
265
266 private HRegionInfo getDaughterRegionInfo(final Result result,
267 final byte [] which)
268 throws IOException {
269 byte [] bytes = result.getValue(HConstants.CATALOG_FAMILY, which);
270 return Writables.getHRegionInfoOrNull(bytes);
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284 Pair<Boolean, Boolean> checkDaughterInFs(final HRegionInfo parent,
285 final HRegionInfo split,
286 final byte [] qualifier)
287 throws IOException {
288 boolean references = false;
289 boolean exists = false;
290 if (split == null) {
291 return new Pair<Boolean, Boolean>(Boolean.FALSE, Boolean.FALSE);
292 }
293 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
294 Path rootdir = this.services.getMasterFileSystem().getRootDir();
295 Path tabledir = new Path(rootdir, split.getTableNameAsString());
296 Path regiondir = new Path(tabledir, split.getEncodedName());
297 exists = fs.exists(regiondir);
298 if (!exists) {
299 LOG.warn("Daughter regiondir does not exist: " + regiondir.toString());
300 return new Pair<Boolean, Boolean>(exists, Boolean.FALSE);
301 }
302 HTableDescriptor parentDescriptor = getTableDescriptor(parent.getTableName());
303
304 for (HColumnDescriptor family: parentDescriptor.getFamilies()) {
305 Path p = Store.getStoreHomedir(tabledir, split.getEncodedName(),
306 family.getName());
307 if (!fs.exists(p)) continue;
308
309 FileStatus [] ps = FSUtils.listStatus(fs, p,
310 new PathFilter () {
311 public boolean accept(Path path) {
312 return StoreFile.isReference(path);
313 }
314 }
315 );
316
317 if (ps != null && ps.length > 0) {
318 references = true;
319 break;
320 }
321 }
322 return new Pair<Boolean, Boolean>(Boolean.valueOf(exists),
323 Boolean.valueOf(references));
324 }
325
326 private HTableDescriptor getTableDescriptor(byte[] tableName)
327 throws FileNotFoundException, IOException {
328 return this.services.getTableDescriptors().get(Bytes.toString(tableName));
329 }
330 }