Author: stack
Date: Tue Sep 13 18:36:54 2011
New Revision: 1170291

URL: http://svn.apache.org/viewvc?rev=1170291&view=rev
Log:
HBASE-4238 CatalogJanitor can clear a daughter that split before processing its 
parent

Modified:
    hbase/trunk/CHANGES.txt
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java
    
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/handler/ServerShutdownHandler.java
    
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java
    
hbase/trunk/src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java

Modified: hbase/trunk/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/hbase/trunk/CHANGES.txt?rev=1170291&r1=1170290&r2=1170291&view=diff
==============================================================================
--- hbase/trunk/CHANGES.txt (original)
+++ hbase/trunk/CHANGES.txt Tue Sep 13 18:36:54 2011
@@ -570,6 +570,8 @@ Release 0.90.5 - Unreleased
    HBASE-4180  HBase should check the isSecurityEnabled flag before login
    HBASE-4325  Improve error message when using STARTROW for meta scans
                (Jonathan Hsieh)
+   HBASE-4238  CatalogJanitor can clear a daughter that split before
+               processing its parent
 
   IMPROVEMENT
    HBASE-4205  Enhance HTable javadoc (Eric Charles)

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java?rev=1170291&r1=1170290&r2=1170291&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java 
(original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java 
Tue Sep 13 18:36:54 2011
@@ -34,9 +34,9 @@ import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.PathFilter;
 import org.apache.hadoop.hbase.Chore;
 import org.apache.hadoop.hbase.HColumnDescriptor;
-import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.Server;
 import org.apache.hadoop.hbase.TableExistsException;
 import org.apache.hadoop.hbase.catalog.MetaEditor;
@@ -47,7 +47,6 @@ import org.apache.hadoop.hbase.regionser
 import org.apache.hadoop.hbase.regionserver.StoreFile;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Pair;
-import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Writables;
 
 
@@ -107,26 +106,7 @@ class CatalogJanitor extends Chore {
     // Keep Map of found split parents.  There are candidates for cleanup.
     // Use a comparator that has split parents come before its daughters.
     final Map<HRegionInfo, Result> splitParents =
-      new TreeMap<HRegionInfo, Result>(new Comparator<HRegionInfo> () {
-        @Override
-        public int compare(HRegionInfo left, HRegionInfo right) {
-          // This comparator differs from the one HRegionInfo in that it sorts
-          // parent before daughters.
-          if (left == null) return -1;
-          if (right == null) return 1;
-          // Same table name.
-          int result = Bytes.compareTo(left.getTableName(),
-            right.getTableName());
-          if (result != 0) return result;
-          // Compare start keys.
-          result = Bytes.compareTo(left.getStartKey(), right.getStartKey());
-          if (result != 0) return result;
-          // Compare end keys.
-          result = Bytes.compareTo(left.getEndKey(), right.getEndKey());
-          if (result != 0) return -result; // Flip the result so parent comes 
first.
-          return result;
-        }
-      });
+      new TreeMap<HRegionInfo, Result>(new SplitParentFirstComparator());
     // This visitor collects split parents and counts rows in the .META. table
     MetaReader.Visitor visitor = new MetaReader.Visitor() {
       @Override
@@ -157,6 +137,31 @@ class CatalogJanitor extends Chore {
   }
 
   /**
+   * Compare HRegionInfos in a way that has split parents sort BEFORE their
+   * daughters.
+   */
+  static class SplitParentFirstComparator implements Comparator<HRegionInfo> {
+    @Override
+    public int compare(HRegionInfo left, HRegionInfo right) {
+      // This comparator differs from the one HRegionInfo in that it sorts
+      // parent before daughters.
+      if (left == null) return -1;
+      if (right == null) return 1;
+      // Same table name.
+      int result = Bytes.compareTo(left.getTableName(),
+          right.getTableName());
+      if (result != 0) return result;
+      // Compare start keys.
+      result = Bytes.compareTo(left.getStartKey(), right.getStartKey());
+      if (result != 0) return result;
+      // Compare end keys.
+      result = Bytes.compareTo(left.getEndKey(), right.getEndKey());
+      if (result != 0) return -result; // Flip the result so parent comes 
first.
+      return result;
+    }
+  }
+
+  /**
    * Get HRegionInfo from passed Map of row values.
    * @param result Map to do lookup in.
    * @return Null if not found (and logs fact that expected COL_REGIONINFO
@@ -192,7 +197,7 @@ class CatalogJanitor extends Chore {
       checkDaughter(parent, rowContent, HConstants.SPLITA_QUALIFIER);
     Pair<Boolean, Boolean> b =
       checkDaughter(parent, rowContent, HConstants.SPLITB_QUALIFIER);
-    if ((a.getFirst() && !a.getSecond()) && (b.getFirst() && !b.getSecond())) {
+    if (hasNoReferences(a) && hasNoReferences(b)) {
       LOG.debug("Deleting region " + parent.getRegionNameAsString() +
         " because daughter splits no longer hold references");
       // This latter regionOffline should not be necessary but is done for now
@@ -211,7 +216,16 @@ class CatalogJanitor extends Chore {
     return result;
   }
 
-  
+  /**
+   * @param p A pair where the first boolean says whether or not the daughter
+   * region directory exists in the filesystem and then the second boolean says
+   * whether the daughter has references to the parent.
+   * @return True the passed <code>p</code> signifies no references.
+   */
+  private boolean hasNoReferences(final Pair<Boolean, Boolean> p) {
+    return !p.getFirst() || !p.getSecond();
+  }
+
   /**
    * See if the passed daughter has references in the filesystem to the parent
    * and if not, remove the note of daughter region in the parent row: its

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/handler/ServerShutdownHandler.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/handler/ServerShutdownHandler.java?rev=1170291&r1=1170290&r2=1170291&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/handler/ServerShutdownHandler.java
 (original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/handler/ServerShutdownHandler.java
 Tue Sep 13 18:36:54 2011
@@ -308,6 +308,11 @@ public class ServerShutdownHandler exten
     if (isDaughterMissing(catalogTracker, daughter)) {
       LOG.info("Fixup; missing daughter " + daughter.getRegionNameAsString());
       MetaEditor.addDaughter(catalogTracker, daughter, null);
+
+      // TODO: Log WARN if the regiondir does not exist in the fs.  If its not
+      // there then something wonky about the split -- things will keep going
+      // but could be missing references to parent region.
+
       // And assign it.
       assignmentManager.assign(daughter, true);
     } else {

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java?rev=1170291&r1=1170290&r2=1170291&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java
 (original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java
 Tue Sep 13 18:36:54 2011
@@ -288,17 +288,28 @@ public class SplitTransaction {
     HRegion b = createDaughterRegion(this.hri_b, this.parent.rsServices);
 
     // Edit parent in meta.  Offlines parent region and adds splita and splitb.
+    // TODO: This can 'fail' by timing out against .META. but the edits could
+    // be applied anyways over on the server.  There is no way to tell for 
sure.
+    // We could try and get the edits again subsequent to their application
+    // whether we fail or not but that could fail too.  We should probably move
+    // the PONR to here before the edits go in but could mean we'd abort the
+    // regionserver when we didn't need to; i.e. the edits did not make it in.
     if (!testing) {
       MetaEditor.offlineParentInMeta(server.getCatalogTracker(),
         this.parent.getRegionInfo(), a.getRegionInfo(), b.getRegionInfo());
     }
 
     // This is the point of no return.  Adding subsequent edits to .META. as we
-    // do below when we do the daugther opens adding each to .META. can fail in
+    // do below when we do the daughter opens adding each to .META. can fail in
     // various interesting ways the most interesting of which is a timeout
-    // BUT the edits all go through (See HBASE-3872).  IF we reach the POWR
+    // BUT the edits all go through (See HBASE-3872).  IF we reach the PONR
     // then subsequent failures need to crash out this regionserver; the
     // server shutdown processing should be able to fix-up the incomplete 
split.
+    // The offlined parent will have the daughters as extra columns.  If
+    // we leave the daughter regions in place and do not remove them when we
+    // crash out, then they will have their references to the parent in place
+    // still and the server shutdown fixup of .META. will point to these
+    // regions.
     this.journal.add(JournalEntry.PONR);
       // Open daughters in parallel.
     DaughterOpener aOpener = new DaughterOpener(server, services, a);
@@ -684,7 +695,9 @@ public class SplitTransaction {
 
       case PONR:
         // We got to the point-of-no-return so we need to just abort. Return
-        // immediately.
+        // immediately.  Do not clean up created daughter regions.  They need
+        // to be in place so we don't delete the parent region mistakenly.
+        // See HBASE-3872.
         return false;
 
       default:

Modified: 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java?rev=1170291&r1=1170290&r2=1170291&view=diff
==============================================================================
--- 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java
 (original)
+++ 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java
 Tue Sep 13 18:36:54 2011
@@ -29,6 +29,8 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
@@ -57,7 +59,6 @@ import org.junit.Test;
 import org.mockito.Mockito;
 
 public class TestCatalogJanitor {
-
   /**
    * Pseudo server for below tests.
    */
@@ -70,8 +71,7 @@ public class TestCatalogJanitor {
       this.c = htu.getConfiguration();
       // Set hbase.rootdir into test dir.
       FileSystem fs = FileSystem.get(this.c);
-      Path rootdir =
-        fs.makeQualified(HBaseTestingUtility.getTestDir(HConstants.HBASE_DIR));
+      Path rootdir = fs.makeQualified(new 
Path(this.c.get(HConstants.HBASE_DIR)));
       this.c.set(HConstants.HBASE_DIR, rootdir.toString());
       this.ct = Mockito.mock(CatalogTracker.class);
       HRegionInterface hri = Mockito.mock(HRegionInterface.class);
@@ -211,9 +211,7 @@ public class TestCatalogJanitor {
         @Override
         public HTableDescriptor get(String tablename)
         throws TableExistsException, FileNotFoundException, IOException {
-          HTableDescriptor htd = new HTableDescriptor("table");
-          htd.addFamily(new HColumnDescriptor("family"));
-          return htd;
+          return createHTableDescriptor();
         }
         
         @Override
@@ -255,12 +253,12 @@ public class TestCatalogJanitor {
   @Test
   public void testCleanParent() throws IOException {
     HBaseTestingUtility htu = new HBaseTestingUtility();
+    setRootDirAndCleanIt(htu, "testCleanParent");
     Server server = new MockServer(htu);
     MasterServices services = new MockMasterServices(server);
     CatalogJanitor janitor = new CatalogJanitor(server, services);
     // Create regions.
-    HTableDescriptor htd = new HTableDescriptor("table");
-    htd.addFamily(new HColumnDescriptor("family"));
+    HTableDescriptor htd = createHTableDescriptor();
     HRegionInfo parent =
       new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
           Bytes.toBytes("eee"));
@@ -272,12 +270,7 @@ public class TestCatalogJanitor {
           Bytes.toBytes("eee"));
     // Test that when both daughter regions are in place, that we do not
     // remove the parent.
-    List<KeyValue> kvs = new ArrayList<KeyValue>();
-    kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
-      HConstants.SPLITA_QUALIFIER, Writables.getBytes(splita)));
-    kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
-      HConstants.SPLITB_QUALIFIER, Writables.getBytes(splitb)));
-    Result r = new Result(kvs);
+    Result r = createResult(parent, splita, splitb);
     // Add a reference under splitA directory so we don't clear out the parent.
     Path rootdir = services.getMasterFileSystem().getRootDir();
     Path tabledir =
@@ -293,14 +286,161 @@ public class TestCatalogJanitor {
     assertFalse(janitor.cleanParent(parent, r));
     // Remove the reference file and try again.
     assertTrue(fs.delete(p, true));
-    // We will fail!!! Because split b is empty, which is right... we should
-    // not remove parent if daughters do not exist in fs.
-    assertFalse(janitor.cleanParent(parent, r));
-    // Put in place daughter dir for b... that should make it so parent gets
-    // cleaned up.
-    storedir = Store.getStoreHomedir(tabledir, splitb.getEncodedName(),
-      htd.getColumnFamilies()[0].getName());
-    assertTrue(fs.mkdirs(storedir));
     assertTrue(janitor.cleanParent(parent, r));
   }
-}
+
+  /**
+   * Make sure parent gets cleaned up even if daughter is cleaned up before it.
+   * @throws IOException
+   * @throws InterruptedException 
+   */
+  @Test
+  public void testParentCleanedEvenIfDaughterGoneFirst()
+  throws IOException, InterruptedException {
+    HBaseTestingUtility htu = new HBaseTestingUtility();
+    setRootDirAndCleanIt(htu, "testParentCleanedEvenIfDaughterGoneFirst");
+    Server server = new MockServer(htu);
+    MasterServices services = new MockMasterServices(server);
+    CatalogJanitor janitor = new CatalogJanitor(server, services);
+    final HTableDescriptor htd = createHTableDescriptor();
+
+    // Create regions: aaa->eee, aaa->ccc, aaa->bbb, bbb->ccc, etc.
+
+    // Parent
+    HRegionInfo parent = new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+      Bytes.toBytes("eee"));
+    // Sleep a second else the encoded name on these regions comes out
+    // same for all with same start key and made in same second.
+    Thread.sleep(1001);
+
+    // Daughter a
+    HRegionInfo splita = new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+      Bytes.toBytes("ccc"));
+    Thread.sleep(1001);
+    // Make daughters of daughter a; splitaa and splitab.
+    HRegionInfo splitaa = new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+      Bytes.toBytes("bbb"));
+    HRegionInfo splitab = new HRegionInfo(htd.getName(), Bytes.toBytes("bbb"),
+      Bytes.toBytes("ccc"));
+
+    // Daughter b
+    HRegionInfo splitb = new HRegionInfo(htd.getName(), Bytes.toBytes("ccc"),
+      Bytes.toBytes("eee"));
+    Thread.sleep(1001);
+    // Make Daughters of daughterb; splitba and splitbb.
+    HRegionInfo splitba = new HRegionInfo(htd.getName(), Bytes.toBytes("ccc"),
+      Bytes.toBytes("ddd"));
+    HRegionInfo splitbb = new HRegionInfo(htd.getName(), Bytes.toBytes("ddd"),
+      Bytes.toBytes("eee"));
+
+    // First test that our Comparator works right up in CatalogJanitor.
+    // Just fo kicks.
+    SortedMap<HRegionInfo, Result> regions =
+      new TreeMap<HRegionInfo, Result>(new 
CatalogJanitor.SplitParentFirstComparator());
+    // Now make sure that this regions map sorts as we expect it to.
+    regions.put(parent, createResult(parent, splita, splitb));
+    regions.put(splitb, createResult(splitb, splitba, splitbb));
+    regions.put(splita, createResult(splita, splitaa, splitab));
+    // Assert its properly sorted.
+    int index = 0;
+    for (Map.Entry<HRegionInfo, Result> e: regions.entrySet()) {
+      if (index == 0) {
+        
assertTrue(e.getKey().getEncodedName().equals(parent.getEncodedName()));
+      } else if (index == 1) {
+        
assertTrue(e.getKey().getEncodedName().equals(splita.getEncodedName()));
+      } else if (index == 2) {
+        
assertTrue(e.getKey().getEncodedName().equals(splitb.getEncodedName()));
+      }
+      index++;
+    }
+
+    // Now play around with the cleanParent function.  Create a ref from splita
+    // up to the parent.
+    Path splitaRef =
+      createReferences(services, htd, parent, splita, Bytes.toBytes("ccc"), 
false);
+    // Make sure actual super parent sticks around because splita has a ref.
+    assertFalse(janitor.cleanParent(parent, regions.get(parent)));
+
+    //splitba, and split bb, do not have dirs in fs.  That means that if
+    // we test splitb, it should get cleaned up.
+    assertTrue(janitor.cleanParent(splitb, regions.get(splitb)));
+
+    // Now remove ref from splita to parent... so parent can be let go and so
+    // the daughter splita can be split (can't split if still references).
+    // BUT make the timing such that the daughter gets cleaned up before we
+    // can get a chance to let go of the parent.
+    FileSystem fs = FileSystem.get(htu.getConfiguration());
+    assertTrue(fs.delete(splitaRef, true));
+    // Create the refs from daughters of splita.
+    Path splitaaRef =
+      createReferences(services, htd, splita, splitaa, Bytes.toBytes("bbb"), 
false);
+    Path splitabRef =
+      createReferences(services, htd, splita, splitab, Bytes.toBytes("bbb"), 
true);
+
+    // Test splita.  It should stick around because references from splitab, 
etc.
+    assertFalse(janitor.cleanParent(splita, regions.get(splita)));
+
+    // Now clean up parent daughter first.  Remove references from its 
daughters.
+    assertTrue(fs.delete(splitaaRef, true));
+    assertTrue(fs.delete(splitabRef, true));
+    assertTrue(janitor.cleanParent(splita, regions.get(splita)));
+
+    // Super parent should get cleaned up now both splita and splitb are gone.
+    assertTrue(janitor.cleanParent(parent, regions.get(parent)));
+  }
+
+  private String setRootDirAndCleanIt(final HBaseTestingUtility htu,
+      final String subdir)
+  throws IOException {
+    Path testdir = HBaseTestingUtility.getTestDir(subdir);
+    FileSystem fs = FileSystem.get(htu.getConfiguration());
+    if (fs.exists(testdir)) assertTrue(fs.delete(testdir, true));
+    htu.getConfiguration().set(HConstants.HBASE_DIR, testdir.toString());
+    return htu.getConfiguration().get(HConstants.HBASE_DIR);
+  }
+
+  /**
+   * @param services Master services instance.
+   * @param htd
+   * @param parent
+   * @param daughter
+   * @param midkey
+   * @param top True if we are to write a 'top' reference.
+   * @return Path to reference we created.
+   * @throws IOException
+   */
+  private Path createReferences(final MasterServices services,
+      final HTableDescriptor htd, final HRegionInfo parent,
+      final HRegionInfo daughter, final byte [] midkey, final boolean top)
+  throws IOException {
+    Path rootdir = services.getMasterFileSystem().getRootDir();
+    Path tabledir = HTableDescriptor.getTableDir(rootdir, 
parent.getTableName());
+    Path storedir = Store.getStoreHomedir(tabledir, daughter.getEncodedName(),
+      htd.getColumnFamilies()[0].getName());
+    Reference ref = new Reference(midkey,
+      top? Reference.Range.top: Reference.Range.bottom);
+    long now = System.currentTimeMillis();
+    // Reference name has this format: StoreFile#REF_NAME_PARSER
+    Path p = new Path(storedir, Long.toString(now) + "." + 
parent.getEncodedName());
+    FileSystem fs = services.getMasterFileSystem().getFileSystem();
+    ref.write(fs, p);
+    return p;
+  }
+
+  private Result createResult(final HRegionInfo parent, final HRegionInfo a,
+      final HRegionInfo b)
+  throws IOException {
+    List<KeyValue> kvs = new ArrayList<KeyValue>();
+    kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
+      HConstants.SPLITA_QUALIFIER, Writables.getBytes(a)));
+    kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
+      HConstants.SPLITB_QUALIFIER, Writables.getBytes(b)));
+    return new Result(kvs);
+  }
+
+  private HTableDescriptor createHTableDescriptor() {
+    HTableDescriptor htd = new HTableDescriptor("t");
+    htd.addFamily(new HColumnDescriptor("f"));
+    return htd;
+  }
+}
\ No newline at end of file


Reply via email to