001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.regionserver; 019 020import java.io.IOException; 021import java.util.List; 022import java.util.Random; 023import org.apache.hadoop.conf.Configuration; 024import org.apache.hadoop.fs.FileStatus; 025import org.apache.hadoop.fs.FileSystem; 026import org.apache.hadoop.fs.Path; 027import org.apache.hadoop.hbase.Cell; 028import org.apache.hadoop.hbase.CellUtil; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.HColumnDescriptor; 032import org.apache.hadoop.hbase.HTableDescriptor; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.client.Admin; 035import org.apache.hadoop.hbase.client.ConnectionConfiguration; 036import org.apache.hadoop.hbase.client.ConnectionFactory; 037import org.apache.hadoop.hbase.client.Get; 038import org.apache.hadoop.hbase.client.Put; 039import org.apache.hadoop.hbase.client.RegionInfo; 040import org.apache.hadoop.hbase.client.Result; 041import org.apache.hadoop.hbase.client.ResultScanner; 042import org.apache.hadoop.hbase.client.Scan; 043import org.apache.hadoop.hbase.client.Table; 044import org.apache.hadoop.hbase.io.hfile.CorruptHFileException; 045import org.apache.hadoop.hbase.io.hfile.TestHFile; 046import org.apache.hadoop.hbase.mob.MobConstants; 047import org.apache.hadoop.hbase.mob.MobTestUtil; 048import org.apache.hadoop.hbase.mob.MobUtils; 049import org.apache.hadoop.hbase.testclassification.MediumTests; 050import org.apache.hadoop.hbase.util.Bytes; 051import org.apache.hadoop.hbase.util.FSUtils; 052import org.apache.hadoop.hbase.util.HFileArchiveUtil; 053import org.junit.AfterClass; 054import org.junit.Assert; 055import org.junit.BeforeClass; 056import org.junit.ClassRule; 057import org.junit.Rule; 058import org.junit.Test; 059import org.junit.experimental.categories.Category; 060import org.junit.rules.TestName; 061 062@Category(MediumTests.class) 063public class TestMobStoreScanner { 064 065 @ClassRule 066 public static final HBaseClassTestRule CLASS_RULE = 067 HBaseClassTestRule.forClass(TestMobStoreScanner.class); 068 069 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 070 private final static byte [] row1 = Bytes.toBytes("row1"); 071 private final static byte [] row2 = Bytes.toBytes("row2"); 072 private final static byte [] family = Bytes.toBytes("family"); 073 private final static byte [] qf1 = Bytes.toBytes("qualifier1"); 074 private final static byte [] qf2 = Bytes.toBytes("qualifier2"); 075 protected final byte[] qf3 = Bytes.toBytes("qualifier3"); 076 private static Table table; 077 private static Admin admin; 078 private static HColumnDescriptor hcd; 079 private static HTableDescriptor desc; 080 private static Random random = new Random(); 081 private static long defaultThreshold = 10; 082 private FileSystem fs; 083 private Configuration conf; 084 085 @Rule 086 public TestName name = new TestName(); 087 088 @BeforeClass 089 public static void setUpBeforeClass() throws Exception { 090 TEST_UTIL.getConfiguration().setInt(ConnectionConfiguration.MAX_KEYVALUE_SIZE_KEY, 091 100 * 1024 * 1024); 092 TEST_UTIL.getConfiguration().setInt(HRegion.HBASE_MAX_CELL_SIZE_KEY, 100 * 1024 * 1024); 093 TEST_UTIL.startMiniCluster(1); 094 } 095 096 @AfterClass 097 public static void tearDownAfterClass() throws Exception { 098 TEST_UTIL.shutdownMiniCluster(); 099 } 100 101 public void setUp(long threshold, TableName tn) throws Exception { 102 conf = TEST_UTIL.getConfiguration(); 103 fs = FileSystem.get(conf); 104 desc = new HTableDescriptor(tn); 105 hcd = new HColumnDescriptor(family); 106 hcd.setMobEnabled(true); 107 hcd.setMobThreshold(threshold); 108 hcd.setMaxVersions(4); 109 desc.addFamily(hcd); 110 admin = TEST_UTIL.getAdmin(); 111 admin.createTable(desc); 112 table = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()) 113 .getTable(tn); 114 } 115 116 /** 117 * Generate the mob value. 118 * 119 * @param size the size of the value 120 * @return the mob value generated 121 */ 122 private static byte[] generateMobValue(int size) { 123 byte[] mobVal = new byte[size]; 124 random.nextBytes(mobVal); 125 return mobVal; 126 } 127 128 /** 129 * Set the scan attribute 130 * 131 * @param reversed if true, scan will be backward order 132 * @param mobScanRaw if true, scan will get the mob reference 133 * @return this 134 */ 135 public void setScan(Scan scan, boolean reversed, boolean mobScanRaw) { 136 scan.setReversed(reversed); 137 scan.setMaxVersions(4); 138 if(mobScanRaw) { 139 scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE)); 140 } 141 } 142 143 @Test 144 public void testMobStoreScanner() throws Exception { 145 testGetFromFiles(false); 146 testGetFromMemStore(false); 147 testGetReferences(false); 148 testMobThreshold(false); 149 testGetFromArchive(false); 150 } 151 152 @Test 153 public void testReversedMobStoreScanner() throws Exception { 154 testGetFromFiles(true); 155 testGetFromMemStore(true); 156 testGetReferences(true); 157 testMobThreshold(true); 158 testGetFromArchive(true); 159 } 160 161 @Test 162 public void testGetMassive() throws Exception { 163 setUp(defaultThreshold, TableName.valueOf(name.getMethodName())); 164 165 // Put some data 5 10, 15, 20 mb ok (this would be right below protobuf 166 // default max size of 64MB. 167 // 25, 30, 40 fail. these is above protobuf max size of 64MB 168 byte[] bigValue = new byte[25*1024*1024]; 169 170 Put put = new Put(row1); 171 put.addColumn(family, qf1, bigValue); 172 put.addColumn(family, qf2, bigValue); 173 put.addColumn(family, qf3, bigValue); 174 table.put(put); 175 176 Get g = new Get(row1); 177 table.get(g); 178 // should not have blown up. 179 } 180 181 @Test 182 public void testReadPt() throws Exception { 183 final TableName tableName = TableName.valueOf(name.getMethodName()); 184 setUp(0L, tableName); 185 long ts = System.currentTimeMillis(); 186 byte[] value1 = Bytes.toBytes("value1"); 187 Put put1 = new Put(row1); 188 put1.addColumn(family, qf1, ts, value1); 189 table.put(put1); 190 Put put2 = new Put(row2); 191 byte[] value2 = Bytes.toBytes("value2"); 192 put2.addColumn(family, qf1, ts, value2); 193 table.put(put2); 194 195 Scan scan = new Scan(); 196 scan.setCaching(1); 197 ResultScanner rs = table.getScanner(scan); 198 Result result = rs.next(); 199 Put put3 = new Put(row1); 200 byte[] value3 = Bytes.toBytes("value3"); 201 put3.addColumn(family, qf1, ts, value3); 202 table.put(put3); 203 Put put4 = new Put(row2); 204 byte[] value4 = Bytes.toBytes("value4"); 205 put4.addColumn(family, qf1, ts, value4); 206 table.put(put4); 207 208 Cell cell = result.getColumnLatestCell(family, qf1); 209 Assert.assertArrayEquals(value1, CellUtil.cloneValue(cell)); 210 211 admin.flush(tableName); 212 result = rs.next(); 213 cell = result.getColumnLatestCell(family, qf1); 214 Assert.assertArrayEquals(value2, CellUtil.cloneValue(cell)); 215 } 216 217 @Test 218 public void testReadFromCorruptMobFilesWithReadEmptyValueOnMobCellMiss() throws Exception { 219 final TableName tableName = TableName.valueOf(name.getMethodName()); 220 setUp(0, tableName); 221 createRecordAndCorruptMobFile(tableName, row1, family, qf1, Bytes.toBytes("value1")); 222 Get get = new Get(row1); 223 get.setAttribute(MobConstants.EMPTY_VALUE_ON_MOBCELL_MISS, Bytes.toBytes(true)); 224 Result result = table.get(get); 225 Cell cell = result.getColumnLatestCell(family, qf1); 226 Assert.assertEquals(0, cell.getValueLength()); 227 } 228 229 @Test 230 public void testReadFromCorruptMobFiles() throws Exception { 231 final TableName tableName = TableName.valueOf(name.getMethodName()); 232 setUp(0, tableName); 233 createRecordAndCorruptMobFile(tableName, row1, family, qf1, Bytes.toBytes("value1")); 234 Get get = new Get(row1); 235 IOException ioe = null; 236 try { 237 table.get(get); 238 } catch (IOException e) { 239 ioe = e; 240 } 241 Assert.assertNotNull(ioe); 242 Assert.assertEquals(CorruptHFileException.class.getName(), ioe.getClass().getName()); 243 } 244 245 private void createRecordAndCorruptMobFile(TableName tn, byte[] row, byte[] family, byte[] qf, 246 byte[] value) throws IOException { 247 Put put1 = new Put(row); 248 put1.addColumn(family, qf, value); 249 table.put(put1); 250 admin.flush(tn); 251 Path mobFile = getFlushedMobFile(conf, fs, tn, Bytes.toString(family)); 252 Assert.assertNotNull(mobFile); 253 // create new corrupt mob file. 254 Path corruptFile = new Path(mobFile.getParent(), "dummy"); 255 TestHFile.truncateFile(fs, mobFile, corruptFile); 256 fs.delete(mobFile, true); 257 fs.rename(corruptFile, mobFile); 258 } 259 260 private Path getFlushedMobFile(Configuration conf, FileSystem fs, TableName table, String family) 261 throws IOException { 262 Path famDir = MobUtils.getMobFamilyPath(conf, table, family); 263 FileStatus[] hfFss = fs.listStatus(famDir); 264 for (FileStatus hfs : hfFss) { 265 if (!hfs.isDirectory()) { 266 return hfs.getPath(); 267 } 268 } 269 return null; 270 } 271 272 private void testGetFromFiles(boolean reversed) throws Exception { 273 TableName tn = TableName.valueOf("testGetFromFiles" + reversed); 274 testGet(tn, reversed, true); 275 } 276 277 private void testGetFromMemStore(boolean reversed) throws Exception { 278 TableName tn = TableName.valueOf("testGetFromMemStore" + reversed); 279 testGet(tn, reversed, false); 280 } 281 282 private void testGet(TableName tableName, boolean reversed, boolean doFlush) 283 throws Exception { 284 setUp(defaultThreshold, tableName); 285 long ts1 = System.currentTimeMillis(); 286 long ts2 = ts1 + 1; 287 long ts3 = ts1 + 2; 288 byte [] value = generateMobValue((int)defaultThreshold+1); 289 290 Put put1 = new Put(row1); 291 put1.addColumn(family, qf1, ts3, value); 292 put1.addColumn(family, qf2, ts2, value); 293 put1.addColumn(family, qf3, ts1, value); 294 table.put(put1); 295 296 if (doFlush) { 297 admin.flush(tableName); 298 } 299 300 Scan scan = new Scan(); 301 setScan(scan, reversed, false); 302 MobTestUtil.assertCellsValue(table, scan, value, 3); 303 } 304 305 private void testGetReferences(boolean reversed) throws Exception { 306 TableName tn = TableName.valueOf("testGetReferences" + reversed); 307 setUp(defaultThreshold, tn); 308 long ts1 = System.currentTimeMillis(); 309 long ts2 = ts1 + 1; 310 long ts3 = ts1 + 2; 311 byte [] value = generateMobValue((int)defaultThreshold+1);; 312 313 Put put1 = new Put(row1); 314 put1.addColumn(family, qf1, ts3, value); 315 put1.addColumn(family, qf2, ts2, value); 316 put1.addColumn(family, qf3, ts1, value); 317 table.put(put1); 318 319 admin.flush(tn); 320 321 Scan scan = new Scan(); 322 setScan(scan, reversed, true); 323 324 ResultScanner results = table.getScanner(scan); 325 int count = 0; 326 for (Result res : results) { 327 List<Cell> cells = res.listCells(); 328 for(Cell cell : cells) { 329 // Verify the value 330 assertIsMobReference(cell, row1, family, value, tn); 331 count++; 332 } 333 } 334 results.close(); 335 Assert.assertEquals(3, count); 336 } 337 338 private void testMobThreshold(boolean reversed) throws Exception { 339 TableName tn = TableName.valueOf("testMobThreshold" + reversed); 340 setUp(defaultThreshold, tn); 341 byte [] valueLess = generateMobValue((int)defaultThreshold-1); 342 byte [] valueEqual = generateMobValue((int)defaultThreshold); 343 byte [] valueGreater = generateMobValue((int)defaultThreshold+1); 344 long ts1 = System.currentTimeMillis(); 345 long ts2 = ts1 + 1; 346 long ts3 = ts1 + 2; 347 348 Put put1 = new Put(row1); 349 put1.addColumn(family, qf1, ts3, valueLess); 350 put1.addColumn(family, qf2, ts2, valueEqual); 351 put1.addColumn(family, qf3, ts1, valueGreater); 352 table.put(put1); 353 354 admin.flush(tn); 355 356 Scan scan = new Scan(); 357 setScan(scan, reversed, true); 358 359 Cell cellLess= null; 360 Cell cellEqual = null; 361 Cell cellGreater = null; 362 ResultScanner results = table.getScanner(scan); 363 int count = 0; 364 for (Result res : results) { 365 List<Cell> cells = res.listCells(); 366 for(Cell cell : cells) { 367 // Verify the value 368 String qf = Bytes.toString(CellUtil.cloneQualifier(cell)); 369 if(qf.equals(Bytes.toString(qf1))) { 370 cellLess = cell; 371 } 372 if(qf.equals(Bytes.toString(qf2))) { 373 cellEqual = cell; 374 } 375 if(qf.equals(Bytes.toString(qf3))) { 376 cellGreater = cell; 377 } 378 count++; 379 } 380 } 381 Assert.assertEquals(3, count); 382 assertNotMobReference(cellLess, row1, family, valueLess); 383 assertNotMobReference(cellEqual, row1, family, valueEqual); 384 assertIsMobReference(cellGreater, row1, family, valueGreater, tn); 385 results.close(); 386 } 387 388 private void testGetFromArchive(boolean reversed) throws Exception { 389 TableName tn = TableName.valueOf("testGetFromArchive" + reversed); 390 setUp(defaultThreshold, tn); 391 long ts1 = System.currentTimeMillis(); 392 long ts2 = ts1 + 1; 393 long ts3 = ts1 + 2; 394 byte [] value = generateMobValue((int)defaultThreshold+1);; 395 // Put some data 396 Put put1 = new Put(row1); 397 put1.addColumn(family, qf1, ts3, value); 398 put1.addColumn(family, qf2, ts2, value); 399 put1.addColumn(family, qf3, ts1, value); 400 table.put(put1); 401 402 admin.flush(tn); 403 404 // Get the files in the mob path 405 Path mobFamilyPath; 406 mobFamilyPath = MobUtils.getMobFamilyPath( 407 TEST_UTIL.getConfiguration(), tn, hcd.getNameAsString()); 408 FileSystem fs = FileSystem.get(TEST_UTIL.getConfiguration()); 409 FileStatus[] files = fs.listStatus(mobFamilyPath); 410 411 // Get the archive path 412 Path rootDir = FSUtils.getRootDir(TEST_UTIL.getConfiguration()); 413 Path tableDir = FSUtils.getTableDir(rootDir, tn); 414 RegionInfo regionInfo = MobUtils.getMobRegionInfo(tn); 415 Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(TEST_UTIL.getConfiguration(), 416 regionInfo, tableDir, family); 417 418 // Move the files from mob path to archive path 419 fs.mkdirs(storeArchiveDir); 420 int fileCount = 0; 421 for(FileStatus file : files) { 422 fileCount++; 423 Path filePath = file.getPath(); 424 Path src = new Path(mobFamilyPath, filePath.getName()); 425 Path dst = new Path(storeArchiveDir, filePath.getName()); 426 fs.rename(src, dst); 427 } 428 429 // Verify the moving success 430 FileStatus[] files1 = fs.listStatus(mobFamilyPath); 431 Assert.assertEquals(0, files1.length); 432 FileStatus[] files2 = fs.listStatus(storeArchiveDir); 433 Assert.assertEquals(fileCount, files2.length); 434 435 // Scan from archive 436 Scan scan = new Scan(); 437 setScan(scan, reversed, false); 438 MobTestUtil.assertCellsValue(table, scan, value, 3); 439 } 440 441 /** 442 * Assert the value is not store in mob. 443 */ 444 private static void assertNotMobReference(Cell cell, byte[] row, byte[] family, 445 byte[] value) throws IOException { 446 Assert.assertArrayEquals(row, CellUtil.cloneRow(cell)); 447 Assert.assertArrayEquals(family, CellUtil.cloneFamily(cell)); 448 Assert.assertArrayEquals(value, CellUtil.cloneValue(cell)); 449 } 450 451 /** 452 * Assert the value is store in mob. 453 */ 454 private static void assertIsMobReference(Cell cell, byte[] row, byte[] family, 455 byte[] value, TableName tn) throws IOException { 456 Assert.assertArrayEquals(row, CellUtil.cloneRow(cell)); 457 Assert.assertArrayEquals(family, CellUtil.cloneFamily(cell)); 458 Assert.assertFalse(Bytes.equals(value, CellUtil.cloneValue(cell))); 459 byte[] referenceValue = CellUtil.cloneValue(cell); 460 String fileName = MobUtils.getMobFileName(cell); 461 int valLen = Bytes.toInt(referenceValue, 0, Bytes.SIZEOF_INT); 462 Assert.assertEquals(value.length, valLen); 463 Path mobFamilyPath = MobUtils.getMobFamilyPath( 464 TEST_UTIL.getConfiguration(), tn, hcd.getNameAsString()); 465 Path targetPath = new Path(mobFamilyPath, fileName); 466 FileSystem fs = FileSystem.get(TEST_UTIL.getConfiguration()); 467 Assert.assertTrue(fs.exists(targetPath)); 468 } 469}