Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,12 @@ public StoreScanner(HStore store, ScanInfo scanInfo, Scan scan, NavigableSet<byt
// key does not exist, then to the start of the next matching Row).
// Always check bloom filter to optimize the top row seek for delete
// family marker.
seekScanners(scanners, matcher.getStartKey(), explicitColumnQuery && lazySeekEnabledGlobally,
parallelSeekEnabled);

// Filters must only see real Cells. A lazy seek can expose a synthetic Cell
// for the scan start row, so disable it for non-Get filters.
boolean useLazySeek =
explicitColumnQuery && lazySeekEnabledGlobally && !(scan.hasFilter() && !scan.isGetScan());
seekScanners(scanners, matcher.getStartKey(), useLazySeek, parallelSeekEnabled);

// set storeLimit
this.storeLimit = scan.getMaxResultsPerColumnFamily();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NavigableSet;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
Expand All @@ -39,16 +42,22 @@
import org.apache.hadoop.hbase.HTestConst;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.UnknownScannerException;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.ByteArrayComparable;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.InclusiveStopFilter;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.WhileMatchFilter;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
Expand Down Expand Up @@ -93,6 +102,10 @@ public class TestScanner {

private static final long START_CODE = Long.MAX_VALUE;

private static final byte[] LAZY_SEEK_FAMILY = Bytes.toBytes("family");
private static final byte[] LAZY_SEEK_QUALIFIER = Bytes.toBytes("qualifier");
private static final byte[] LAZY_SEEK_ROW = Bytes.toBytes("row");

private HRegion region;

private byte[] firstRowBytes, secondRowBytes, thirdRowBytes;
Expand All @@ -111,6 +124,185 @@ public TestScanner() {
col1 = Bytes.toBytes("column1");
}

private static final class RecordingStoreScanner extends StoreScanner {
private boolean initialSeekWasLazy;

RecordingStoreScanner(HStore store, Scan scan, NavigableSet<byte[]> columns)
throws IOException {
super(store, store.getScanInfo(), scan, columns, Long.MAX_VALUE);
}

@Override
protected void seekScanners(List<? extends KeyValueScanner> scanners, Cell seekKey,
boolean isLazy, boolean isParallelSeek) throws IOException {
initialSeekWasLazy = isLazy;
super.seekScanners(scanners, seekKey, isLazy, isParallelSeek);
}
}

private static final class TrackingRowComparator extends ByteArrayComparable {
private final List<byte[]> comparedRows = new ArrayList<>();

TrackingRowComparator(byte[] value) {
super(value);
}

@Override
public int compareTo(byte[] value, int offset, int length) {
comparedRows.add(Bytes.copy(value, offset, length));
return Bytes.compareTo(getValue(), 0, getValue().length, value, offset, length);
}

@Override
public byte[] toByteArray() {
return getValue();
}
}

@Test
public void testFilterComparatorOnlySeesActualRows() throws Exception {
byte[] family = Bytes.toBytes("family");
byte[] qualifier = Bytes.toBytes("qualifier");
byte[] regionStartKey = new byte[] { 1 };
byte[] row = new byte[] { 1, 0, 1 };
TableDescriptor tableDescriptor =
TableDescriptorBuilder.newBuilder(TableName.valueOf("testFilterComparatorOnlySeesActualRows"))
.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(family)
.setBloomFilterType(BloomType.ROWCOL).build())
.build();
TrackingRowComparator comparator = new TrackingRowComparator(row);

StoreScanner.enableLazySeekGlobally(true);
try {
this.region = TEST_UTIL.createLocalHRegion(tableDescriptor, regionStartKey, null);
Put put = new Put(row);
put.addColumn(family, qualifier, Bytes.toBytes("value"));
region.put(put);
region.flush(true);

Scan scan = new Scan().withStartRow(regionStartKey);
scan.addColumn(family, qualifier);
scan.setFilter(new RowFilter(CompareOperator.EQUAL, comparator));
List<Cell> results = new ArrayList<>();
try (InternalScanner scanner = region.getScanner(scan)) {
assertFalse(scanner.next(results));
}

assertEquals(1, results.size());
assertTrue(CellUtil.matchingRows(results.get(0), row));
assertEquals(1, comparator.comparedRows.size());
assertTrue(Bytes.equals(row, comparator.comparedRows.get(0)));
} finally {
StoreScanner.enableLazySeekGlobally(StoreScanner.LAZY_SEEK_ENABLED_BY_DEFAULT);
HBaseTestingUtility.closeRegionAndWAL(this.region);
}
}

@Test
public void testWhileMatchFilterOnlySeesActualRows() throws Exception {
byte[] family = Bytes.toBytes("family");
byte[] qualifier = Bytes.toBytes("qualifier");
TableDescriptor tableDescriptor =
TableDescriptorBuilder.newBuilder(TableName.valueOf("testWhileMatchFilterOnlySeesActualRows"))
.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(family).build()).build();

StoreScanner.enableLazySeekGlobally(true);
try {
this.region = TEST_UTIL.createLocalHRegion(tableDescriptor, null, null);
List<String> rows = Arrays.asList("row1", "row2", "row3");
for (String row : rows) {
Put put = new Put(Bytes.toBytes(row)).addColumn(family, qualifier, Bytes.toBytes("value"));
region.put(put);
}
region.flush(true);

Scan scan = new Scan().addColumn(family, qualifier)
.setFilter(new WhileMatchFilter(new RowFilter(CompareOperator.NOT_EQUAL,
new BinaryComparator(HConstants.EMPTY_START_ROW))));
int scannedRows = 0;
try (InternalScanner scanner = region.getScanner(scan)) {
boolean hasMoreRows;
do {
List<Cell> results = new ArrayList<>();
hasMoreRows = scanner.next(results);
if (!results.isEmpty()) {
++scannedRows;
}
} while (hasMoreRows);
}

assertEquals(rows.size(), scannedRows);
} finally {
StoreScanner.enableLazySeekGlobally(StoreScanner.LAZY_SEEK_ENABLED_BY_DEFAULT);
HBaseTestingUtility.closeRegionAndWAL(this.region);
}
}

