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.Map;
26 import java.util.TreeMap;
27 import java.util.concurrent.atomic.AtomicInteger;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.hadoop.fs.FileStatus;
32 import org.apache.hadoop.fs.FileSystem;
33 import org.apache.hadoop.fs.Path;
34 import org.apache.hadoop.fs.PathFilter;
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.TableExistsException;
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.HRegion;
46 import org.apache.hadoop.hbase.regionserver.Store;
47 import org.apache.hadoop.hbase.regionserver.StoreFile;
48 import org.apache.hadoop.hbase.util.Bytes;
49 import org.apache.hadoop.hbase.util.FSUtils;
50 import org.apache.hadoop.hbase.util.Pair;
51 import org.apache.hadoop.hbase.util.Writables;
52
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
104 void scan() throws IOException {
105
106 final AtomicInteger count = new AtomicInteger(0);
107
108
109 final Map<HRegionInfo, Result> splitParents =
110 new TreeMap<HRegionInfo, Result>(new SplitParentFirstComparator());
111
112 MetaReader.Visitor visitor = new MetaReader.Visitor() {
113 @Override
114 public boolean visit(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 MetaReader.fullScan(this.server.getCatalogTracker(), visitor);
126
127 int cleaned = 0;
128 for (Map.Entry<HRegionInfo, Result> e : splitParents.entrySet()) {
129 if (cleanParent(e.getKey(), e.getValue())) cleaned++;
130 }
131 if (cleaned != 0) {
132 LOG.info("Scanned " + count.get() + " catalog row(s) and gc'd " + cleaned +
133 " unreferenced parent region(s)");
134 } else if (LOG.isDebugEnabled()) {
135 LOG.debug("Scanned " + count.get() + " catalog row(s) and gc'd " + cleaned +
136 " unreferenced parent region(s)");
137 }
138 }
139
140
141
142
143
144 static class SplitParentFirstComparator implements Comparator<HRegionInfo> {
145 @Override
146 public int compare(HRegionInfo left, HRegionInfo right) {
147
148
149 if (left == null) return -1;
150 if (right == null) return 1;
151
152 int result = Bytes.compareTo(left.getTableName(),
153 right.getTableName());
154 if (result != 0) return result;
155
156 result = Bytes.compareTo(left.getStartKey(), right.getStartKey());
157 if (result != 0) return result;
158
159 result = Bytes.compareTo(left.getEndKey(), right.getEndKey());
160 if (result != 0) return -result;
161 return result;
162 }
163 }
164
165
166
167
168
169
170
171
172 static HRegionInfo getHRegionInfo(final Result result)
173 throws IOException {
174 byte [] bytes =
175 result.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
176 if (bytes == null) {
177 LOG.warn("REGIONINFO_QUALIFIER is empty in " + result);
178 return null;
179 }
180 return Writables.getHRegionInfo(bytes);
181 }
182
183
184
185
186
187
188
189
190
191
192
193 boolean cleanParent(final HRegionInfo parent, Result rowContent)
194 throws IOException {
195 boolean result = false;
196
197 HRegionInfo a_region = getDaughterRegionInfo(rowContent, HConstants.SPLITA_QUALIFIER);
198 HRegionInfo b_region = getDaughterRegionInfo(rowContent, HConstants.SPLITB_QUALIFIER);
199 Pair<Boolean, Boolean> a =
200 checkDaughterInFs(parent, a_region, HConstants.SPLITA_QUALIFIER);
201 Pair<Boolean, Boolean> b =
202 checkDaughterInFs(parent, b_region, HConstants.SPLITB_QUALIFIER);
203 if (hasNoReferences(a) && hasNoReferences(b)) {
204 LOG.debug("Deleting region " + parent.getRegionNameAsString() +
205 " because daughter splits no longer hold references");
206
207 removeDaughtersFromParent(parent);
208
209
210
211 if (this.services.getAssignmentManager() != null) {
212
213
214 this.services.getAssignmentManager().regionOffline(parent);
215 }
216 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
217 Path rootdir = this.services.getMasterFileSystem().getRootDir();
218 HRegion.deleteRegion(fs, rootdir, parent);
219 MetaEditor.deleteRegion(this.server.getCatalogTracker(), parent);
220 result = true;
221 }
222 return result;
223 }
224
225
226
227
228
229
230
231 private boolean hasNoReferences(final Pair<Boolean, Boolean> p) {
232 return !p.getFirst() || !p.getSecond();
233 }
234
235
236
237
238
239
240
241
242
243 private HRegionInfo getDaughterRegionInfo(final Result result,
244 final byte [] which)
245 throws IOException {
246 byte [] bytes = result.getValue(HConstants.CATALOG_FAMILY, which);
247 return Writables.getHRegionInfoOrNull(bytes);
248 }
249
250
251
252
253
254
255 private void removeDaughtersFromParent(final HRegionInfo parent)
256 throws IOException {
257 MetaEditor.deleteDaughtersReferencesInParent(this.server.getCatalogTracker(), parent);
258 }
259
260
261
262
263
264
265
266
267
268
269
270
271 Pair<Boolean, Boolean> checkDaughterInFs(final HRegionInfo parent,
272 final HRegionInfo split,
273 final byte [] qualifier)
274 throws IOException {
275 boolean references = false;
276 boolean exists = false;
277 if (split == null) {
278 return new Pair<Boolean, Boolean>(Boolean.FALSE, Boolean.FALSE);
279 }
280 FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
281 Path rootdir = this.services.getMasterFileSystem().getRootDir();
282 Path tabledir = new Path(rootdir, split.getTableNameAsString());
283 Path regiondir = new Path(tabledir, split.getEncodedName());
284 exists = fs.exists(regiondir);
285 if (!exists) {
286 LOG.warn("Daughter regiondir does not exist: " + regiondir.toString());
287 return new Pair<Boolean, Boolean>(exists, Boolean.FALSE);
288 }
289 HTableDescriptor parentDescriptor = getTableDescriptor(parent.getTableName());
290
291 for (HColumnDescriptor family: parentDescriptor.getFamilies()) {
292 Path p = Store.getStoreHomedir(tabledir, split.getEncodedName(),
293 family.getName());
294 if (!fs.exists(p)) continue;
295
296 FileStatus [] ps = FSUtils.listStatus(fs, p,
297 new PathFilter () {
298 public boolean accept(Path path) {
299 return StoreFile.isReference(path);
300 }
301 }
302 );
303
304 if (ps != null && ps.length > 0) {
305 references = true;
306 break;
307 }
308 }
309 return new Pair<Boolean, Boolean>(Boolean.valueOf(exists),
310 Boolean.valueOf(references));
311 }
312
313 private HTableDescriptor getTableDescriptor(byte[] tableName)
314 throws TableExistsException, FileNotFoundException, IOException {
315 return this.services.getTableDescriptors().get(Bytes.toString(tableName));
316 }
317 }