This is an automated email from the ASF dual-hosted git repository. kturner pushed a commit to branch 2.0 in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/2.0 by this push: new 422a9d8 correctly handle first and last tablets in LinkingIterator #1260 (#1266) 422a9d8 is described below commit 422a9d88cea0061533ef9c087d69e0123721c049 Author: Keith Turner <ktur...@apache.org> AuthorDate: Thu Jul 11 17:58:47 2019 -0400 correctly handle first and last tablets in LinkingIterator #1260 (#1266) The LinkingIterator checks the structure of the AccumuloMetadata table as it is scans. It was not properly handling a split of the first tablet. It was also not ensuring the default tablet for a table was seen. This patch fixes those two issues. These issues were discovered while researching #1260, however I am not sure if these issues could have caused #1260. --- .../core/metadata/schema/LinkingIterator.java | 40 +++++++++++++-- .../core/metadata/schema/LinkingIteratorTest.java | 59 ++++++++++++++++++++-- 2 files changed, 93 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/apache/accumulo/core/metadata/schema/LinkingIterator.java b/core/src/main/java/org/apache/accumulo/core/metadata/schema/LinkingIterator.java index d12e34c..15ac4ce 100644 --- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/LinkingIterator.java +++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/LinkingIterator.java @@ -26,6 +26,7 @@ import java.util.function.Function; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.PartialKey; import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.dataImpl.KeyExtent; import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection; import org.apache.hadoop.io.Text; import org.slf4j.Logger; @@ -62,7 +63,20 @@ public class LinkingIterator implements Iterator<TabletMetadata> { @Override public boolean hasNext() { - return source.hasNext(); + boolean hasNext = source.hasNext(); + + // Always expect the default tablet to exist for a table. The following checks for the case when + // the default tablet was not seen when it should have been seen. + if (!hasNext && prevTablet != null && prevTablet.getEndRow() != null) { + Text defaultTabletRow = TabletsSection.getRow(prevTablet.getTableId(), null); + if (range.contains(new Key(defaultTabletRow))) { + throw new IllegalStateException( + "Scan range incudled default tablet, but did not see default tablet. Last tablet seen : " + + prevTablet.getExtent()); + } + } + + return hasNext; } static boolean goodTransition(TabletMetadata prev, TabletMetadata curr) { @@ -101,6 +115,7 @@ public class LinkingIterator implements Iterator<TabletMetadata> { private void resetSource() { if (prevTablet == null) { + log.debug("Resetting scanner to {}", range); source = iteratorFactory.apply(range); } else { // get the metadata table row for the previous tablet @@ -115,7 +130,7 @@ public class LinkingIterator implements Iterator<TabletMetadata> { Range seekRange = new Range(new Key(prevMetaRow).followingKey(PartialKey.ROW), true, range.getEndKey(), range.isEndKeyInclusive()); - log.info("Resetting scanner to {}", seekRange); + log.debug("Resetting scanner to {}", seekRange); source = iteratorFactory.apply(seekRange); } @@ -132,7 +147,26 @@ public class LinkingIterator implements Iterator<TabletMetadata> { if (prevTablet == null) { if (tmp.sawPrevEndRow()) { - currTablet = tmp; + + Text prevMetaRow = null; + + KeyExtent extent = tmp.getExtent(); + + if (extent.getPrevEndRow() != null) { + prevMetaRow = TabletsSection.getRow(extent.getTableId(), extent.getPrevEndRow()); + } + + // If the first tablet seen has a prev endrow within the range it means a preceding tablet + // exists that we need to go back and read. This could be caused by the first tablet in + // the range splitting concurrently while this code is reading. + + if (prevMetaRow == null || range.beforeStartKey(new Key(prevMetaRow))) { + currTablet = tmp; + } else { + log.debug( + "First tablet seen provides evidence of earlier tablet in range, retrying {} {} ", + prevMetaRow, range); + } } else { log.warn("Tablet has no prev end row " + tmp.getTableId() + " " + tmp.getEndRow()); } diff --git a/core/src/test/java/org/apache/accumulo/core/metadata/schema/LinkingIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/metadata/schema/LinkingIteratorTest.java index af9a687..371f6bb 100644 --- a/core/src/test/java/org/apache/accumulo/core/metadata/schema/LinkingIteratorTest.java +++ b/core/src/test/java/org/apache/accumulo/core/metadata/schema/LinkingIteratorTest.java @@ -29,7 +29,10 @@ import java.util.stream.Stream; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.data.TableId; import org.apache.accumulo.core.dataImpl.KeyExtent; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection; +import org.apache.hadoop.io.Text; import org.junit.Test; import com.google.common.collect.Lists; @@ -55,13 +58,21 @@ public class LinkingIteratorTest { } } - private static void check(List<TabletMetadata> expected, IterFactory iterFactory) { + private static void check(List<TabletMetadata> expected, IterFactory iterFactory, Range range) { List<KeyExtent> actual = new ArrayList<>(); - new LinkingIterator(iterFactory, new Range()) - .forEachRemaining(tm -> actual.add(tm.getExtent())); + new LinkingIterator(iterFactory, range).forEachRemaining(tm -> actual.add(tm.getExtent())); assertEquals(Lists.transform(expected, TabletMetadata::getExtent), actual); } + private static void check(List<TabletMetadata> expected, IterFactory iterFactory) { + check(expected, iterFactory, new Range()); + } + + private static void check(List<TabletMetadata> expected, IterFactory iterFactory, + TableId tableId) { + check(expected, iterFactory, TabletsSection.getRange(tableId)); + } + @Test public void testHole() { @@ -109,4 +120,46 @@ public class LinkingIteratorTest { check(tablets2, new IterFactory(tablets1, tablets2)); } + + @Test + public void testFirstTabletSplits() { + // check when first tablet has a prev end row that points to a non existent tablet. This could + // be caused by the first table splitting concurrently with a metadata scan of the first tablet. + List<TabletMetadata> tablets1 = Arrays.asList(create("4", "f", "m"), create("4", "m", null)); + List<TabletMetadata> tablets2 = + Arrays.asList(create("4", null, "f"), create("4", "f", "m"), create("4", "m", null)); + + check(tablets2, new IterFactory(tablets1, tablets2), TableId.of("4")); + check(tablets2, new IterFactory(tablets1, tablets2), + new KeyExtent(TableId.of("4"), null, new Text("e")).toMetadataRange()); + + // following should not care about missing tablet + check(tablets1, new IterFactory(tablets1, tablets2), + new KeyExtent(TableId.of("4"), null, new Text("g")).toMetadataRange()); + check(tablets1, new IterFactory(tablets1, tablets2), + new KeyExtent(TableId.of("4"), null, new Text("f")).toMetadataRange()); + } + + @Test(expected = IllegalStateException.class) + public void testIncompleteTable() { + // the last tablet in a table should have a null end row. Ensure the code detects when this does + // not happen. + List<TabletMetadata> tablets1 = Arrays.asList(create("4", null, "f"), create("4", "f", "m")); + + LinkingIterator li = new LinkingIterator(new IterFactory(tablets1, tablets1), + TabletsSection.getRange(TableId.of("4"))); + + while (li.hasNext()) { + li.next(); + } + } + + @Test + public void testIncompleteTableWithRange() { + // because the scan range does not got to end of table, this should not care about missing + // tablets at end of table. + List<TabletMetadata> tablets1 = Arrays.asList(create("4", null, "f"), create("4", "f", "m")); + check(tablets1, new IterFactory(tablets1, tablets1), + new KeyExtent(TableId.of("4"), new Text("r"), new Text("e")).toMetadataRange()); + } }