@Test
public void testInitialLazySeekForUnfilteredExplicitColumnScan() throws Exception {
Scan scan = new Scan().withStartRow(LAZY_SEEK_ROW);
scan.addColumn(LAZY_SEEK_FAMILY, LAZY_SEEK_QUALIFIER);
assertInitialLazySeek(scan, true, true);
}

@Test
public void testInitialLazySeekForFilteredGet() throws Exception {
Get get = new Get(LAZY_SEEK_ROW);
get.addColumn(LAZY_SEEK_FAMILY, LAZY_SEEK_QUALIFIER);
get.setFilter(new PrefixFilter(LAZY_SEEK_ROW));
assertInitialLazySeek(new Scan(get), true, true);
}

@Test
public void testInitialLazySeekForFilteredNonGetScan() throws Exception {
Scan scan = new Scan().withStartRow(LAZY_SEEK_ROW);
scan.addColumn(LAZY_SEEK_FAMILY, LAZY_SEEK_QUALIFIER);
scan.setFilter(new PrefixFilter(LAZY_SEEK_ROW));
assertInitialLazySeek(scan, true, false);
}

@Test
public void testInitialLazySeekForAllColumnScan() throws Exception {
assertInitialLazySeek(new Scan().withStartRow(LAZY_SEEK_ROW), true, false);
}

@Test
public void testInitialLazySeekWhenDisabledGlobally() throws Exception {
Scan scan = new Scan().withStartRow(LAZY_SEEK_ROW);
scan.addColumn(LAZY_SEEK_FAMILY, LAZY_SEEK_QUALIFIER);
assertInitialLazySeek(scan, false, false);
}

private void assertInitialLazySeek(Scan scan, boolean lazySeekEnabled, boolean expected)
throws IOException {
StoreScanner.enableLazySeekGlobally(lazySeekEnabled);
try {
HStore store = createLazySeekTestStore();
try (RecordingStoreScanner scanner =
new RecordingStoreScanner(store, scan, scan.getFamilyMap().get(LAZY_SEEK_FAMILY))) {
assertEquals(expected, scanner.initialSeekWasLazy);
}
} finally {
StoreScanner.enableLazySeekGlobally(StoreScanner.LAZY_SEEK_ENABLED_BY_DEFAULT);
if (this.region != null) {
HBaseTestingUtility.closeRegionAndWAL(this.region);
this.region = null;
}
}
}

private HStore createLazySeekTestStore() throws IOException {
TableDescriptor tableDescriptor = TableDescriptorBuilder
.newBuilder(TableName.valueOf("testInitialLazySeek"))
.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(LAZY_SEEK_FAMILY).build()).build();
this.region = TEST_UTIL.createLocalHRegion(tableDescriptor, null, null);
Put put = new Put(LAZY_SEEK_ROW);
put.addColumn(LAZY_SEEK_FAMILY, LAZY_SEEK_QUALIFIER, Bytes.toBytes("value"));
region.put(put);
region.flush(true);
return region.getStore(LAZY_SEEK_FAMILY);
}

