Index: hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestQueryMatcher.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestQueryMatcher.java (revision 1575433) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestQueryMatcher.java (working copy) @@ -89,22 +89,8 @@ } - public void testMatch_ExplicitColumns() - throws IOException { - //Moving up from the Tracker by using Gets and List instead - //of just byte [] - - //Expected result - List expected = new ArrayList(); - expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); - expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); - expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); - expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); - expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW); - expected.add(ScanQueryMatcher.MatchCode.DONE); - - // 2,4,5 - + private void _testMatch_ExplicitColumns(Scan scan, List expected) throws IOException { + // 2,4,5 ScanQueryMatcher qm = new ScanQueryMatcher(scan, new ScanInfo(fam2, 0, 1, ttl, false, 0, rowComparator), get.getFamilyMap().get(fam2), EnvironmentEdgeManager.currentTimeMillis() - ttl); @@ -136,7 +122,43 @@ } } + public void testMatch_ExplicitColumns() + throws IOException { + //Moving up from the Tracker by using Gets and List instead + //of just byte [] + //Expected result + List expected = new ArrayList(); + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW); + expected.add(ScanQueryMatcher.MatchCode.DONE); + + _testMatch_ExplicitColumns(scan, expected); + } + + public void testMatch_ExplicitColumnsEagerNext() + throws IOException { + //Moving up from the Tracker by using Gets and List instead + //of just byte [] + + //Expected result + List expected = new ArrayList(); + expected.add(ScanQueryMatcher.MatchCode.SKIP); + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); + expected.add(ScanQueryMatcher.MatchCode.SKIP); + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW); + expected.add(ScanQueryMatcher.MatchCode.DONE); + + Scan s = new Scan(scan); + s.setEagerNext(2); + _testMatch_ExplicitColumns(s, expected); + } + + public void testMatch_Wildcard() throws IOException { //Moving up from the Tracker by using Gets and List instead Index: hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestExplicitColumnTracker.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestExplicitColumnTracker.java (revision 1575433) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestExplicitColumnTracker.java (working copy) @@ -44,9 +44,9 @@ private void runTest(int maxVersions, TreeSet trackColumns, List scannerColumns, - List expected) throws IOException { + List expected, int eagerNext) throws IOException { ColumnTracker exp = new ExplicitColumnTracker( - trackColumns, 0, maxVersions, Long.MIN_VALUE); + trackColumns, 0, maxVersions, Long.MIN_VALUE, eagerNext); //Initialize result @@ -95,7 +95,7 @@ scanner.add(col4); scanner.add(col5); - runTest(maxVersions, columns, scanner, expected); + runTest(maxVersions, columns, scanner, expected, 0); } public void testGet_MultiVersion() throws IOException{ @@ -150,10 +150,64 @@ scanner.add(col5); //Initialize result - runTest(maxVersions, columns, scanner, expected); + runTest(maxVersions, columns, scanner, expected, 0); } + public void testGet_MultiVersionWithEagerNext() throws IOException{ + if(PRINT){ + System.out.println("\nMultiVersion"); + } + //Create tracker + TreeSet columns = new TreeSet(Bytes.BYTES_COMPARATOR); + //Looking for every other + columns.add(col2); + columns.add(col4); + + List expected = new ArrayList(); + expected.add(ScanQueryMatcher.MatchCode.SKIP); + expected.add(ScanQueryMatcher.MatchCode.SKIP); + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); + + expected.add(ScanQueryMatcher.MatchCode.INCLUDE); // col2; 1st version + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); // col2; 2nd version + expected.add(ScanQueryMatcher.MatchCode.SKIP); + + expected.add(ScanQueryMatcher.MatchCode.SKIP); + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); + + expected.add(ScanQueryMatcher.MatchCode.INCLUDE); // col4; 1st version + expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW); // col4; 2nd version + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_ROW); + + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_ROW); + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_ROW); + expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_ROW); + int maxVersions = 2; + + //Create "Scanner" + List scanner = new ArrayList(); + scanner.add(col1); + scanner.add(col1); + scanner.add(col1); + scanner.add(col2); + scanner.add(col2); + scanner.add(col2); + scanner.add(col3); + scanner.add(col3); + scanner.add(col3); + scanner.add(col4); + scanner.add(col4); + scanner.add(col4); + scanner.add(col5); + scanner.add(col5); + scanner.add(col5); + + //Initialize result + runTest(maxVersions, columns, scanner, expected, 2); + } + /** * hbase-2259 */ @@ -165,7 +219,7 @@ } ColumnTracker explicit = new ExplicitColumnTracker(columns, 0, maxVersions, - Long.MIN_VALUE); + Long.MIN_VALUE, 0); for (int i = 0; i < 100000; i+=2) { byte [] col = Bytes.toBytes("col"+i); ScanQueryMatcher.checkColumn(explicit, col, 0, col.length, 1, KeyValue.Type.Put.getCode(), @@ -193,7 +247,7 @@ new ScanQueryMatcher.MatchCode[] { ScanQueryMatcher.MatchCode.SEEK_NEXT_COL, ScanQueryMatcher.MatchCode.SEEK_NEXT_COL }); - runTest(1, columns, scanner, expected); + runTest(1, columns, scanner, expected, 0); } } Index: hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java (revision 1575433) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java (working copy) @@ -185,8 +185,8 @@ // We can share the ExplicitColumnTracker, diff is we reset // between rows, not between storefiles. - this.columns = new ExplicitColumnTracker(columns, - scanInfo.getMinVersions(), maxVersions, oldestUnexpiredTS); + this.columns = new ExplicitColumnTracker(columns, scanInfo.getMinVersions(), maxVersions, + oldestUnexpiredTS, scan.getEagerNext()); } this.isReversed = scan.isReversed(); } Index: hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitColumnTracker.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitColumnTracker.java (revision 1575433) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitColumnTracker.java (working copy) @@ -56,6 +56,10 @@ private final int maxVersions; private final int minVersions; + // hint for the tracker about many KVs we will attempt to search via next() + // before we schedule a (re)seek operation + private final int eagerSkip; + /** * Contains the list of columns that the ExplicitColumnTracker is tracking. * Each ColumnCount instance also tracks how many versions of the requested @@ -68,6 +72,7 @@ * Used to eliminate duplicates. */ private long latestTSOfCurrentColumn; private long oldestStamp; + private int skipCount; /** * Default constructor. @@ -78,9 +83,10 @@ * based on TTL */ public ExplicitColumnTracker(NavigableSet columns, int minVersions, - int maxVersions, long oldestUnexpiredTS) { + int maxVersions, long oldestUnexpiredTS, int eagerSkip) { this.maxVersions = maxVersions; this.minVersions = minVersions; + this.eagerSkip = eagerSkip; this.oldestStamp = oldestUnexpiredTS; this.columns = new ColumnCount[columns.size()]; int i=0; @@ -136,7 +142,8 @@ if (ret > 0) { // The current KV is smaller than the column the ExplicitColumnTracker // is interested in, so seek to that column of interest. - return ScanQueryMatcher.MatchCode.SEEK_NEXT_COL; + return this.skipCount++ < this.eagerSkip ? ScanQueryMatcher.MatchCode.SKIP + : ScanQueryMatcher.MatchCode.SEEK_NEXT_COL; } // The current KV is bigger than the column the ExplicitColumnTracker @@ -145,6 +152,7 @@ // column of interest, and check again. if (ret <= -1) { ++this.index; + this.skipCount = 0; if (done()) { // No more to match, do not include, done with this row. return ScanQueryMatcher.MatchCode.SEEK_NEXT_ROW; // done_row @@ -169,6 +177,7 @@ if (count >= maxVersions || (count >= minVersions && isExpired(timestamp))) { // Done with versions for this column ++this.index; + this.skipCount = 0; resetTS(); if (done()) { // We have served all the requested columns. @@ -187,6 +196,7 @@ // Called between every row. public void reset() { this.index = 0; + this.skipCount = 0; this.column = this.columns[this.index]; for(ColumnCount col : this.columns) { col.setCount(0); @@ -226,6 +236,7 @@ resetTS(); if (compare <= 0) { ++this.index; + this.skipCount = 0; if (done()) { // Will not hit any more columns in this storefile this.column = null; Index: hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java =================================================================== --- hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java (revision 1575433) +++ hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java (working copy) @@ -88,6 +88,7 @@ private static final String RAW_ATTR = "_raw_"; private static final String ISOLATION_LEVEL = "_isolationlevel_"; + public static final String HINT_EAGER_NEXT = "_eager_next_"; private byte [] startRow = HConstants.EMPTY_START_ROW; private byte [] stopRow = HConstants.EMPTY_END_ROW; @@ -614,6 +615,30 @@ } /** + * EXPERT ONLY. + * Indicate to the scanner logic how many times we attempt to retrieve the next KV + * before we schedule a reseek. + * The right value depends on the size of the average KV. A reseek is more efficient when + * it can skip 5-10 KVs or 512B-1KB. + * Setting this only has any effect when columns were added with + * {@link #addColumn(byte[], byte[])} + * + * Default is 0 (always reseek). + * @param eagerNext The number of KVs to search via next() + */ + public void setEagerNext(int eagerNext) { + setAttribute(HINT_EAGER_NEXT, Bytes.toBytes(eagerNext)); + } + + /** + * @return the current eager next value + */ + public int getEagerNext() { + byte[] attr = getAttribute(HINT_EAGER_NEXT); + return attr == null ? 0 : Bytes.toInt(attr); + } + +/** * Compile the table and column family (i.e. schema) information * into a String. Useful for parsing and aggregation by debugging, * logging, and administration tools.