diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobConstants.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobConstants.java index dd33cda288c..4dfb7b6cb94 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobConstants.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobConstants.java @@ -35,6 +35,7 @@ public class MobConstants { public static final String MOB_SCAN_RAW = "hbase.mob.scan.raw"; public static final String MOB_CACHE_BLOCKS = "hbase.mob.cache.blocks"; public static final String MOB_SCAN_REF_ONLY = "hbase.mob.scan.ref.only"; + public static final String EMPTY_VALUE_ON_MOBCELL_MISS = "empty.value.on.mobcell.miss"; public static final String MOB_FILE_CACHE_SIZE_KEY = "hbase.mob.file.cache.size"; public static final int DEFAULT_MOB_FILE_CACHE_SIZE = 1000; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobUtils.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobUtils.java index c40767c35ca..53cd1a14e3c 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobUtils.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mob/MobUtils.java @@ -855,4 +855,19 @@ public class MobUtils { } return false; } + + /** + * Indicates whether return null value when the mob file is missing or corrupt. + * The information is set in the attribute "empty.value.on.mobcell.miss" of scan. + * @param scan The current scan. + * @return True if the readEmptyValueOnMobCellMiss is enabled. + */ + public static boolean isReadEmptyValueOnMobCellMiss(Scan scan) { + byte[] readEmptyValueOnMobCellMiss = scan.getAttribute(MobConstants.EMPTY_VALUE_ON_MOBCELL_MISS); + try { + return readEmptyValueOnMobCellMiss != null && Bytes.toBoolean(readEmptyValueOnMobCellMiss); + } catch (IllegalArgumentException e) { + return false; + } + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HMobStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HMobStore.java index 4a782dd8d54..8f126561c60 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HMobStore.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HMobStore.java @@ -46,6 +46,7 @@ import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.filter.FilterList; import org.apache.hadoop.hbase.io.compress.Compression; import org.apache.hadoop.hbase.io.hfile.CacheConfig; +import org.apache.hadoop.hbase.io.hfile.CorruptHFileException; import org.apache.hadoop.hbase.io.hfile.HFileContext; import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder; import org.apache.hadoop.hbase.master.TableLockManager; @@ -310,13 +311,14 @@ public class HMobStore extends HStore { /** * Reads the cell from the mob file, and the read point does not count. + * This is used for DefaultMobStoreCompactor where we can read empty value for the missing cell. * @param reference The cell found in the HBase, its value is a path to a mob file. * @param cacheBlocks Whether the scanner should cache blocks. * @return The cell found in the mob file. * @throws IOException */ public Cell resolve(Cell reference, boolean cacheBlocks) throws IOException { - return resolve(reference, cacheBlocks, -1); + return resolve(reference, cacheBlocks, -1, true); } /** @@ -324,10 +326,13 @@ public class HMobStore extends HStore { * @param reference The cell found in the HBase, its value is a path to a mob file. * @param cacheBlocks Whether the scanner should cache blocks. * @param readPt the read point. + * @param readEmptyValueOnMobCellMiss Whether return null value when the mob file is + * missing or corrupt. * @return The cell found in the mob file. * @throws IOException */ - public Cell resolve(Cell reference, boolean cacheBlocks, long readPt) throws IOException { + public Cell resolve(Cell reference, boolean cacheBlocks, long readPt, + boolean readEmptyValueOnMobCellMiss) throws IOException { Cell result = null; if (MobUtils.hasValidMobRefCellValue(reference)) { String fileName = MobUtils.getMobFileName(reference); @@ -352,7 +357,8 @@ public class HMobStore extends HStore { keyLock.releaseLockEntry(lockEntry); } } - result = readCell(locations, fileName, reference, cacheBlocks, readPt); + result = readCell(locations, fileName, reference, cacheBlocks, readPt, + readEmptyValueOnMobCellMiss); } } if (result == null) { @@ -380,12 +386,15 @@ public class HMobStore extends HStore { * @param search The cell to be searched. * @param cacheMobBlocks Whether the scanner should cache blocks. * @param readPt the read point. + * @param readEmptyValueOnMobCellMiss Whether return null value when the mob file is + * missing or corrupt. * @return The found cell. Null if there's no such a cell. * @throws IOException */ private Cell readCell(List locations, String fileName, Cell search, boolean cacheMobBlocks, - long readPt) throws IOException { + long readPt, boolean readEmptyValueOnMobCellMiss) throws IOException { FileSystem fs = getFileSystem(); + Throwable throwable = null; for (Path location : locations) { MobFile file = null; Path path = new Path(location, fileName); @@ -395,27 +404,39 @@ public class HMobStore extends HStore { cacheMobBlocks); } catch (IOException e) { mobCacheConfig.getMobFileCache().evictFile(fileName); + throwable = e; if ((e instanceof FileNotFoundException) || (e.getCause() instanceof FileNotFoundException)) { LOG.warn("Fail to read the cell, the mob file " + path + " doesn't exist", e); + } else if (e instanceof CorruptHFileException) { + LOG.error("The mob file " + path + " is corrupt", e); + break; } else { throw e; } - } catch (NullPointerException e) { + } catch (NullPointerException e) { // HDFS 1.x - DFSInputStream.getBlockAt() mobCacheConfig.getMobFileCache().evictFile(fileName); LOG.warn("Fail to read the cell", e); - } catch (AssertionError e) { + throwable = e; + } catch (AssertionError e) { // assert in HDFS 1.x - DFSInputStream.getBlockAt() mobCacheConfig.getMobFileCache().evictFile(fileName); LOG.warn("Fail to read the cell", e); + throwable = e; } finally { if (file != null) { mobCacheConfig.getMobFileCache().closeFile(file); } } } - LOG.error("The mob file " + fileName + " could not be found in the locations " - + locations); - return null; + LOG.error("The mob file " + fileName + " could not be found in the locations " + locations + + " or it is corrupt"); + if (readEmptyValueOnMobCellMiss) { + return null; + } else if (throwable instanceof IOException) { + throw (IOException) throwable; + } else { + throw new IOException(throwable); + } } /** diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MobStoreScanner.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MobStoreScanner.java index 0521cce53c0..46bbfd52daf 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MobStoreScanner.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MobStoreScanner.java @@ -36,12 +36,16 @@ import org.apache.hadoop.hbase.mob.MobUtils; public class MobStoreScanner extends StoreScanner { private boolean cacheMobBlocks = false; + private boolean rawMobScan = false; + private boolean readEmptyValueOnMobCellMiss = false; private final HMobStore mobStore; public MobStoreScanner(Store store, ScanInfo scanInfo, Scan scan, final NavigableSet columns, long readPt) throws IOException { super(store, scanInfo, scan, columns, readPt); cacheMobBlocks = MobUtils.isCacheMobBlocks(scan); + rawMobScan = MobUtils.isRawMobScan(scan); + readEmptyValueOnMobCellMiss = MobUtils.isReadEmptyValueOnMobCellMiss(scan); if (!(store instanceof HMobStore)) { throw new IllegalArgumentException("The store " + store + " is not a HMobStore"); } @@ -56,7 +60,7 @@ public class MobStoreScanner extends StoreScanner { @Override public boolean next(List outResult, ScannerContext ctx) throws IOException { boolean result = super.next(outResult, ctx); - if (!MobUtils.isRawMobScan(scan)) { + if (!rawMobScan) { // retrieve the mob data if (outResult.isEmpty()) { return result; @@ -66,7 +70,8 @@ public class MobStoreScanner extends StoreScanner { for (int i = 0; i < outResult.size(); i++) { Cell cell = outResult.get(i); if (MobUtils.isMobReferenceCell(cell)) { - Cell mobCell = mobStore.resolve(cell, cacheMobBlocks, readPt); + Cell mobCell = mobStore + .resolve(cell, cacheMobBlocks, readPt, readEmptyValueOnMobCellMiss); mobKVCount++; mobKVSize += mobCell.getValueLength(); outResult.set(i, mobCell); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReversedMobStoreScanner.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReversedMobStoreScanner.java index aa36e3e3322..78c17209214 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReversedMobStoreScanner.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReversedMobStoreScanner.java @@ -36,12 +36,16 @@ import org.apache.hadoop.hbase.mob.MobUtils; public class ReversedMobStoreScanner extends ReversedStoreScanner { private boolean cacheMobBlocks = false; + private boolean rawMobScan = false; + private boolean readEmptyValueOnMobCellMiss = false; protected final HMobStore mobStore; ReversedMobStoreScanner(Store store, ScanInfo scanInfo, Scan scan, NavigableSet columns, long readPt) throws IOException { super(store, scanInfo, scan, columns, readPt); cacheMobBlocks = MobUtils.isCacheMobBlocks(scan); + rawMobScan = MobUtils.isRawMobScan(scan); + readEmptyValueOnMobCellMiss = MobUtils.isReadEmptyValueOnMobCellMiss(scan); if (!(store instanceof HMobStore)) { throw new IllegalArgumentException("The store " + store + " is not a HMobStore"); } @@ -56,7 +60,7 @@ public class ReversedMobStoreScanner extends ReversedStoreScanner { @Override public boolean next(List outResult, ScannerContext ctx) throws IOException { boolean result = super.next(outResult, ctx); - if (!MobUtils.isRawMobScan(scan)) { + if (!rawMobScan) { // retrieve the mob data if (outResult.isEmpty()) { return result; @@ -66,7 +70,8 @@ public class ReversedMobStoreScanner extends ReversedStoreScanner { for (int i = 0; i < outResult.size(); i++) { Cell cell = outResult.get(i); if (MobUtils.isMobReferenceCell(cell)) { - Cell mobCell = mobStore.resolve(cell, cacheMobBlocks, readPt); + Cell mobCell = mobStore + .resolve(cell, cacheMobBlocks, readPt, readEmptyValueOnMobCellMiss); mobKVCount++; mobKVSize += mobCell.getValueLength(); outResult.set(i, mobCell); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMobStoreScanner.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMobStoreScanner.java index 01b6a5f8ffd..820087a5339 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMobStoreScanner.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMobStoreScanner.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.List; import java.util.Random; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -40,6 +41,8 @@ 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.io.hfile.CorruptHFileException; +import org.apache.hadoop.hbase.io.hfile.TestHFile; import org.apache.hadoop.hbase.mob.MobConstants; import org.apache.hadoop.hbase.mob.MobUtils; import org.apache.hadoop.hbase.testclassification.MediumTests; @@ -68,6 +71,8 @@ public class TestMobStoreScanner { private static HTableDescriptor desc; private static Random random = new Random(); private static long defaultThreshold = 10; + private FileSystem fs; + private Configuration conf; @BeforeClass public static void setUpBeforeClass() throws Exception { @@ -84,6 +89,8 @@ public class TestMobStoreScanner { } public void setUp(long threshold, TableName tn) throws Exception { + conf = TEST_UTIL.getConfiguration(); + fs = FileSystem.get(conf); desc = new HTableDescriptor(tn); hcd = new HColumnDescriptor(family); hcd.setMobEnabled(true); @@ -196,6 +203,62 @@ public class TestMobStoreScanner { Assert.assertEquals("value2", Bytes.toString(cell.getValue())); } + @Test + public void testReadFromCorruptMobFilesWithReadEmptyValueOnMobCellMiss() throws Exception { + TableName tn = TableName.valueOf("testReadFromCorruptMobFilesWithReadEmptyValueOnMobCellMiss"); + setUp(0, tn); + createRecordAndCorruptMobFile(tn, row1, family, qf1, Bytes.toBytes("value1")); + Get get = new Get(row1); + get.setAttribute(MobConstants.EMPTY_VALUE_ON_MOBCELL_MISS, Bytes.toBytes(true)); + Result result = table.get(get); + Cell cell = result.getColumnLatestCell(family, qf1); + Assert.assertEquals(0, CellUtil.cloneValue(cell).length); + } + + @Test + public void testReadFromCorruptMobFiles() throws Exception { + TableName tn = TableName.valueOf("testReadFromCorruptMobFiles"); + setUp(0, tn); + createRecordAndCorruptMobFile(tn, row1, family, qf1, Bytes.toBytes("value1")); + Get get = new Get(row1); + IOException ioe = null; + try { + table.get(get); + } catch (IOException e) { + ioe = e; + } + Assert.assertNotNull(ioe); + Assert.assertEquals(CorruptHFileException.class.getName(), ioe.getClass().getName()); + } + + private void createRecordAndCorruptMobFile(TableName tn, byte[] row, byte[] family, byte[] qf, + byte[] value) throws IOException { + Put put1 = new Put(row); + put1.addColumn(family, qf, value); + table.put(put1); + admin.flush(tn); + Path mobFile = getFlushedMobFile(conf, fs, tn, Bytes.toString(family)); + Assert.assertNotNull(mobFile); + // create new corrupt mob file. + Path corruptFile = new Path(mobFile.getParent(), "dummy"); + TestHFile.truncateFile(fs, mobFile, corruptFile); + fs.delete(mobFile, true); + fs.rename(corruptFile, mobFile); + } + + private Path getFlushedMobFile(Configuration conf, FileSystem fs, TableName table, String family) + throws IOException { + Path regionDir = MobUtils.getMobRegionPath(conf, table); + Path famDir = new Path(regionDir, family); + FileStatus[] hfFss = fs.listStatus(famDir); + for (FileStatus hfs : hfFss) { + if (!hfs.isDirectory()) { + return hfs.getPath(); + } + } + return null; + } + private void testGetFromFiles(boolean reversed) throws Exception { TableName tn = TableName.valueOf("testGetFromFiles" + reversed); setUp(defaultThreshold, tn);