Index: src/test/java/org/apache/hadoop/hbase/regionserver/TestExplicitColumnTracker.java =================================================================== --- src/test/java/org/apache/hadoop/hbase/regionserver/TestExplicitColumnTracker.java (revision 1575012) +++ src/test/java/org/apache/hadoop/hbase/regionserver/TestExplicitColumnTracker.java (working copy) @@ -47,7 +47,7 @@ List scannerColumns, List expected) throws IOException { ColumnTracker exp = new ExplicitColumnTracker( - trackColumns, 0, maxVersions, Long.MIN_VALUE); + trackColumns, 0, maxVersions, Long.MIN_VALUE, 0); //Initialize result @@ -166,7 +166,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(), Index: src/main/java/org/apache/hadoop/hbase/client/Scan.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/client/Scan.java (revision 1575012) +++ src/main/java/org/apache/hadoop/hbase/client/Scan.java (working copy) @@ -83,7 +83,10 @@ private static final String RAW_ATTR = "_raw_"; private static final String ONDEMAND_ATTR = "_ondemand_"; private static final String ISOLATION_LEVEL = "_isolationlevel_"; + + /** Scan Hints */ private static final String SMALL_ATTR = "_small_"; + static public final String HINT_EAGER_NEXT = "_eager_next_"; private static final byte SCAN_VERSION = (byte)2; private byte [] startRow = HConstants.EMPTY_START_ROW; @@ -525,6 +528,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. Index: src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java (revision 1575012) +++ src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java (working copy) @@ -176,8 +176,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()); } } Index: src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitColumnTracker.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitColumnTracker.java (revision 1575012) +++ src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitColumnTracker.java (working copy) @@ -54,6 +54,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 @@ -66,6 +70,7 @@ * Used to eliminate duplicates. */ private long latestTSOfCurrentColumn; private long oldestStamp; + private int skipCount; /** * Default constructor. @@ -77,9 +82,10 @@ * @param ttl The timeToLive to enforce */ 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; @@ -135,7 +141,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 @@ -144,6 +151,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 @@ -168,6 +176,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. @@ -186,6 +195,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); @@ -225,6 +235,7 @@ resetTS(); if (compare <= 0) { ++this.index; + this.skipCount = 0; if (done()) { // Will not hit any more columns in this storefile this.column = null;