/**
* Test basic stop row filter works.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.apache.hadoop.hbase.regionserver;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
Expand All @@ -42,6 +43,7 @@
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
Expand All @@ -50,6 +52,7 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.params.provider.Arguments;
import org.slf4j.Logger;
Expand Down Expand Up @@ -119,19 +122,17 @@ public TestSeekOptimizations(Compression.Algorithm comprAlgo, BloomType bloomTyp
}

@BeforeEach
public void setUp() {
public void setUp(TestInfo testInfo) throws IOException {
RNG.setSeed(91238123L);
expectedKVs.clear();
TEST_UTIL.getConfiguration().setInt(BloomFilterUtil.PREFIX_LENGTH_KEY, 10);
}

@TestTemplate
public void testMultipleTimestampRanges() throws IOException {
// enable seek counting
StoreFileScanner.instrument();

region = TEST_UTIL.createTestRegion("testMultipleTimestampRanges", new HColumnDescriptor(FAMILY)
.setCompressionType(comprAlgo).setBloomFilterType(bloomType).setMaxVersions(3));
region = TEST_UTIL.createTestRegion(testInfo.getTestMethod().get().getName(),
new HColumnDescriptor(FAMILY).setCompressionType(comprAlgo).setBloomFilterType(bloomType)
.setMaxVersions(3));

// Delete the given timestamp and everything before.
final long latestDelTS = USE_MANY_STORE_FILES ? 1397 : -1;
Expand All @@ -147,12 +148,15 @@ public void testMultipleTimestampRanges() throws IOException {
}

prepareExpectedKVs(latestDelTS);
}

@TestTemplate
public void testMultipleTimestampRanges() throws IOException {
for (int[] columnArr : COLUMN_SETS) {
for (int[] rowRange : ROW_RANGES) {
for (int maxVersions : MAX_VERSIONS_VALUES) {
for (boolean lazySeekEnabled : new boolean[] { false, true }) {
testScan(columnArr, lazySeekEnabled, rowRange[0], rowRange[1], maxVersions);
testScan(columnArr, lazySeekEnabled, rowRange[0], rowRange[1], maxVersions, false);
}
}
}
Expand All @@ -173,8 +177,9 @@ public void testMultipleTimestampRanges() throws IOException {
+ String.format("%.2f%%", expectedSeekSavings * 100));
}

private void testScan(final int[] columnArr, final boolean lazySeekEnabled, final int startRow,
final int endRow, int maxVersions) throws IOException {
private ScanResult testScan(final int[] columnArr, final boolean lazySeekEnabled,
final int startRow, final int endRow, final int maxVersions, final boolean filtered)
throws IOException {
StoreScanner.enableLazySeekGlobally(lazySeekEnabled);
final Scan scan = new Scan();
final Set<String> qualSet = new HashSet<>();
Expand All @@ -183,6 +188,9 @@ private void testScan(final int[] columnArr, final boolean lazySeekEnabled, fina
scan.addColumn(FAMILY_BYTES, Bytes.toBytes(qualStr));
qualSet.add(qualStr);
}
if (filtered) {
scan.setFilter(new PrefixFilter(Bytes.toBytes("row")));
}
scan.setMaxVersions(maxVersions);
scan.setStartRow(rowBytes(startRow));

Expand All @@ -194,18 +202,23 @@ private void testScan(final int[] columnArr, final boolean lazySeekEnabled, fina

final long initialSeekCount = StoreFileScanner.getSeekCount();
final InternalScanner scanner = region.getScanner(scan);
final long scannerOpenSeekCount = StoreFileScanner.getSeekCount() - initialSeekCount;
final List<Cell> results = new ArrayList<>();
final List<Cell> actualKVs = new ArrayList<>();

// Such a clumsy do-while loop appears to be the official way to use an
// internalScanner. scanner.next() return value refers to the _next_
// result, not to the one already returned in results.
boolean hasNext;
do {
hasNext = scanner.next(results);
actualKVs.addAll(results);
results.clear();
} while (hasNext);
try {
boolean hasNext;
do {
hasNext = scanner.next(results);
actualKVs.addAll(results);
results.clear();
} while (hasNext);
} finally {
scanner.close();
}

List<Cell> filteredKVs =
filterExpectedResults(qualSet, rowBytes(startRow), rowBytes(endRow), maxVersions);
Expand All @@ -230,6 +243,7 @@ private void testScan(final int[] columnArr, final boolean lazySeekEnabled, fina
totalSeekDiligent += seekCount;
}
assertKVListsEqual(testDesc, filteredKVs, actualKVs);
return new ScanResult(actualKVs, scannerOpenSeekCount);
}

private List<Cell> filterExpectedResults(Set<String> qualSet, byte[] startRow, byte[] endRow,
Expand Down Expand Up @@ -444,4 +458,25 @@ public void assertKVListsEqual(String additionalMsg, final List<? extends Cell>
+ HBaseTestingUtility.safeGetAsStr(actual, i) + " (length " + aLen + ")" + additionalMsg);
}
}

@TestTemplate
public void testSeeksEagerlyWhenFiltered() throws IOException {
ScanResult filteredLazyResults = testScan(new int[] { 0 }, true, 0, 2, 1, true);
ScanResult filteredEagerResults = testScan(new int[] { 0 }, false, 0, 2, 1, true);
assertKVListsEqual("Filtered explicit column scan results differ with lazy seeking enabled",
filteredEagerResults.cells, filteredLazyResults.cells);
assertEquals(filteredEagerResults.scannerOpenSeekCount,
filteredLazyResults.scannerOpenSeekCount,
"Filtered explicit column scans must always eagerly seek");
}

private static final class ScanResult {
private final List<Cell> cells;
private final long scannerOpenSeekCount;

private ScanResult(List<Cell> cells, long scannerOpenSeekCount) {
this.cells = cells;
this.scannerOpenSeekCount = scannerOpenSeekCount;
}
}
}
Loading