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.Map;
24  import java.util.TreeMap;
25  import java.util.concurrent.atomic.AtomicInteger;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.fs.FileStatus;
30  import org.apache.hadoop.fs.FileSystem;
31  import org.apache.hadoop.fs.Path;
32  import org.apache.hadoop.fs.PathFilter;
33  import org.apache.hadoop.hbase.Chore;
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.Server;
38  import org.apache.hadoop.hbase.catalog.MetaEditor;
39  import org.apache.hadoop.hbase.catalog.MetaReader;
40  import org.apache.hadoop.hbase.client.Result;
41  import org.apache.hadoop.hbase.regionserver.HRegion;
42  import org.apache.hadoop.hbase.regionserver.Store;
43  import org.apache.hadoop.hbase.regionserver.StoreFile;
44  import org.apache.hadoop.hbase.util.Writables;
45  
46  /**
47   * A janitor for the catalog tables.  Scans the <code>.META.</code> catalog
48   * table on a period looking for unused regions to garbage collect.
49   */
50  class CatalogJanitor extends Chore {
51    private static final Log LOG = LogFactory.getLog(CatalogJanitor.class.getName());
52    private final Server server;
53    private final MasterServices services;
54    private boolean enabled = true;
55  
56    CatalogJanitor(final Server server, final MasterServices services) {
57      super(server.getServerName() + "-CatalogJanitor",
58        server.getConfiguration().getInt("hbase.catalogjanitor.interval", 300000),
59        server);
60      this.server = server;
61      this.services = services;
62    }
63  
64    @Override
65    protected boolean initialChore() {
66      try {
67        if (this.enabled) scan();
68      } catch (IOException e) {
69        LOG.warn("Failed initial scan of catalog table", e);
70        return false;
71      }
72      return true;
73    }
74  
75    /**
76     * @param enabled
77     */
78    public void setEnabled(final boolean enabled) {
79      this.enabled = enabled;
80    }
81  
82    @Override
83    protected void chore() {
84      try {
85        scan();
86      } catch (IOException e) {
87        LOG.warn("Failed scan of catalog table", e);
88      }
89    }
90  
91    /**
92     * Run janitorial scan of catalog <code>.META.</code> table looking for
93     * garbage to collect.
94     * @throws IOException
95     */
96    void scan() throws IOException {
97      // TODO: Only works with single .META. region currently.  Fix.
98      final AtomicInteger count = new AtomicInteger(0);
99      // Keep Map of found split parents.  There are candidates for cleanup.
100     final Map<HRegionInfo, Result> splitParents =
101       new TreeMap<HRegionInfo, Result>();
102     // This visitor collects split parents and counts rows in the .META. table
103     MetaReader.Visitor visitor = new MetaReader.Visitor() {
104       @Override
105       public boolean visit(Result r) throws IOException {
106         if (r == null || r.isEmpty()) return true;
107         count.incrementAndGet();
108         HRegionInfo info = getHRegionInfo(r);
109         if (info == null) return true; // Keep scanning
110         if (info.isSplitParent()) splitParents.put(info, r);
111         // Returning true means "keep scanning"
112         return true;
113       }
114     };
115     // Run full scan of .META. catalog table passing in our custom visitor
116     MetaReader.fullScan(this.server.getCatalogTracker(), visitor);
117     // Now work on our list of found parents. See if any we can clean up.
118     int cleaned = 0;
119     for (Map.Entry<HRegionInfo, Result> e : splitParents.entrySet()) {
120       if (cleanParent(e.getKey(), e.getValue())) cleaned++;
121     }
122     if (cleaned != 0) {
123       LOG.info("Scanned " + count.get() + " catalog row(s) and gc'd " + cleaned +
124         " unreferenced parent region(s)");
125     } else if (LOG.isDebugEnabled()) {
126       LOG.debug("Scanned " + count.get() + " catalog row(s) and gc'd " + cleaned +
127       " unreferenced parent region(s)");
128     }
129   }
130 
131   /**
132    * Get HRegionInfo from passed Map of row values.
133    * @param result Map to do lookup in.
134    * @return Null if not found (and logs fact that expected COL_REGIONINFO
135    * was missing) else deserialized {@link HRegionInfo}
136    * @throws IOException
137    */
138   static HRegionInfo getHRegionInfo(final Result result)
139   throws IOException {
140     byte [] bytes =
141       result.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
142     if (bytes == null) {
143       LOG.warn("REGIONINFO_QUALIFIER is empty in " + result);
144       return null;
145     }
146     return Writables.getHRegionInfo(bytes);
147   }
148 
149   /**
150    * If daughters no longer hold reference to the parents, delete the parent.
151    * @param server HRegionInterface of meta server to talk to 
152    * @param parent HRegionInfo of split offlined parent
153    * @param rowContent Content of <code>parent</code> row in
154    * <code>metaRegionName</code>
155    * @return True if we removed <code>parent</code> from meta table and from
156    * the filesystem.
157    * @throws IOException
158    */
159   boolean cleanParent(final HRegionInfo parent,
160     Result rowContent)
161   throws IOException {
162     boolean result = false;
163     // Run checks on each daughter split.
164     boolean hasReferencesA =
165       checkDaughter(parent, rowContent, HConstants.SPLITA_QUALIFIER);
166     boolean hasReferencesB =
167       checkDaughter(parent, rowContent, HConstants.SPLITB_QUALIFIER);
168     if (!hasReferencesA && !hasReferencesB) {
169       LOG.debug("Deleting region " + parent.getRegionNameAsString() +
170         " because daughter splits no longer hold references");
171       // This latter regionOffline should not be necessary but is done for now
172       // until we let go of regionserver to master heartbeats.  See HBASE-3368.
173       if (this.services.getAssignmentManager() != null) {
174         // The mock used in testing catalogjanitor returns null for getAssignmnetManager.
175         // Allow for null result out of getAssignmentManager.
176         this.services.getAssignmentManager().regionOffline(parent);
177       }
178       FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
179       Path rootdir = this.services.getMasterFileSystem().getRootDir();
180       HRegion.deleteRegion(fs, rootdir, parent);
181       MetaEditor.deleteRegion(this.server.getCatalogTracker(), parent);
182       result = true;
183     }
184     return result;
185   }
186 
187   
188   /**
189    * See if the passed daughter has references in the filesystem to the parent
190    * and if not, remove the note of daughter region in the parent row: its
191    * column info:splitA or info:splitB.
192    * @param parent
193    * @param rowContent
194    * @param qualifier
195    * @return True if this daughter still has references to the parent.
196    * @throws IOException
197    */
198   boolean checkDaughter(final HRegionInfo parent,
199     final Result rowContent, final byte [] qualifier)
200   throws IOException {
201     HRegionInfo hri = getDaughterRegionInfo(rowContent, qualifier);
202     return hasReferences(parent, rowContent, hri, qualifier);
203   }
204 
205   /**
206    * Get daughter HRegionInfo out of parent info:splitA/info:splitB columns.
207    * @param result
208    * @param which Whether "info:splitA" or "info:splitB" column
209    * @return Deserialized content of the info:splitA or info:splitB as a
210    * HRegionInfo
211    * @throws IOException
212    */
213   private HRegionInfo getDaughterRegionInfo(final Result result,
214     final byte [] which)
215   throws IOException {
216     byte [] bytes = result.getValue(HConstants.CATALOG_FAMILY, which);
217     return Writables.getHRegionInfoOrNull(bytes);
218   }
219 
220   /**
221    * Remove mention of daughter from parent row.
222    * parent row.
223    * @param metaRegionName
224    * @param srvr
225    * @param parent
226    * @param split
227    * @param qualifier
228    * @throws IOException
229    */
230   private void removeDaughterFromParent(final HRegionInfo parent,
231     final HRegionInfo split, final byte [] qualifier)
232   throws IOException {
233     MetaEditor.deleteDaughterReferenceInParent(this.server.getCatalogTracker(),
234       parent, qualifier, split);
235   }
236 
237   /**
238    * Checks if a daughter region -- either splitA or splitB -- still holds
239    * references to parent.  If not, removes reference to the split from
240    * the parent meta region row so we don't check it any more.
241    * @param parent Parent region name. 
242    * @param rowContent Keyed content of the parent row in meta region.
243    * @param split Which column family.
244    * @param qualifier Which of the daughters to look at, splitA or splitB.
245    * @return True if still has references to parent.
246    * @throws IOException
247    */
248   boolean hasReferences(final HRegionInfo parent,
249     final Result rowContent, final HRegionInfo split,
250     final byte [] qualifier)
251   throws IOException {
252     boolean result = false;
253     if (split == null)  return result;
254     FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
255     Path rootdir = this.services.getMasterFileSystem().getRootDir();
256     Path tabledir = new Path(rootdir, split.getTableDesc().getNameAsString());
257     for (HColumnDescriptor family: split.getTableDesc().getFamilies()) {
258       Path p = Store.getStoreHomedir(tabledir, split.getEncodedName(),
259         family.getName());
260       // Look for reference files.  Call listStatus with anonymous instance of PathFilter.
261       FileStatus [] ps = fs.listStatus(p,
262           new PathFilter () {
263             public boolean accept(Path path) {
264               return StoreFile.isReference(path);
265             }
266           }
267       );
268 
269       if (ps != null && ps.length > 0) {
270         result = true;
271         break;
272       }
273     }
274     if (!result) {
275       removeDaughterFromParent(parent, split, qualifier);
276     }
277     return result;
278   }
279 }