Skip to content

Commit 39ecaa1

Browse files
authored
HBASE-26784 Use HIGH_QOS for ResultScanner.close requests (#4146)
Signed-off-by: Duo Zhang <[email protected]> Signed-off-by: Andrew Purtell <[email protected]> Signed-off-by: Xiaolin Ha <[email protected]>
1 parent bcd9a9a commit 39ecaa1

File tree

8 files changed

+236
-80
lines changed

8 files changed

+236
-80
lines changed

hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncClientScanner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ private void startScan(OpenScannerResponse resp) {
188188
private CompletableFuture<OpenScannerResponse> openScanner(int replicaId) {
189189
return conn.callerFactory.<OpenScannerResponse> single().table(tableName)
190190
.row(scan.getStartRow()).replicaId(replicaId).locateType(getLocateType(scan))
191+
.priority(scan.getPriority())
191192
.rpcTimeout(rpcTimeoutNs, TimeUnit.NANOSECONDS)
192193
.operationTimeout(scanTimeoutNs, TimeUnit.NANOSECONDS).pause(pauseNs, TimeUnit.NANOSECONDS)
193194
.pauseForCQTBE(pauseForCQTBENs, TimeUnit.NANOSECONDS).maxAttempts(maxAttempts)

hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncScanSingleRegionRpcRetryingCaller.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.concurrent.TimeUnit;
3737
import org.apache.hadoop.hbase.CallQueueTooBigException;
3838
import org.apache.hadoop.hbase.DoNotRetryIOException;
39+
import org.apache.hadoop.hbase.HConstants;
3940
import org.apache.hadoop.hbase.HRegionLocation;
4041
import org.apache.hadoop.hbase.NotServingRegionException;
4142
import org.apache.hadoop.hbase.UnknownScannerException;
@@ -347,7 +348,7 @@ private long remainingTimeNs() {
347348

348349
private void closeScanner() {
349350
incRPCCallsMetrics(scanMetrics, regionServerRemote);
350-
resetController(controller, rpcTimeoutNs, priority);
351+
resetController(controller, rpcTimeoutNs, HConstants.HIGH_QOS);
351352
ScanRequest req = RequestConverter.buildScanRequest(this.scannerId, 0, true, false);
352353
stub.scan(controller, req, resp -> {
353354
if (controller.failed()) {

hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableRpcPriority.java

Lines changed: 117 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
*/
1818
package org.apache.hadoop.hbase.client;
1919

20+
import static org.apache.hadoop.hbase.HConstants.HIGH_QOS;
2021
import static org.apache.hadoop.hbase.HConstants.NORMAL_QOS;
2122
import static org.apache.hadoop.hbase.HConstants.SYSTEMTABLE_QOS;
2223
import static org.apache.hadoop.hbase.NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
24+
import static org.junit.Assert.assertEquals;
25+
import static org.junit.Assert.assertFalse;
2326
import static org.junit.Assert.assertNotNull;
27+
import static org.junit.Assert.assertTrue;
2428
import static org.mockito.ArgumentMatchers.any;
2529
import static org.mockito.ArgumentMatchers.anyInt;
2630
import static org.mockito.ArgumentMatchers.anyLong;
@@ -33,8 +37,13 @@
3337

3438
import java.io.IOException;
3539
import java.util.Arrays;
40+
import java.util.Optional;
3641
import java.util.concurrent.CompletableFuture;
42+
import java.util.concurrent.ExecutorService;
43+
import java.util.concurrent.Executors;
44+
import java.util.concurrent.TimeUnit;
3745
import java.util.concurrent.atomic.AtomicInteger;
46+
3847
import org.apache.hadoop.conf.Configuration;
3948
import org.apache.hadoop.hbase.Cell;
4049
import org.apache.hadoop.hbase.Cell.Type;
@@ -59,9 +68,7 @@
5968
import org.mockito.ArgumentMatcher;
6069
import org.mockito.invocation.InvocationOnMock;
6170
import org.mockito.stubbing.Answer;
62-
6371
import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback;
64-
6572
import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
6673
import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
6774
import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.ClientService;
@@ -91,41 +98,18 @@ public class TestAsyncTableRpcPriority {
9198

9299
private ClientService.Interface stub;
93100

101+
private ExecutorService threadPool;
102+
94103
private AsyncConnection conn;
95104

96105
@Rule
97106
public TestName name = new TestName();
98107

99108
@Before
100109
public void setUp() throws IOException {
110+
this.threadPool = Executors.newSingleThreadExecutor();
101111
stub = mock(ClientService.Interface.class);
102-
AtomicInteger scanNextCalled = new AtomicInteger(0);
103-
doAnswer(new Answer<Void>() {
104112

105-
@Override
106-
public Void answer(InvocationOnMock invocation) throws Throwable {
107-
ScanRequest req = invocation.getArgument(1);
108-
RpcCallback<ScanResponse> done = invocation.getArgument(2);
109-
if (!req.hasScannerId()) {
110-
done.run(ScanResponse.newBuilder().setScannerId(1).setTtl(800)
111-
.setMoreResultsInRegion(true).setMoreResults(true).build());
112-
} else {
113-
if (req.hasCloseScanner() && req.getCloseScanner()) {
114-
done.run(ScanResponse.getDefaultInstance());
115-
} else {
116-
Cell cell = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY).setType(Type.Put)
117-
.setRow(Bytes.toBytes(scanNextCalled.incrementAndGet()))
118-
.setFamily(Bytes.toBytes("cf")).setQualifier(Bytes.toBytes("cq"))
119-
.setValue(Bytes.toBytes("v")).build();
120-
Result result = Result.create(Arrays.asList(cell));
121-
done.run(
122-
ScanResponse.newBuilder().setScannerId(1).setTtl(800).setMoreResultsInRegion(true)
123-
.setMoreResults(true).addResults(ProtobufUtil.toResult(result)).build());
124-
}
125-
}
126-
return null;
127-
}
128-
}).when(stub).scan(any(HBaseRpcController.class), any(ScanRequest.class), any());
129113
doAnswer(new Answer<Void>() {
130114

131115
@Override
@@ -218,6 +202,16 @@ public boolean matches(HBaseRpcController controller) {
218202
});
219203
}
220204

205+
private ScanRequest assertScannerCloseRequest() {
206+
return argThat(new ArgumentMatcher<ScanRequest>() {
207+
208+
@Override
209+
public boolean matches(ScanRequest request) {
210+
return request.hasCloseScanner() && request.getCloseScanner();
211+
}
212+
});
213+
}
214+
221215
@Test
222216
public void testGet() {
223217
conn.getTable(TableName.valueOf(name.getMethodName()))
@@ -478,53 +472,113 @@ public void testCheckAndMutateMetaTable() throws IOException {
478472
any(ClientProtos.MultiRequest.class), any());
479473
}
480474

475+
private CompletableFuture<Void> mockScanReturnRenewFuture(int scanPriority) {
476+
int scannerId = 1;
477+
CompletableFuture<Void> future = new CompletableFuture<>();
478+
AtomicInteger scanNextCalled = new AtomicInteger(0);
479+
doAnswer(new Answer<Void>() {
480+
481+
@SuppressWarnings("FutureReturnValueIgnored")
482+
@Override
483+
public Void answer(InvocationOnMock invocation) throws Throwable {
484+
threadPool.submit(() -> {
485+
ScanRequest req = invocation.getArgument(1);
486+
RpcCallback<ScanResponse> done = invocation.getArgument(2);
487+
if (!req.hasScannerId()) {
488+
done.run(ScanResponse.newBuilder()
489+
.setScannerId(scannerId).setTtl(800)
490+
.setMoreResultsInRegion(true).setMoreResults(true)
491+
.build());
492+
} else {
493+
if (req.hasRenew() && req.getRenew()) {
494+
future.complete(null);
495+
}
496+
497+
assertFalse("close scanner should not come in with scan priority " + scanPriority,
498+
req.hasCloseScanner() && req.getCloseScanner());
499+
500+
Cell cell = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
501+
.setType(Type.Put).setRow(Bytes.toBytes(scanNextCalled.incrementAndGet()))
502+
.setFamily(Bytes.toBytes("cf")).setQualifier(Bytes.toBytes("cq"))
503+
.setValue(Bytes.toBytes("v")).build();
504+
Result result = Result.create(Arrays.asList(cell));
505+
done.run(
506+
ScanResponse.newBuilder()
507+
.setScannerId(scannerId).setTtl(800).setMoreResultsInRegion(true)
508+
.setMoreResults(true).addResults(ProtobufUtil.toResult(result))
509+
.build());
510+
}
511+
});
512+
return null;
513+
}
514+
}).when(stub).scan(assertPriority(scanPriority), any(ScanRequest.class), any());
515+
516+
doAnswer(new Answer<Void>() {
517+
518+
@SuppressWarnings("FutureReturnValueIgnored")
519+
@Override
520+
public Void answer(InvocationOnMock invocation) throws Throwable {
521+
threadPool.submit(() ->{
522+
ScanRequest req = invocation.getArgument(1);
523+
RpcCallback<ScanResponse> done = invocation.getArgument(2);
524+
assertTrue("close request should have scannerId", req.hasScannerId());
525+
assertEquals("close request's scannerId should match", scannerId, req.getScannerId());
526+
assertTrue("close request should have closerScanner set",
527+
req.hasCloseScanner() && req.getCloseScanner());
528+
529+
done.run(ScanResponse.getDefaultInstance());
530+
});
531+
return null;
532+
}
533+
}).when(stub).scan(assertPriority(HIGH_QOS), assertScannerCloseRequest(), any());
534+
return future;
535+
}
536+
481537
@Test
482-
public void testScan() throws IOException, InterruptedException {
483-
try (ResultScanner scanner = conn.getTable(TableName.valueOf(name.getMethodName()))
484-
.getScanner(new Scan().setCaching(1).setMaxResultSize(1).setPriority(19))) {
485-
assertNotNull(scanner.next());
486-
Thread.sleep(1000);
487-
}
488-
Thread.sleep(1000);
489-
// open, next, several renew lease, and then close
490-
verify(stub, atLeast(4)).scan(assertPriority(19), any(ScanRequest.class), any());
538+
public void testScan() throws Exception {
539+
CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(19);
540+
testForTable(TableName.valueOf(name.getMethodName()), renewFuture, Optional.of(19));
491541
}
492542

493543
@Test
494-
public void testScanNormalTable() throws IOException, InterruptedException {
495-
try (ResultScanner scanner = conn.getTable(TableName.valueOf(name.getMethodName()))
496-
.getScanner(new Scan().setCaching(1).setMaxResultSize(1))) {
497-
assertNotNull(scanner.next());
498-
Thread.sleep(1000);
499-
}
500-
Thread.sleep(1000);
501-
// open, next, several renew lease, and then close
502-
verify(stub, atLeast(4)).scan(assertPriority(NORMAL_QOS), any(ScanRequest.class), any());
544+
public void testScanNormalTable() throws Exception {
545+
CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(NORMAL_QOS);
546+
testForTable(TableName.valueOf(name.getMethodName()), renewFuture, Optional.of(NORMAL_QOS));
503547
}
504548

505549
@Test
506-
public void testScanSystemTable() throws IOException, InterruptedException {
507-
try (ResultScanner scanner =
508-
conn.getTable(TableName.valueOf(SYSTEM_NAMESPACE_NAME_STR, name.getMethodName()))
509-
.getScanner(new Scan().setCaching(1).setMaxResultSize(1))) {
510-
assertNotNull(scanner.next());
511-
Thread.sleep(1000);
512-
}
513-
Thread.sleep(1000);
514-
// open, next, several renew lease, and then close
515-
verify(stub, atLeast(4)).scan(assertPriority(SYSTEMTABLE_QOS), any(ScanRequest.class), any());
550+
public void testScanSystemTable() throws Exception {
551+
CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(SYSTEMTABLE_QOS);
552+
testForTable(TableName.valueOf(SYSTEM_NAMESPACE_NAME_STR, name.getMethodName()),
553+
renewFuture, Optional.empty());
516554
}
517555

518556
@Test
519-
public void testScanMetaTable() throws IOException, InterruptedException {
520-
try (ResultScanner scanner = conn.getTable(TableName.META_TABLE_NAME)
521-
.getScanner(new Scan().setCaching(1).setMaxResultSize(1))) {
557+
public void testScanMetaTable() throws Exception {
558+
CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(SYSTEMTABLE_QOS);
559+
testForTable(TableName.META_TABLE_NAME, renewFuture, Optional.empty());
560+
}
561+
562+
private void testForTable(TableName tableName, CompletableFuture<Void> renewFuture,
563+
Optional<Integer> priority) throws Exception {
564+
Scan scan = new Scan().setCaching(1).setMaxResultSize(1);
565+
priority.ifPresent(scan::setPriority);
566+
567+
try (ResultScanner scanner = conn.getTable(tableName).getScanner(scan)) {
522568
assertNotNull(scanner.next());
523-
Thread.sleep(1000);
569+
// wait for at least one renew to come in before closing
570+
renewFuture.join();
524571
}
525-
Thread.sleep(1000);
526-
// open, next, several renew lease, and then close
527-
verify(stub, atLeast(4)).scan(assertPriority(SYSTEMTABLE_QOS), any(ScanRequest.class), any());
572+
573+
// ensures the close thread has time to finish before asserting
574+
threadPool.shutdown();
575+
threadPool.awaitTermination(5, TimeUnit.SECONDS);
576+
577+
// just verify that the calls happened. verification of priority occurred in the mocking
578+
// open, next, then one or more lease renewals, then close
579+
verify(stub, atLeast(4)).scan(any(), any(ScanRequest.class), any());
580+
// additionally, explicitly check for a close request
581+
verify(stub, times(1)).scan(any(), assertScannerCloseRequest(), any());
528582
}
529583

530584
@Test

hbase-server/src/test/java/org/apache/hadoop/hbase/client/AbstractTestAsyncTableScan.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ protected static List<Object[]> getTableAndScanCreatorParams() {
129129

130130
protected abstract Scan createScan();
131131

132-
protected abstract List<Result> doScan(Scan scan) throws Exception;
132+
protected abstract List<Result> doScan(Scan scan, int closeAfter) throws Exception;
133133

134134
protected final List<Result> convertFromBatchResult(List<Result> results) {
135135
assertTrue(results.size() % 2 == 0);
@@ -145,7 +145,7 @@ protected final List<Result> convertFromBatchResult(List<Result> results) {
145145

146146
@Test
147147
public void testScanAll() throws Exception {
148-
List<Result> results = doScan(createScan());
148+
List<Result> results = doScan(createScan(), -1);
149149
// make sure all scanners are closed at RS side
150150
TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer())
151151
.forEach(
@@ -169,7 +169,7 @@ private void assertResultEquals(Result result, int i) {
169169

170170
@Test
171171
public void testReversedScanAll() throws Exception {
172-
List<Result> results = doScan(createScan().setReversed(true));
172+
List<Result> results = doScan(createScan().setReversed(true), -1);
173173
assertEquals(COUNT, results.size());
174174
IntStream.range(0, COUNT).forEach(i -> assertResultEquals(results.get(i), COUNT - i - 1));
175175
}
@@ -178,7 +178,7 @@ public void testReversedScanAll() throws Exception {
178178
public void testScanNoStopKey() throws Exception {
179179
int start = 345;
180180
List<Result> results =
181-
doScan(createScan().withStartRow(Bytes.toBytes(String.format("%03d", start))));
181+
doScan(createScan().withStartRow(Bytes.toBytes(String.format("%03d", start))), -1);
182182
assertEquals(COUNT - start, results.size());
183183
IntStream.range(0, COUNT - start).forEach(i -> assertResultEquals(results.get(i), start + i));
184184
}
@@ -187,36 +187,44 @@ public void testScanNoStopKey() throws Exception {
187187
public void testReverseScanNoStopKey() throws Exception {
188188
int start = 765;
189189
List<Result> results = doScan(
190-
createScan().withStartRow(Bytes.toBytes(String.format("%03d", start))).setReversed(true));
190+
createScan().withStartRow(Bytes.toBytes(String.format("%03d", start))).setReversed(true), -1);
191191
assertEquals(start + 1, results.size());
192192
IntStream.range(0, start + 1).forEach(i -> assertResultEquals(results.get(i), start - i));
193193
}
194194

195195
@Test
196196
public void testScanWrongColumnFamily() throws Exception {
197197
try {
198-
doScan(createScan().addFamily(Bytes.toBytes("WrongColumnFamily")));
198+
doScan(createScan().addFamily(Bytes.toBytes("WrongColumnFamily")), -1);
199199
} catch (Exception e) {
200200
assertTrue(e instanceof NoSuchColumnFamilyException ||
201201
e.getCause() instanceof NoSuchColumnFamilyException);
202202
}
203203
}
204204

205205
private void testScan(int start, boolean startInclusive, int stop, boolean stopInclusive,
206-
int limit) throws Exception {
206+
int limit) throws Exception {
207+
testScan(start, startInclusive, stop, stopInclusive, limit, -1);
208+
}
209+
210+
private void testScan(int start, boolean startInclusive, int stop, boolean stopInclusive,
211+
int limit, int closeAfter) throws Exception {
207212
Scan scan =
208213
createScan().withStartRow(Bytes.toBytes(String.format("%03d", start)), startInclusive)
209214
.withStopRow(Bytes.toBytes(String.format("%03d", stop)), stopInclusive);
210215
if (limit > 0) {
211216
scan.setLimit(limit);
212217
}
213-
List<Result> results = doScan(scan);
218+
List<Result> results = doScan(scan, closeAfter);
214219
int actualStart = startInclusive ? start : start + 1;
215220
int actualStop = stopInclusive ? stop + 1 : stop;
216221
int count = actualStop - actualStart;
217222
if (limit > 0) {
218223
count = Math.min(count, limit);
219224
}
225+
if (closeAfter > 0) {
226+
count = Math.min(count, closeAfter);
227+
}
220228
assertEquals(count, results.size());
221229
IntStream.range(0, count).forEach(i -> assertResultEquals(results.get(i), actualStart + i));
222230
}
@@ -229,7 +237,7 @@ private void testReversedScan(int start, boolean startInclusive, int stop, boole
229237
if (limit > 0) {
230238
scan.setLimit(limit);
231239
}
232-
List<Result> results = doScan(scan);
240+
List<Result> results = doScan(scan, -1);
233241
int actualStart = startInclusive ? start : start - 1;
234242
int actualStop = stopInclusive ? stop - 1 : stop;
235243
int count = actualStart - actualStop;
@@ -309,4 +317,13 @@ public void testReversedScanWithLimitGreaterThanActualCount() throws Exception {
309317
testReversedScan(765, false, 543, true, 200);
310318
testReversedScan(876, false, 654, false, 200);
311319
}
320+
321+
@Test
322+
public void testScanEndingEarly() throws Exception {
323+
testScan(1, true, 998, false, 0, 900); // from first region to last region
324+
testScan(123, true, 234, true, 0, 100);
325+
testScan(234, true, 456, false, 0, 100);
326+
testScan(345, false, 567, true, 0, 100);
327+
testScan(456, false, 678, false, 0, 100);
328+
}
312329
}

0 commit comments

Comments
 (0)