You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by st...@apache.org on 2021/10/30 05:22:22 UTC

[phoenix] 02/06: PHOENIX-6472 In case of region inconsistency phoenix should stop gracefully

This is an automated email from the ASF dual-hosted git repository.

stoty pushed a commit to branch 5.1
in repository https://gitbox.apache.org/repos/asf/phoenix.git

commit f4fff44acfeef1ff2b0485cdfe1d18eb981a4c2d
Author: Xinyi Yan <xy...@salesforce.com>
AuthorDate: Thu Aug 19 09:44:48 2021 -0700

    PHOENIX-6472 In case of region inconsistency phoenix should stop gracefully
---
 .../phoenix/query/ConnectionQueryServicesImpl.java | 16 +++++-
 .../query/ConnectionQueryServicesImplTest.java     | 60 ++++++++++++++++++++++
 2 files changed, 75 insertions(+), 1 deletion(-)

diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
index 9f44b8b..88ca0a6 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
@@ -653,6 +653,20 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
         ((ClusterConnection)connection).clearRegionCache(tableName);
     }
 
+    public byte[] getNextRegionStartKey(HRegionLocation regionLocation, byte[] currentKey) throws IOException {
+        // in order to check the overlap/inconsistencies bad region info, we have to make sure
+        // the current endKey always increasing(compare the previous endKey)
+        if (Bytes.compareTo(regionLocation.getRegionInfo().getEndKey(), currentKey) <= 0
+                && !Bytes.equals(currentKey, HConstants.EMPTY_START_ROW)
+                && !Bytes.equals(regionLocation.getRegionInfo().getEndKey(), HConstants.EMPTY_END_ROW)) {
+            String regionNameString =
+                    new String(regionLocation.getRegionInfo().getRegionName(), StandardCharsets.UTF_8);
+            throw new IOException(String.format(
+                    "HBase region information overlap/inconsistencies on region %s", regionNameString));
+        }
+        return regionLocation.getRegionInfo().getEndKey();
+    }
+
     @Override
     public List<HRegionLocation> getAllTableRegions(byte[] tableName) throws SQLException {
         /*
@@ -671,8 +685,8 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
                 do {
                     HRegionLocation regionLocation = ((ClusterConnection)connection).getRegionLocation(
                             TableName.valueOf(tableName), currentKey, reload);
+                    currentKey = getNextRegionStartKey(regionLocation, currentKey);
                     locations.add(regionLocation);
-                    currentKey = regionLocation.getRegion().getEndKey();
                 } while (!Bytes.equals(currentKey, HConstants.EMPTY_END_ROW));
                 return locations;
             } catch (org.apache.hadoop.hbase.TableNotFoundException e) {
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/ConnectionQueryServicesImplTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/ConnectionQueryServicesImplTest.java
index df0e1b2..63f60fd 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/ConnectionQueryServicesImplTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/ConnectionQueryServicesImplTest.java
@@ -50,6 +50,8 @@ import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
 import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.Mutation;
 import org.apache.hadoop.hbase.client.Table;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.phoenix.SystemExitRule;
@@ -158,6 +160,64 @@ public class ConnectionQueryServicesImplTest {
     }
 
     @Test
+    public void testGetNextRegionStartKey() {
+        HRegionInfo mockHRegionInfo = org.mockito.Mockito.mock(HRegionInfo.class);
+        HRegionLocation mockRegionLocation = org.mockito.Mockito.mock(HRegionLocation.class);
+        ConnectionQueryServicesImpl mockCqsi = org.mockito.Mockito.mock(ConnectionQueryServicesImpl.class,
+                org.mockito.Mockito.CALLS_REAL_METHODS);
+        byte[] corruptedStartAndEndKey = "0x3000".getBytes();
+        byte[] corruptedDecreasingKey = "0x2999".getBytes();
+        byte[] notCorruptedStartKey = "0x2999".getBytes();
+        byte[] notCorruptedEndKey = "0x3000".getBytes();
+        byte[] notCorruptedNewKey = "0x3001".getBytes();
+        byte[] mockTableName = "dummyTable".getBytes();
+        when(mockRegionLocation.getRegionInfo()).thenReturn(mockHRegionInfo);
+        when(mockHRegionInfo.getRegionName()).thenReturn(mockTableName);
+
+        // comparing the current regionInfo endKey is equal to the previous endKey
+        // [0x3000, Ox3000) vs 0x3000
+        when(mockHRegionInfo.getStartKey()).thenReturn(corruptedStartAndEndKey);
+        when(mockHRegionInfo.getEndKey()).thenReturn(corruptedStartAndEndKey);
+        testGetNextRegionStartKey(mockCqsi, mockRegionLocation, corruptedStartAndEndKey, true);
+
+        // comparing the current regionInfo endKey is less than previous endKey
+        // [0x3000,0x2999) vs 0x3000
+        when(mockHRegionInfo.getStartKey()).thenReturn(corruptedStartAndEndKey);
+        when(mockHRegionInfo.getEndKey()).thenReturn(corruptedDecreasingKey);
+        testGetNextRegionStartKey(mockCqsi, mockRegionLocation, corruptedStartAndEndKey, true);
+
+        // comparing the current regionInfo endKey is greater than the previous endKey
+        // [0x3000,0x3000) vs 0x3001
+        when(mockHRegionInfo.getStartKey()).thenReturn(notCorruptedStartKey);
+        when(mockHRegionInfo.getEndKey()).thenReturn(notCorruptedNewKey);
+        testGetNextRegionStartKey(mockCqsi, mockRegionLocation, notCorruptedEndKey, false);
+
+        // test EMPTY_START_ROW
+        when(mockHRegionInfo.getStartKey()).thenReturn(HConstants.EMPTY_START_ROW);
+        when(mockHRegionInfo.getEndKey()).thenReturn(notCorruptedEndKey);
+        testGetNextRegionStartKey(mockCqsi, mockRegionLocation, HConstants.EMPTY_START_ROW, false);
+
+        //test EMPTY_END_ROW
+        when(mockHRegionInfo.getStartKey()).thenReturn(notCorruptedStartKey);
+        when(mockHRegionInfo.getEndKey()).thenReturn(HConstants.EMPTY_END_ROW);
+        testGetNextRegionStartKey(mockCqsi, mockRegionLocation, notCorruptedStartKey, false);
+    }
+
+    private void testGetNextRegionStartKey(ConnectionQueryServicesImpl mockCqsi,
+                                           HRegionLocation mockRegionLocation, byte[] key, boolean isCorrupted) {
+        try {
+            mockCqsi.getNextRegionStartKey(mockRegionLocation, key);
+            if (isCorrupted) {
+                fail();
+            }
+        } catch (IOException e) {
+            if (!isCorrupted) {
+                fail();
+            }
+        }
+    }
+
+    @Test
     public void testSysMutexCheckReturnsFalseWhenTableAbsent() throws Exception {
         // Override the getDescriptor() call to throw instead
         doThrow(new TableNotFoundException())