View Javadoc

1   /**
2    * Copyright 2008 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.master;
21  
22  import java.io.IOException;
23  import java.util.Comparator;
24  import java.util.Map;
25  import java.util.TreeMap;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.fs.FileStatus;
31  import org.apache.hadoop.fs.FileSystem;
32  import org.apache.hadoop.fs.Path;
33  import org.apache.hadoop.fs.PathFilter;
34  import org.apache.hadoop.hbase.Chore;
35  import org.apache.hadoop.hbase.HColumnDescriptor;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.Server;
39  import org.apache.hadoop.hbase.catalog.MetaEditor;
40  import org.apache.hadoop.hbase.catalog.MetaReader;
41  import org.apache.hadoop.hbase.client.Result;
42  import org.apache.hadoop.hbase.regionserver.HRegion;
43  import org.apache.hadoop.hbase.regionserver.Store;
44  import org.apache.hadoop.hbase.regionserver.StoreFile;
45  import org.apache.hadoop.hbase.util.Bytes;
46  import org.apache.hadoop.hbase.util.Pair;
47  import org.apache.hadoop.hbase.util.Writables;
48  
49  /**
50   * A janitor for the catalog tables.  Scans the <code>.META.</code> catalog
51   * table on a period looking for unused regions to garbage collect.
52   */
53  class CatalogJanitor extends Chore {
54    private static final Log LOG = LogFactory.getLog(CatalogJanitor.class.getName());
55    private final Server server;
56    private final MasterServices services;
57    private boolean enabled = true;
58  
59    CatalogJanitor(final Server server, final MasterServices services) {
60      super(server.getServerName() + "-CatalogJanitor",
61        server.getConfiguration().getInt("hbase.catalogjanitor.interval", 300000),
62        server);
63      this.server = server;
64      this.services = services;
65    }
66  
67    @Override
68    protected boolean initialChore() {
69      try {
70        if (this.enabled) scan();
71      } catch (IOException e) {
72        LOG.warn("Failed initial scan of catalog table", e);
73        return false;
74      }
75      return true;
76    }
77  
78    /**
79     * @param enabled
80     */
81    public void setEnabled(final boolean enabled) {
82      this.enabled = enabled;
83    }
84  
85    @Override
86    protected void chore() {
87      try {
88        scan();
89      } catch (IOException e) {
90        LOG.warn("Failed scan of catalog table", e);
91      }
92    }
93  
94    /**
95     * Run janitorial scan of catalog <code>.META.</code> table looking for
96     * garbage to collect.
97     * @throws IOException
98     */
99    void scan() throws IOException {
100     // TODO: Only works with single .META. region currently.  Fix.
101     final AtomicInteger count = new AtomicInteger(0);
102     // Keep Map of found split parents.  There are candidates for cleanup.
103     // Use a comparator that has split parents come before its daughters.
104     final Map<HRegionInfo, Result> splitParents =
105       new TreeMap<HRegionInfo, Result>(new Comparator<HRegionInfo> () {
106         @Override
107         public int compare(HRegionInfo left, HRegionInfo right) {
108           // This comparator differs from the one HRegionInfo in that it sorts
109           // parent before daughters.
110           if (left == null) return -1;
111           if (right == null) return 1;
112           // Same table name.
113           int result = Bytes.compareTo(left.getTableDesc().getName(),
114             right.getTableDesc().getName());
115           if (result != 0) return result;
116           // Compare start keys.
117           result = Bytes.compareTo(left.getStartKey(), right.getStartKey());
118           if (result != 0) return result;
119           // Compare end keys.
120           result = Bytes.compareTo(left.getEndKey(), right.getEndKey());
121           if (result != 0) return -result; // Flip the result so parent comes first.
122           return result;
123         }
124       });
125     // This visitor collects split parents and counts rows in the .META. table
126     MetaReader.Visitor visitor = new MetaReader.Visitor() {
127       @Override
128       public boolean visit(Result r) throws IOException {
129         if (r == null || r.isEmpty()) return true;
130         count.incrementAndGet();
131         HRegionInfo info = getHRegionInfo(r);
132         if (info == null) return true; // Keep scanning
133         if (info.isSplitParent()) splitParents.put(info, r);
134         // Returning true means "keep scanning"
135         return true;
136       }
137     };
138     // Run full scan of .META. catalog table passing in our custom visitor
139     MetaReader.fullScan(this.server.getCatalogTracker(), visitor);
140     // Now work on our list of found parents. See if any we can clean up.
141     int cleaned = 0;
142     for (Map.Entry<HRegionInfo, Result> e : splitParents.entrySet()) {
143       if (cleanParent(e.getKey(), e.getValue())) cleaned++;
144     }
145     if (cleaned != 0) {
146       LOG.info("Scanned " + count.get() + " catalog row(s) and gc'd " + cleaned +
147         " unreferenced parent region(s)");
148     } else if (LOG.isDebugEnabled()) {
149       LOG.debug("Scanned " + count.get() + " catalog row(s) and gc'd " + cleaned +
150       " unreferenced parent region(s)");
151     }
152   }
153 
154   /**
155    * Get HRegionInfo from passed Map of row values.
156    * @param result Map to do lookup in.
157    * @return Null if not found (and logs fact that expected COL_REGIONINFO
158    * was missing) else deserialized {@link HRegionInfo}
159    * @throws IOException
160    */
161   static HRegionInfo getHRegionInfo(final Result result)
162   throws IOException {
163     byte [] bytes =
164       result.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
165     if (bytes == null) {
166       LOG.warn("REGIONINFO_QUALIFIER is empty in " + result);
167       return null;
168     }
169     return Writables.getHRegionInfo(bytes);
170   }
171 
172   /**
173    * If daughters no longer hold reference to the parents, delete the parent.
174    * @param server HRegionInterface of meta server to talk to 
175    * @param parent HRegionInfo of split offlined parent
176    * @param rowContent Content of <code>parent</code> row in
177    * <code>metaRegionName</code>
178    * @return True if we removed <code>parent</code> from meta table and from
179    * the filesystem.
180    * @throws IOException
181    */
182   boolean cleanParent(final HRegionInfo parent, Result rowContent)
183   throws IOException {
184     boolean result = false;
185     // Run checks on each daughter split.
186     Pair<Boolean, Boolean> a =
187       checkDaughter(parent, rowContent, HConstants.SPLITA_QUALIFIER);
188     Pair<Boolean, Boolean> b =
189       checkDaughter(parent, rowContent, HConstants.SPLITB_QUALIFIER);
190     if ((a.getFirst() && !a.getSecond()) && (b.getFirst() && !b.getSecond())) {
191       LOG.debug("Deleting region " + parent.getRegionNameAsString() +
192         " because daughter splits no longer hold references");
193       // This latter regionOffline should not be necessary but is done for now
194       // until we let go of regionserver to master heartbeats.  See HBASE-3368.
195       if (this.services.getAssignmentManager() != null) {
196         // The mock used in testing catalogjanitor returns null for getAssignmnetManager.
197         // Allow for null result out of getAssignmentManager.
198         this.services.getAssignmentManager().regionOffline(parent);
199       }
200       FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
201       Path rootdir = this.services.getMasterFileSystem().getRootDir();
202       HRegion.deleteRegion(fs, rootdir, parent);
203       MetaEditor.deleteRegion(this.server.getCatalogTracker(), parent);
204       result = true;
205     }
206     return result;
207   }
208 
209   
210   /**
211    * See if the passed daughter has references in the filesystem to the parent
212    * and if not, remove the note of daughter region in the parent row: its
213    * column info:splitA or info:splitB.
214    * @param parent
215    * @param rowContent
216    * @param qualifier
217    * @return A pair where the first boolean says whether or not the daughter
218    * region directory exists in the filesystem and then the second boolean says
219    * whether the daughter has references to the parent.
220    * @throws IOException
221    */
222   Pair<Boolean, Boolean> checkDaughter(final HRegionInfo parent,
223     final Result rowContent, final byte [] qualifier)
224   throws IOException {
225     HRegionInfo hri = getDaughterRegionInfo(rowContent, qualifier);
226     Pair<Boolean, Boolean> result =
227       checkDaughterInFs(parent, rowContent, hri, qualifier);
228     if (result.getFirst() && !result.getSecond()) {
229       // Remove daughter from the parent IFF the daughter region exists in FS.
230       // If there is no daughter region in the filesystem, must be because of
231       // a failed split.  The ServerShutdownHandler will do the fixup.  Don't
232       // do any deletes in here that could intefere with ServerShutdownHandler
233       // fixup
234       removeDaughterFromParent(parent, hri, qualifier);
235     }
236     return result;
237   }
238 
239   /**
240    * Get daughter HRegionInfo out of parent info:splitA/info:splitB columns.
241    * @param result
242    * @param which Whether "info:splitA" or "info:splitB" column
243    * @return Deserialized content of the info:splitA or info:splitB as a
244    * HRegionInfo
245    * @throws IOException
246    */
247   private HRegionInfo getDaughterRegionInfo(final Result result,
248     final byte [] which)
249   throws IOException {
250     byte [] bytes = result.getValue(HConstants.CATALOG_FAMILY, which);
251     return Writables.getHRegionInfoOrNull(bytes);
252   }
253 
254   /**
255    * Remove mention of daughter from parent row.
256    * parent row.
257    * @param metaRegionName
258    * @param srvr
259    * @param parent
260    * @param split
261    * @param qualifier
262    * @throws IOException
263    */
264   private void removeDaughterFromParent(final HRegionInfo parent,
265     final HRegionInfo split, final byte [] qualifier)
266   throws IOException {
267     MetaEditor.deleteDaughterReferenceInParent(this.server.getCatalogTracker(),
268       parent, qualifier, split);
269   }
270 
271   /**
272    * Checks if a daughter region -- either splitA or splitB -- still holds
273    * references to parent.
274    * @param parent Parent region name. 
275    * @param rowContent Keyed content of the parent row in meta region.
276    * @param split Which column family.
277    * @param qualifier Which of the daughters to look at, splitA or splitB.
278    * @return A pair where the first boolean says whether or not the daughter
279    * region directory exists in the filesystem and then the second boolean says
280    * whether the daughter has references to the parent.
281    * @throws IOException
282    */
283   Pair<Boolean, Boolean> checkDaughterInFs(final HRegionInfo parent,
284     final Result rowContent, final HRegionInfo split,
285     final byte [] qualifier)
286   throws IOException {
287     boolean references = false;
288     boolean exists = false;
289     if (split == null)  {
290       return new Pair<Boolean, Boolean>(Boolean.FALSE, Boolean.FALSE);
291     }
292     FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
293     Path rootdir = this.services.getMasterFileSystem().getRootDir();
294     Path tabledir = new Path(rootdir, split.getTableDesc().getNameAsString());
295     Path regiondir = new Path(tabledir, split.getEncodedName());
296     exists = fs.exists(regiondir);
297     if (!exists) {
298       LOG.warn("Daughter regiondir does not exist: " + regiondir.toString());
299       return new Pair<Boolean, Boolean>(exists, Boolean.FALSE);
300     }
301     for (HColumnDescriptor family: split.getTableDesc().getFamilies()) {
302       Path p = Store.getStoreHomedir(tabledir, split.getEncodedName(),
303         family.getName());
304       // Look for reference files.  Call listStatus with anonymous instance of PathFilter.
305       FileStatus [] ps = fs.listStatus(p,
306           new PathFilter () {
307             public boolean accept(Path path) {
308               return StoreFile.isReference(path);
309             }
310           }
311       );
312 
313       if (ps != null && ps.length > 0) {
314         references = true;
315         break;
316       }
317     }
318     return new Pair<Boolean, Boolean>(Boolean.valueOf(exists),
319       Boolean.valueOf(references));
320   }
321 }