Skip to content

Commit 89513a5

Browse files
committed
Add a config flag (via file or JVM flag) called 'hbase.meta.scan' that should
be set when HBase 2.x is used. This will turn on reverse scanning for meta information as opposed to the old way of calling getClosestRowBefore(). As part of this, some callbacks in the Scanner class are now package private so we can re-use them in HBaseClient.java. Fixes #150 Thanks @saintstack TODO - need to see how this behaves with split meta. TODO - I'd still like to auto-detect HBase 2.0 somehow. Signed-off-by: Chris Larsen <clarsen@yahoo-inc.com>
1 parent 4247a7e commit 89513a5

3 files changed

Lines changed: 237 additions & 8 deletions

File tree

src/HBaseClient.java

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2010-2012 The Async HBase Authors. All rights reserved.
2+
* Copyright (C) 2010-2018 The Async HBase Authors. All rights reserved.
33
* This file is part of Async HBase.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -416,6 +416,9 @@ public final class HBaseClient {
416416

417417
/** Default RPC timeout in milliseconds from the config */
418418
private final int rpc_timeout;
419+
420+
/** Whether or not we have to scan meta instead of making getClosestBeforeRow calls. */
421+
private final boolean scan_meta;
419422

420423
private boolean increment_buffer_durable = false;
421424

@@ -550,6 +553,11 @@ public HBaseClient(final String quorum_spec, final String base_path,
550553
if (config.properties.containsKey("hbase.increments.durable")) {
551554
increment_buffer_durable = config.getBoolean("hbase.increments.durable");
552555
}
556+
if (config.hasProperty("hbase.meta.scan")) {
557+
scan_meta = config.getBoolean("hbase.meta.scan");
558+
} else {
559+
scan_meta = Boolean.parseBoolean(System.getProperty("hbase.meta.scan", "false"));
560+
}
553561
}
554562

555563
/**
@@ -611,6 +619,11 @@ public HBaseClient(final Config config,
611619
if (config.properties.containsKey("hbase.increments.durable")) {
612620
increment_buffer_durable = config.getBoolean("hbase.increments.durable");
613621
}
622+
if (config.hasProperty("hbase.meta.scan")) {
623+
scan_meta = config.getBoolean("hbase.meta.scan");
624+
} else {
625+
scan_meta = Boolean.parseBoolean(System.getProperty("hbase.meta.scan", "false"));
626+
}
614627
}
615628

616629
/**
@@ -2715,11 +2728,21 @@ private Deferred<Object> locateRegion(final HBaseRpc request,
27152728
Deferred<Object> d = null;
27162729
try {
27172730
if (return_location) {
2718-
d = client.getClosestRowBefore(meta_region, meta_name, meta_key, INFO)
2731+
if (scan_meta) {
2732+
d = scanMeta(client, meta_region, meta_name, meta_key, INFO)
27192733
.addCallback(meta_lookup_done_return_location);
2720-
} else{
2721-
d = client.getClosestRowBefore(meta_region, meta_name, meta_key, INFO)
2734+
} else {
2735+
d = client.getClosestRowBefore(meta_region, meta_name, meta_key, INFO)
2736+
.addCallback(meta_lookup_done_return_location);
2737+
}
2738+
} else {
2739+
if (scan_meta) {
2740+
d = scanMeta(client, meta_region, meta_name, meta_key, INFO)
27222741
.addCallback(meta_lookup_done);
2742+
} else {
2743+
d = client.getClosestRowBefore(meta_region, meta_name, meta_key, INFO)
2744+
.addCallback(meta_lookup_done);
2745+
}
27232746
}
27242747
} catch (RuntimeException e) {
27252748
LOG.error("Unexpected exception while performing meta lookup", e);
@@ -2771,6 +2794,75 @@ public String toString() {
27712794
closest_before, return_location));
27722795
}
27732796

2797+
/**
2798+
* Later versions of HBase dropped the old
2799+
* {@link RegionClient#getClosestRowBefore(RegionInfo, byte[], byte[], byte[])}
2800+
* method and switched to performing reverse scans. This method will
2801+
* handle the scanning returning either a meta row if found or an empty
2802+
* array if the row/table was not found.
2803+
* TODO - need to see what happens with split meta.
2804+
*
2805+
* @param client The region client to open the scanner on.
2806+
* @param region The region we're scanning for.
2807+
* @param table The name of the table to scan for.
2808+
* @param row The row to scan for.
2809+
* @param family The family to scan on.
2810+
* @return A deferred resolving to an array list containing region
2811+
* info if successful or an empty array if the table/region doesn't
2812+
* exist. Or an exception if something goes pear shaped.
2813+
*/
2814+
private Deferred<ArrayList<KeyValue>> scanMeta(final RegionClient client,
2815+
final RegionInfo region,
2816+
final byte[] table,
2817+
final byte[] row,
2818+
final byte[] family) {
2819+
final Scanner scanner = newScanner(table);
2820+
scanner.setReversed(true);
2821+
scanner.setMaxNumRows(1);
2822+
scanner.setStartKey(row);
2823+
scanner.setFamily(family);
2824+
scanner.setRegionName(region);
2825+
2826+
final Deferred<ArrayList<KeyValue>> deferred =
2827+
new Deferred<ArrayList<KeyValue>>();
2828+
2829+
class ErrorCB implements Callback<Object, Exception> {
2830+
@Override
2831+
public Object call(final Exception ex) throws Exception {
2832+
scanner.close();
2833+
deferred.callback(ex);
2834+
return null;
2835+
}
2836+
@Override
2837+
public String toString() {
2838+
return "scanMeta.ErrorCB";
2839+
}
2840+
}
2841+
2842+
class MetaScanCB implements Callback<Void, ArrayList<ArrayList<KeyValue>>> {
2843+
@Override
2844+
public Void call(final ArrayList<ArrayList<KeyValue>> rows)
2845+
throws Exception {
2846+
final ArrayList<KeyValue> row = (rows == null || rows.isEmpty())
2847+
? new ArrayList<KeyValue>(0) : rows.get(0);
2848+
scanner.close();
2849+
deferred.callback(row);
2850+
return null;
2851+
}
2852+
@Override
2853+
public String toString() {
2854+
return "scanMeta.MetaScanCB";
2855+
}
2856+
}
2857+
2858+
HBaseRpc open_request = scanner.getOpenRequestForReverseScan(row);
2859+
open_request.region = region;
2860+
open_request.getDeferred().addCallbackDeferring(scanner.opened_scanner)
2861+
.addCallbacks(new MetaScanCB(), new ErrorCB());
2862+
client.sendRpc(open_request);
2863+
return deferred;
2864+
}
2865+
27742866
/**
27752867
* Callback executed when a lookup in META completes
27762868
* and user wants RegionLocation.

src/Scanner.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ public Deferred<ArrayList<ArrayList<KeyValue>>> nextRows() {
766766
/**
767767
* Callback to handle response from opening a scanner
768768
*/
769-
private final Callback<Deferred<ArrayList<ArrayList<KeyValue>>>, Object>
769+
final Callback<Deferred<ArrayList<ArrayList<KeyValue>>>, Object>
770770
opened_scanner =
771771
new Callback<Deferred<ArrayList<ArrayList<KeyValue>>>, Object>() {
772772
public Deferred<ArrayList<ArrayList<KeyValue>>> call(final Object arg) {
@@ -806,7 +806,7 @@ public String toString() {
806806
* This returns an {@code ArrayList<ArrayList<KeyValue>>} (possibly inside a
807807
* deferred one).
808808
*/
809-
private final Callback<Object, Object> got_next_row =
809+
final Callback<Object, Object> got_next_row =
810810
new Callback<Object, Object>() {
811811
public Object call(final Object response) {
812812
ArrayList<ArrayList<KeyValue>> rows = null;
@@ -841,7 +841,7 @@ public String toString() {
841841
/**
842842
* Creates a new errback to handle errors while trying to get more rows.
843843
*/
844-
private final Callback<Object, Object> nextRowErrback() {
844+
final Callback<Object, Object> nextRowErrback() {
845845
return new Callback<Object, Object>() {
846846
public Object call(final Object error) {
847847
final RegionInfo old_region = region; // Save before invalidate().
@@ -1481,7 +1481,7 @@ public String toString() {
14811481
* RPC sent out to fetch the next rows from the RegionServer.
14821482
*/
14831483
final class GetNextRowsRequest extends HBaseRpc {
1484-
1484+
14851485
@Override
14861486
byte[] method(final byte server_version) {
14871487
return (server_version >= RegionClient.SERVER_VERSION_095_OR_ABOVE

test/TestHBaseClientLocateRegion.java

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
import static org.junit.Assert.assertTrue;
88
import static org.junit.Assert.fail;
99
import static org.mockito.Matchers.any;
10+
import static org.mockito.Mockito.doAnswer;
1011
import static org.mockito.Mockito.when;
1112

1213
import java.util.ArrayList;
1314

15+
import org.hbase.async.Scanner.OpenScannerRequest;
16+
import org.hbase.async.Scanner.Response;
1417
import org.junit.Before;
1518
import org.junit.Test;
1619
import org.junit.runner.RunWith;
@@ -21,6 +24,7 @@
2124
import org.powermock.modules.junit4.PowerMockRunner;
2225
import org.powermock.reflect.Whitebox;
2326

27+
import com.google.common.collect.Lists;
2428
import com.stumbleupon.async.Deferred;
2529

2630
@RunWith(PowerMockRunner.class)
@@ -313,6 +317,139 @@ public void locateRegionLookupInZK() throws Exception {
313317
assertCounters(0, 0, 0);
314318
}
315319

320+
//--------------- TABLE LOOKUP IN META --------------
321+
322+
@Test
323+
public void locateRegionInMeta() throws Exception {
324+
clearCaches();
325+
326+
Whitebox.setInternalState(client, "has_root", false);
327+
328+
when(rootclient.isAlive()).thenReturn(true);
329+
when(rootclient.acquireMetaLookupPermit()).thenReturn(true);
330+
when(rootclient.getClosestRowBefore(any(RegionInfo.class), any(byte[].class),
331+
any(byte[].class), any(byte[].class)))
332+
.thenReturn(Deferred.<ArrayList<KeyValue>>fromResult(metaRow()));
333+
334+
final Deferred<Object> obj = Whitebox.invokeMethod(client, "locateRegion",
335+
get, TABLE, KEY);
336+
assertTrue(root_deferred != obj);
337+
final RegionClient rc = (RegionClient) obj.join(1);
338+
assertCounters(0, 1, 0);
339+
assertEquals(1, client2regions.size());
340+
assertNotNull(client2regions.get(rc));
341+
}
342+
343+
@Test
344+
public void locateRegionInMetaNoSuchTable() throws Exception {
345+
clearCaches();
346+
347+
Whitebox.setInternalState(client, "has_root", false);
348+
349+
when(rootclient.isAlive()).thenReturn(true);
350+
when(rootclient.acquireMetaLookupPermit()).thenReturn(true);
351+
when(rootclient.getClosestRowBefore(any(RegionInfo.class), any(byte[].class),
352+
any(byte[].class), any(byte[].class)))
353+
.thenReturn(Deferred.<ArrayList<KeyValue>>fromResult(new ArrayList<KeyValue>(0)));
354+
355+
final Deferred<Object> obj = Whitebox.invokeMethod(client, "locateRegion",
356+
get, HBaseClient.META, EMPTY_ARRAY);
357+
assertTrue(root_deferred != obj);
358+
try {
359+
obj.join(1);
360+
fail("Expected TableNotFoundException");
361+
} catch (TableNotFoundException e) { }
362+
assertCounters(0, 1, 0);
363+
assertEquals(0, client2regions.size());
364+
}
365+
366+
@Test
367+
public void locateRegionInMetaScan() throws Exception {
368+
clearCaches();
369+
370+
Whitebox.setInternalState(client, "has_root", false);
371+
Whitebox.setInternalState(client, "scan_meta", true);
372+
373+
when(rootclient.isAlive()).thenReturn(true);
374+
when(rootclient.acquireMetaLookupPermit()).thenReturn(true);
375+
doAnswer(new Answer<Void>() {
376+
@Override
377+
public Void answer(InvocationOnMock invocation) throws Throwable {
378+
final ArrayList<ArrayList<KeyValue>> rows = Lists.newArrayList();
379+
rows.add(metaRow());
380+
((HBaseRpc) invocation.getArguments()[0]).getDeferred()
381+
.callback(new Response(0, rows, false, true));
382+
return null;
383+
}
384+
}).when(rootclient).sendRpc(any(OpenScannerRequest.class));
385+
386+
final Deferred<Object> obj = Whitebox.invokeMethod(client, "locateRegion",
387+
get, TABLE, KEY);
388+
assertTrue(root_deferred != obj);
389+
final RegionClient rc = (RegionClient) obj.join(1);
390+
assertCounters(0, 1, 0);
391+
assertEquals(1, client2regions.size());
392+
assertNotNull(client2regions.get(rc));
393+
}
394+
395+
@Test
396+
public void locateRegionInMetaScanNoSuchTable() throws Exception {
397+
clearCaches();
398+
399+
Whitebox.setInternalState(client, "has_root", false);
400+
Whitebox.setInternalState(client, "scan_meta", true);
401+
402+
when(rootclient.isAlive()).thenReturn(true);
403+
when(rootclient.acquireMetaLookupPermit()).thenReturn(true);
404+
doAnswer(new Answer<Void>() {
405+
@Override
406+
public Void answer(InvocationOnMock invocation) throws Throwable {
407+
((HBaseRpc) invocation.getArguments()[0]).getDeferred()
408+
.callback(new Response(0, null, false, true));
409+
return null;
410+
}
411+
}).when(rootclient).sendRpc(any(OpenScannerRequest.class));
412+
413+
final Deferred<Object> obj = Whitebox.invokeMethod(client, "locateRegion",
414+
get, TABLE, KEY);
415+
assertTrue(root_deferred != obj);
416+
try {
417+
obj.join(1);
418+
fail("Expected TableNotFoundException");
419+
} catch (TableNotFoundException e) { }
420+
assertCounters(0, 1, 0);
421+
assertEquals(0, client2regions.size());
422+
}
423+
424+
@Test
425+
public void locateRegionInMetaScanException() throws Exception {
426+
clearCaches();
427+
428+
Whitebox.setInternalState(client, "has_root", false);
429+
Whitebox.setInternalState(client, "scan_meta", true);
430+
431+
when(rootclient.isAlive()).thenReturn(true);
432+
when(rootclient.acquireMetaLookupPermit()).thenReturn(true);
433+
doAnswer(new Answer<Void>() {
434+
@Override
435+
public Void answer(InvocationOnMock invocation) throws Throwable {
436+
((HBaseRpc) invocation.getArguments()[0]).getDeferred()
437+
.callback(new NonRecoverableException("Boo!"));
438+
return null;
439+
}
440+
}).when(rootclient).sendRpc(any(OpenScannerRequest.class));
441+
442+
final Deferred<Object> obj = Whitebox.invokeMethod(client, "locateRegion",
443+
get, TABLE, KEY);
444+
assertTrue(root_deferred != obj);
445+
try {
446+
obj.join(1);
447+
fail("Expected NonRecoverableException");
448+
} catch (NonRecoverableException e) { }
449+
assertCounters(0, 1, 0);
450+
assertEquals(0, client2regions.size());
451+
}
452+
316453
// ---------- PARAMS -----------
317454
@Test (expected = NullPointerException.class)
318455
public void locateRegionNullTable() throws Exception {

0 commit comments

Comments
 (0)