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 static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertFalse; 023import static org.junit.Assert.assertNotNull; 024import static org.junit.Assert.assertNull; 025import static org.junit.Assert.assertTrue; 026import static org.junit.Assert.fail; 027import static org.mockito.ArgumentMatchers.any; 028import static org.mockito.Mockito.mock; 029import static org.mockito.Mockito.when; 030 031import java.io.IOException; 032import java.nio.ByteBuffer; 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.List; 038import java.util.Map; 039import java.util.OptionalLong; 040import java.util.TreeSet; 041import java.util.concurrent.atomic.AtomicInteger; 042import org.apache.hadoop.conf.Configuration; 043import org.apache.hadoop.fs.FileSystem; 044import org.apache.hadoop.fs.Path; 045import org.apache.hadoop.hbase.Cell; 046import org.apache.hadoop.hbase.CellUtil; 047import org.apache.hadoop.hbase.HBaseClassTestRule; 048import org.apache.hadoop.hbase.HBaseTestingUtility; 049import org.apache.hadoop.hbase.HConstants; 050import org.apache.hadoop.hbase.HRegionInfo; 051import org.apache.hadoop.hbase.KeyValue; 052import org.apache.hadoop.hbase.KeyValueUtil; 053import org.apache.hadoop.hbase.PrivateCellUtil; 054import org.apache.hadoop.hbase.TableDescriptors; 055import org.apache.hadoop.hbase.TableName; 056import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 057import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 058import org.apache.hadoop.hbase.client.RegionInfo; 059import org.apache.hadoop.hbase.client.RegionInfoBuilder; 060import org.apache.hadoop.hbase.client.Scan; 061import org.apache.hadoop.hbase.client.TableDescriptor; 062import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 063import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper; 064import org.apache.hadoop.hbase.io.HFileLink; 065import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 066import org.apache.hadoop.hbase.io.hfile.BlockCache; 067import org.apache.hadoop.hbase.io.hfile.BlockCacheFactory; 068import org.apache.hadoop.hbase.io.hfile.CacheConfig; 069import org.apache.hadoop.hbase.io.hfile.CacheStats; 070import org.apache.hadoop.hbase.io.hfile.HFileContext; 071import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder; 072import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder; 073import org.apache.hadoop.hbase.io.hfile.HFileInfo; 074import org.apache.hadoop.hbase.io.hfile.HFileScanner; 075import org.apache.hadoop.hbase.io.hfile.ReaderContext; 076import org.apache.hadoop.hbase.io.hfile.ReaderContextBuilder; 077import org.apache.hadoop.hbase.master.MasterServices; 078import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 079import org.apache.hadoop.hbase.testclassification.MediumTests; 080import org.apache.hadoop.hbase.testclassification.RegionServerTests; 081import org.apache.hadoop.hbase.util.BloomFilterFactory; 082import org.apache.hadoop.hbase.util.Bytes; 083import org.apache.hadoop.hbase.util.ChecksumType; 084import org.apache.hadoop.hbase.util.CommonFSUtils; 085import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 086import org.junit.AfterClass; 087import org.junit.Before; 088import org.junit.ClassRule; 089import org.junit.Rule; 090import org.junit.Test; 091import org.junit.experimental.categories.Category; 092import org.junit.rules.TestName; 093import org.mockito.Mockito; 094import org.slf4j.Logger; 095import org.slf4j.LoggerFactory; 096 097import org.apache.hbase.thirdparty.com.google.common.base.Joiner; 098import org.apache.hbase.thirdparty.com.google.common.collect.Iterables; 099import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 100 101/** 102 * Test HStoreFile 103 */ 104@Category({ RegionServerTests.class, MediumTests.class }) 105public class TestHStoreFile { 106 107 @ClassRule 108 public static final HBaseClassTestRule CLASS_RULE = 109 HBaseClassTestRule.forClass(TestHStoreFile.class); 110 111 private static final Logger LOG = LoggerFactory.getLogger(TestHStoreFile.class); 112 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 113 private CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration()); 114 private static String ROOT_DIR = TEST_UTIL.getDataTestDir("TestStoreFile").toString(); 115 private static final ChecksumType CKTYPE = ChecksumType.CRC32C; 116 private static final int CKBYTES = 512; 117 private static String TEST_FAMILY = "cf"; 118 private static final char FIRST_CHAR = 'a'; 119 private static final char LAST_CHAR = 'z'; 120 121 @Rule 122 public TestName name = new TestName(); 123 124 private Configuration conf; 125 private Path testDir; 126 private FileSystem fs; 127 128 @Before 129 public void setUp() throws IOException { 130 conf = TEST_UTIL.getConfiguration(); 131 testDir = TEST_UTIL.getDataTestDir(name.getMethodName()); 132 fs = testDir.getFileSystem(conf); 133 } 134 135 @AfterClass 136 public static void tearDownAfterClass() { 137 TEST_UTIL.cleanupTestDir(); 138 } 139 140 /** 141 * Write a file and then assert that we can read from top and bottom halves using two 142 * HalfMapFiles. 143 */ 144 @Test 145 public void testBasicHalfAndHFileLinkMapFile() throws Exception { 146 final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testBasicHalfAndHFileLinkMapFile")); 147 // The locations of HFileLink refers hfiles only should be consistent with the table dir 148 // create by CommonFSUtils directory, so we should make the region directory under 149 // the mode of CommonFSUtils.getTableDir here. 150 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, 151 CommonFSUtils.getTableDir(CommonFSUtils.getRootDir(conf), hri.getTable()), hri); 152 153 HFileContext meta = new HFileContextBuilder().withBlockSize(2 * 1024).build(); 154 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs) 155 .withFilePath(regionFs.createTempName()).withFileContext(meta).build(); 156 writeStoreFile(writer); 157 158 Path sfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath()); 159 HStoreFile sf = new HStoreFile(this.fs, sfPath, conf, cacheConf, BloomType.NONE, true); 160 checkHalfHFile(regionFs, sf); 161 } 162 163 private void writeStoreFile(final StoreFileWriter writer) throws IOException { 164 writeStoreFile(writer, Bytes.toBytes(name.getMethodName()), 165 Bytes.toBytes(name.getMethodName())); 166 } 167 168 // pick an split point (roughly halfway) 169 byte[] SPLITKEY = new byte[] { (LAST_CHAR + FIRST_CHAR) / 2, FIRST_CHAR }; 170 171 /* 172 * Writes HStoreKey and ImmutableBytes data to passed writer and then closes it. nn 173 */ 174 public static void writeStoreFile(final StoreFileWriter writer, byte[] fam, byte[] qualifier) 175 throws IOException { 176 long now = EnvironmentEdgeManager.currentTime(); 177 try { 178 for (char d = FIRST_CHAR; d <= LAST_CHAR; d++) { 179 for (char e = FIRST_CHAR; e <= LAST_CHAR; e++) { 180 byte[] b = new byte[] { (byte) d, (byte) e }; 181 writer.append(new KeyValue(b, fam, qualifier, now, b)); 182 } 183 } 184 } finally { 185 writer.close(); 186 } 187 } 188 189 /** 190 * Test that our mechanism of writing store files in one region to reference store files in other 191 * regions works. 192 */ 193 @Test 194 public void testReference() throws IOException { 195 final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testReferenceTb")); 196 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, 197 new Path(testDir, hri.getTable().getNameAsString()), hri); 198 199 HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build(); 200 // Make a store file and write data to it. 201 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs) 202 .withFilePath(regionFs.createTempName()).withFileContext(meta).build(); 203 writeStoreFile(writer); 204 205 Path hsfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath()); 206 HStoreFile hsf = new HStoreFile(this.fs, hsfPath, conf, cacheConf, BloomType.NONE, true); 207 hsf.initReader(); 208 StoreFileReader reader = hsf.getReader(); 209 // Split on a row, not in middle of row. Midkey returned by reader 210 // may be in middle of row. Create new one with empty column and 211 // timestamp. 212 byte[] midRow = CellUtil.cloneRow(reader.midKey().get()); 213 byte[] finalRow = CellUtil.cloneRow(reader.getLastKey().get()); 214 hsf.closeStoreFile(true); 215 216 // Make a reference 217 HRegionInfo splitHri = new HRegionInfo(hri.getTable(), null, midRow); 218 Path refPath = splitStoreFile(regionFs, splitHri, TEST_FAMILY, hsf, midRow, true); 219 HStoreFile refHsf = new HStoreFile(this.fs, refPath, conf, cacheConf, BloomType.NONE, true); 220 refHsf.initReader(); 221 // Now confirm that I can read from the reference and that it only gets 222 // keys from top half of the file. 223 HFileScanner s = refHsf.getReader().getScanner(false, false); 224 Cell kv = null; 225 for (boolean first = true; (!s.isSeeked() && s.seekTo()) || s.next();) { 226 ByteBuffer bb = ByteBuffer.wrap(((KeyValue) s.getKey()).getKey()); 227 kv = KeyValueUtil.createKeyValueFromKey(bb); 228 if (first) { 229 assertTrue(Bytes.equals(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength(), midRow, 0, 230 midRow.length)); 231 first = false; 232 } 233 } 234 assertTrue(Bytes.equals(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength(), finalRow, 0, 235 finalRow.length)); 236 } 237 238 @Test 239 public void testStoreFileReference() throws Exception { 240 final RegionInfo hri = 241 RegionInfoBuilder.newBuilder(TableName.valueOf("testStoreFileReference")).build(); 242 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, 243 new Path(testDir, hri.getTable().getNameAsString()), hri); 244 HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build(); 245 246 // Make a store file and write data to it. 247 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs) 248 .withFilePath(regionFs.createTempName()).withFileContext(meta).build(); 249 writeStoreFile(writer); 250 Path hsfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath()); 251 writer.close(); 252 253 HStoreFile file = new HStoreFile(this.fs, hsfPath, conf, cacheConf, BloomType.NONE, true); 254 file.initReader(); 255 StoreFileReader r = file.getReader(); 256 assertNotNull(r); 257 StoreFileScanner scanner = 258 new StoreFileScanner(r, mock(HFileScanner.class), false, false, 0, 0, false); 259 260 // Verify after instantiating scanner refCount is increased 261 assertTrue("Verify file is being referenced", file.isReferencedInReads()); 262 scanner.close(); 263 // Verify after closing scanner refCount is decreased 264 assertFalse("Verify file is not being referenced", file.isReferencedInReads()); 265 } 266 267 @Test 268 public void testEmptyStoreFileRestrictKeyRanges() throws Exception { 269 StoreFileReader reader = mock(StoreFileReader.class); 270 HStore store = mock(HStore.class); 271 byte[] cf = Bytes.toBytes("ty"); 272 ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder.of(cf); 273 when(store.getColumnFamilyDescriptor()).thenReturn(cfd); 274 try (StoreFileScanner scanner = 275 new StoreFileScanner(reader, mock(HFileScanner.class), false, false, 0, 0, true)) { 276 Scan scan = new Scan(); 277 scan.setColumnFamilyTimeRange(cf, 0, 1); 278 assertFalse(scanner.shouldUseScanner(scan, store, 0)); 279 } 280 } 281 282 @Test 283 public void testHFileLink() throws IOException { 284 final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testHFileLinkTb")); 285 // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/ 286 Configuration testConf = new Configuration(this.conf); 287 CommonFSUtils.setRootDir(testConf, testDir); 288 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(testConf, fs, 289 CommonFSUtils.getTableDir(testDir, hri.getTable()), hri); 290 HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build(); 291 292 // Make a store file and write data to it. 293 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs) 294 .withFilePath(regionFs.createTempName()).withFileContext(meta).build(); 295 writeStoreFile(writer); 296 297 Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath()); 298 Path dstPath = new Path(regionFs.getTableDir(), new Path("test-region", TEST_FAMILY)); 299 HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName()); 300 Path linkFilePath = 301 new Path(dstPath, HFileLink.createHFileLinkName(hri, storeFilePath.getName())); 302 303 // Try to open store file from link 304 StoreFileInfo storeFileInfo = new StoreFileInfo(testConf, this.fs, linkFilePath, true); 305 HStoreFile hsf = new HStoreFile(storeFileInfo, BloomType.NONE, cacheConf); 306 assertTrue(storeFileInfo.isLink()); 307 hsf.initReader(); 308 309 // Now confirm that I can read from the link 310 int count = 1; 311 HFileScanner s = hsf.getReader().getScanner(false, false); 312 s.seekTo(); 313 while (s.next()) { 314 count++; 315 } 316 assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count); 317 } 318 319 /** 320 * This test creates an hfile and then the dir structures and files to verify that references to 321 * hfilelinks (created by snapshot clones) can be properly interpreted. 322 */ 323 @Test 324 public void testReferenceToHFileLink() throws IOException { 325 // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/ 326 Configuration testConf = new Configuration(this.conf); 327 CommonFSUtils.setRootDir(testConf, testDir); 328 329 // adding legal table name chars to verify regex handles it. 330 HRegionInfo hri = new HRegionInfo(TableName.valueOf("_original-evil-name")); 331 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(testConf, fs, 332 CommonFSUtils.getTableDir(testDir, hri.getTable()), hri); 333 334 HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build(); 335 // Make a store file and write data to it. <root>/<tablename>/<rgn>/<cf>/<file> 336 StoreFileWriter writer = new StoreFileWriter.Builder(testConf, cacheConf, this.fs) 337 .withFilePath(regionFs.createTempName()).withFileContext(meta).build(); 338 writeStoreFile(writer); 339 Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath()); 340 341 // create link to store file. <root>/clone/region/<cf>/<hfile>-<region>-<table> 342 RegionInfo hriClone = RegionInfoBuilder.newBuilder(TableName.valueOf("clone")).build(); 343 HRegionFileSystem cloneRegionFs = HRegionFileSystem.createRegionOnFileSystem(testConf, fs, 344 CommonFSUtils.getTableDir(testDir, hri.getTable()), hriClone); 345 Path dstPath = cloneRegionFs.getStoreDir(TEST_FAMILY); 346 HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName()); 347 Path linkFilePath = 348 new Path(dstPath, HFileLink.createHFileLinkName(hri, storeFilePath.getName())); 349 350 // create splits of the link. 351 // <root>/clone/splitA/<cf>/<reftohfilelink>, 352 // <root>/clone/splitB/<cf>/<reftohfilelink> 353 HRegionInfo splitHriA = new HRegionInfo(hri.getTable(), null, SPLITKEY); 354 HRegionInfo splitHriB = new HRegionInfo(hri.getTable(), SPLITKEY, null); 355 HStoreFile f = new HStoreFile(fs, linkFilePath, testConf, cacheConf, BloomType.NONE, true); 356 f.initReader(); 357 Path pathA = splitStoreFile(cloneRegionFs, splitHriA, TEST_FAMILY, f, SPLITKEY, true); // top 358 Path pathB = splitStoreFile(cloneRegionFs, splitHriB, TEST_FAMILY, f, SPLITKEY, false);// bottom 359 f.closeStoreFile(true); 360 // OK test the thing 361 CommonFSUtils.logFileSystemState(fs, testDir, LOG); 362 363 // There is a case where a file with the hfilelink pattern is actually a daughter 364 // reference to a hfile link. This code in StoreFile that handles this case. 365 366 // Try to open store file from link 367 HStoreFile hsfA = new HStoreFile(this.fs, pathA, testConf, cacheConf, BloomType.NONE, true); 368 hsfA.initReader(); 369 370 // Now confirm that I can read from the ref to link 371 int count = 1; 372 HFileScanner s = hsfA.getReader().getScanner(false, false); 373 s.seekTo(); 374 while (s.next()) { 375 count++; 376 } 377 assertTrue(count > 0); // read some rows here 378 379 // Try to open store file from link 380 HStoreFile hsfB = new HStoreFile(this.fs, pathB, testConf, cacheConf, BloomType.NONE, true); 381 hsfB.initReader(); 382 383 // Now confirm that I can read from the ref to link 384 HFileScanner sB = hsfB.getReader().getScanner(false, false); 385 sB.seekTo(); 386 387 // count++ as seekTo() will advance the scanner 388 count++; 389 while (sB.next()) { 390 count++; 391 } 392 393 // read the rest of the rows 394 assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count); 395 } 396 397 private void checkHalfHFile(final HRegionFileSystem regionFs, final HStoreFile f) 398 throws IOException { 399 f.initReader(); 400 Cell midkey = f.getReader().midKey().get(); 401 KeyValue midKV = (KeyValue) midkey; 402 // 1. test using the midRow as the splitKey, this test will generate two Reference files 403 // in the children 404 byte[] midRow = CellUtil.cloneRow(midKV); 405 // Create top split. 406 HRegionInfo topHri = new HRegionInfo(regionFs.getRegionInfo().getTable(), null, midRow); 407 Path topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, midRow, true); 408 // Create bottom split. 409 HRegionInfo bottomHri = new HRegionInfo(regionFs.getRegionInfo().getTable(), midRow, null); 410 Path bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, midRow, false); 411 // Make readers on top and bottom. 412 HStoreFile topF = new HStoreFile(this.fs, topPath, conf, cacheConf, BloomType.NONE, true); 413 topF.initReader(); 414 StoreFileReader top = topF.getReader(); 415 HStoreFile bottomF = new HStoreFile(this.fs, bottomPath, conf, cacheConf, BloomType.NONE, true); 416 bottomF.initReader(); 417 StoreFileReader bottom = bottomF.getReader(); 418 ByteBuffer previous = null; 419 LOG.info("Midkey: " + midKV.toString()); 420 ByteBuffer bbMidkeyBytes = ByteBuffer.wrap(midKV.getKey()); 421 try { 422 // Now make two HalfMapFiles and assert they can read the full backing 423 // file, one from the top and the other from the bottom. 424 // Test bottom half first. 425 // Now test reading from the top. 426 boolean first = true; 427 ByteBuffer key = null; 428 HFileScanner topScanner = top.getScanner(false, false); 429 while ( 430 (!topScanner.isSeeked() && topScanner.seekTo()) 431 || (topScanner.isSeeked() && topScanner.next()) 432 ) { 433 key = ByteBuffer.wrap(((KeyValue) topScanner.getKey()).getKey()); 434 435 if ( 436 (PrivateCellUtil.compare(topScanner.getReader().getComparator(), midKV, key.array(), 437 key.arrayOffset(), key.limit())) > 0 438 ) { 439 fail("key=" + Bytes.toStringBinary(key) + " < midkey=" + midkey); 440 } 441 if (first) { 442 first = false; 443 LOG.info("First in top: " + Bytes.toString(Bytes.toBytes(key))); 444 } 445 } 446 LOG.info("Last in top: " + Bytes.toString(Bytes.toBytes(key))); 447 448 first = true; 449 HFileScanner bottomScanner = bottom.getScanner(false, false); 450 while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) || bottomScanner.next()) { 451 previous = ByteBuffer.wrap(((KeyValue) bottomScanner.getKey()).getKey()); 452 key = ByteBuffer.wrap(((KeyValue) bottomScanner.getKey()).getKey()); 453 if (first) { 454 first = false; 455 LOG.info("First in bottom: " + Bytes.toString(Bytes.toBytes(previous))); 456 } 457 assertTrue(key.compareTo(bbMidkeyBytes) < 0); 458 } 459 if (previous != null) { 460 LOG.info("Last in bottom: " + Bytes.toString(Bytes.toBytes(previous))); 461 } 462 // Remove references. 463 regionFs.cleanupDaughterRegion(topHri); 464 regionFs.cleanupDaughterRegion(bottomHri); 465 466 // 2. test using a midkey which will generate one Reference file and one HFileLink file. 467 // First, do a key that is < than first key. Ensure splits behave 468 // properly. 469 byte[] badmidkey = Bytes.toBytes(" ."); 470 assertTrue(fs.exists(f.getPath())); 471 topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, badmidkey, true); 472 bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false); 473 474 assertNull(bottomPath); 475 476 topF = new HStoreFile(this.fs, topPath, conf, cacheConf, BloomType.NONE, true); 477 topF.initReader(); 478 top = topF.getReader(); 479 // Now read from the top. 480 first = true; 481 topScanner = top.getScanner(false, false); 482 KeyValue.KeyOnlyKeyValue keyOnlyKV = new KeyValue.KeyOnlyKeyValue(); 483 while ((!topScanner.isSeeked() && topScanner.seekTo()) || topScanner.next()) { 484 key = ByteBuffer.wrap(((KeyValue) topScanner.getKey()).getKey()); 485 keyOnlyKV.setKey(key.array(), 0 + key.arrayOffset(), key.limit()); 486 assertTrue(PrivateCellUtil.compare(topScanner.getReader().getComparator(), keyOnlyKV, 487 badmidkey, 0, badmidkey.length) >= 0); 488 if (first) { 489 first = false; 490 KeyValue keyKV = KeyValueUtil.createKeyValueFromKey(key); 491 LOG.info("First top when key < bottom: " + keyKV); 492 String tmp = 493 Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength()); 494 for (int i = 0; i < tmp.length(); i++) { 495 assertTrue(tmp.charAt(i) == 'a'); 496 } 497 } 498 } 499 KeyValue keyKV = KeyValueUtil.createKeyValueFromKey(key); 500 LOG.info("Last top when key < bottom: " + keyKV); 501 String tmp = Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength()); 502 for (int i = 0; i < tmp.length(); i++) { 503 assertTrue(tmp.charAt(i) == 'z'); 504 } 505 // Remove references. 506 regionFs.cleanupDaughterRegion(topHri); 507 regionFs.cleanupDaughterRegion(bottomHri); 508 509 // Test when badkey is > than last key in file ('||' > 'zz'). 510 badmidkey = Bytes.toBytes("|||"); 511 topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, badmidkey, true); 512 bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false); 513 assertNull(topPath); 514 515 bottomF = new HStoreFile(this.fs, bottomPath, conf, cacheConf, BloomType.NONE, true); 516 bottomF.initReader(); 517 bottom = bottomF.getReader(); 518 first = true; 519 bottomScanner = bottom.getScanner(false, false); 520 while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) || bottomScanner.next()) { 521 key = ByteBuffer.wrap(((KeyValue) bottomScanner.getKey()).getKey()); 522 if (first) { 523 first = false; 524 keyKV = KeyValueUtil.createKeyValueFromKey(key); 525 LOG.info("First bottom when key > top: " + keyKV); 526 tmp = Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength()); 527 for (int i = 0; i < tmp.length(); i++) { 528 assertTrue(tmp.charAt(i) == 'a'); 529 } 530 } 531 } 532 keyKV = KeyValueUtil.createKeyValueFromKey(key); 533 LOG.info("Last bottom when key > top: " + keyKV); 534 for (int i = 0; i < tmp.length(); i++) { 535 assertTrue( 536 Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength()).charAt(i) 537 == 'z'); 538 } 539 } finally { 540 if (top != null) { 541 top.close(true); // evict since we are about to delete the file 542 } 543 if (bottom != null) { 544 bottom.close(true); // evict since we are about to delete the file 545 } 546 fs.delete(f.getPath(), true); 547 } 548 } 549 550 private static StoreFileScanner getStoreFileScanner(StoreFileReader reader, boolean cacheBlocks, 551 boolean pread) { 552 return reader.getStoreFileScanner(cacheBlocks, pread, false, 0, 0, false); 553 } 554 555 private static final String localFormatter = "%010d"; 556 557 private void bloomWriteRead(StoreFileWriter writer, FileSystem fs) throws Exception { 558 float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0); 559 Path f = writer.getPath(); 560 long now = EnvironmentEdgeManager.currentTime(); 561 for (int i = 0; i < 2000; i += 2) { 562 String row = String.format(localFormatter, i); 563 KeyValue kv = new KeyValue(Bytes.toBytes(row), Bytes.toBytes("family"), Bytes.toBytes("col"), 564 now, Bytes.toBytes("value")); 565 writer.append(kv); 566 } 567 writer.close(); 568 569 ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, f).build(); 570 HFileInfo fileInfo = new HFileInfo(context, conf); 571 StoreFileReader reader = 572 new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf); 573 fileInfo.initMetaAndIndex(reader.getHFileReader()); 574 reader.loadFileInfo(); 575 reader.loadBloomfilter(); 576 StoreFileScanner scanner = getStoreFileScanner(reader, false, false); 577 578 // check false positives rate 579 int falsePos = 0; 580 int falseNeg = 0; 581 for (int i = 0; i < 2000; i++) { 582 String row = String.format(localFormatter, i); 583 TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR); 584 columns.add(Bytes.toBytes("family:col")); 585 586 Scan scan = new Scan().withStartRow(Bytes.toBytes(row)).withStopRow(Bytes.toBytes(row), true); 587 scan.addColumn(Bytes.toBytes("family"), Bytes.toBytes("family:col")); 588 HStore store = mock(HStore.class); 589 when(store.getColumnFamilyDescriptor()) 590 .thenReturn(ColumnFamilyDescriptorBuilder.of("family")); 591 boolean exists = scanner.shouldUseScanner(scan, store, Long.MIN_VALUE); 592 if (i % 2 == 0) { 593 if (!exists) { 594 falseNeg++; 595 } 596 } else { 597 if (exists) { 598 falsePos++; 599 } 600 } 601 } 602 reader.close(true); // evict because we are about to delete the file 603 fs.delete(f, true); 604 assertEquals("False negatives: " + falseNeg, 0, falseNeg); 605 int maxFalsePos = (int) (2 * 2000 * err); 606 assertTrue("Too many false positives: " + falsePos + " (err=" + err + ", expected no more than " 607 + maxFalsePos + ")", falsePos <= maxFalsePos); 608 } 609 610 private static final int BLOCKSIZE_SMALL = 8192; 611 612 @Test 613 public void testBloomFilter() throws Exception { 614 conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01); 615 conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true); 616 617 // write the file 618 Path f = new Path(ROOT_DIR, name.getMethodName()); 619 HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL) 620 .withChecksumType(CKTYPE).withBytesPerCheckSum(CKBYTES).build(); 621 // Make a store file and write data to it. 622 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f) 623 .withBloomType(BloomType.ROW).withMaxKeyCount(2000).withFileContext(meta).build(); 624 bloomWriteRead(writer, fs); 625 } 626 627 @Test 628 public void testDeleteFamilyBloomFilter() throws Exception { 629 conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01); 630 conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true); 631 float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0); 632 633 // write the file 634 Path f = new Path(ROOT_DIR, name.getMethodName()); 635 636 HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL) 637 .withChecksumType(CKTYPE).withBytesPerCheckSum(CKBYTES).build(); 638 // Make a store file and write data to it. 639 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f) 640 .withMaxKeyCount(2000).withFileContext(meta).build(); 641 642 // add delete family 643 long now = EnvironmentEdgeManager.currentTime(); 644 for (int i = 0; i < 2000; i += 2) { 645 String row = String.format(localFormatter, i); 646 KeyValue kv = new KeyValue(Bytes.toBytes(row), Bytes.toBytes("family"), Bytes.toBytes("col"), 647 now, KeyValue.Type.DeleteFamily, Bytes.toBytes("value")); 648 writer.append(kv); 649 } 650 writer.close(); 651 652 ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, f).build(); 653 HFileInfo fileInfo = new HFileInfo(context, conf); 654 StoreFileReader reader = 655 new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf); 656 fileInfo.initMetaAndIndex(reader.getHFileReader()); 657 reader.loadFileInfo(); 658 reader.loadBloomfilter(); 659 660 // check false positives rate 661 int falsePos = 0; 662 int falseNeg = 0; 663 for (int i = 0; i < 2000; i++) { 664 String row = String.format(localFormatter, i); 665 byte[] rowKey = Bytes.toBytes(row); 666 boolean exists = reader.passesDeleteFamilyBloomFilter(rowKey, 0, rowKey.length); 667 if (i % 2 == 0) { 668 if (!exists) { 669 falseNeg++; 670 } 671 } else { 672 if (exists) { 673 falsePos++; 674 } 675 } 676 } 677 assertEquals(1000, reader.getDeleteFamilyCnt()); 678 reader.close(true); // evict because we are about to delete the file 679 fs.delete(f, true); 680 assertEquals("False negatives: " + falseNeg, 0, falseNeg); 681 int maxFalsePos = (int) (2 * 2000 * err); 682 assertTrue("Too many false positives: " + falsePos + " (err=" + err + ", expected no more than " 683 + maxFalsePos, falsePos <= maxFalsePos); 684 } 685 686 /** 687 * Test for HBASE-8012 688 */ 689 @Test 690 public void testReseek() throws Exception { 691 // write the file 692 Path f = new Path(ROOT_DIR, name.getMethodName()); 693 HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build(); 694 // Make a store file and write data to it. 695 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f) 696 .withFileContext(meta).build(); 697 698 writeStoreFile(writer); 699 writer.close(); 700 701 ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, f).build(); 702 HFileInfo fileInfo = new HFileInfo(context, conf); 703 StoreFileReader reader = 704 new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf); 705 fileInfo.initMetaAndIndex(reader.getHFileReader()); 706 707 // Now do reseek with empty KV to position to the beginning of the file 708 709 KeyValue k = KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY); 710 StoreFileScanner s = getStoreFileScanner(reader, false, false); 711 s.reseek(k); 712 713 assertNotNull("Intial reseek should position at the beginning of the file", s.peek()); 714 } 715 716 @Test 717 public void testBloomTypes() throws Exception { 718 float err = (float) 0.01; 719 FileSystem fs = FileSystem.getLocal(conf); 720 conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, err); 721 conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true); 722 723 int rowCount = 50; 724 int colCount = 10; 725 int versions = 2; 726 727 // run once using columns and once using rows 728 BloomType[] bt = { BloomType.ROWCOL, BloomType.ROW }; 729 int[] expKeys = { rowCount * colCount, rowCount }; 730 // below line deserves commentary. it is expected bloom false positives 731 // column = rowCount*2*colCount inserts 732 // row-level = only rowCount*2 inserts, but failures will be magnified by 733 // 2nd for loop for every column (2*colCount) 734 float[] expErr = { 2 * rowCount * colCount * err, 2 * rowCount * 2 * colCount * err }; 735 736 for (int x : new int[] { 0, 1 }) { 737 // write the file 738 Path f = new Path(ROOT_DIR, name.getMethodName() + x); 739 HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL) 740 .withChecksumType(CKTYPE).withBytesPerCheckSum(CKBYTES).build(); 741 // Make a store file and write data to it. 742 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f) 743 .withBloomType(bt[x]).withMaxKeyCount(expKeys[x]).withFileContext(meta).build(); 744 745 long now = EnvironmentEdgeManager.currentTime(); 746 for (int i = 0; i < rowCount * 2; i += 2) { // rows 747 for (int j = 0; j < colCount * 2; j += 2) { // column qualifiers 748 String row = String.format(localFormatter, i); 749 String col = String.format(localFormatter, j); 750 for (int k = 0; k < versions; ++k) { // versions 751 KeyValue kv = new KeyValue(Bytes.toBytes(row), Bytes.toBytes("family"), 752 Bytes.toBytes("col" + col), now - k, Bytes.toBytes(-1L)); 753 writer.append(kv); 754 } 755 } 756 } 757 writer.close(); 758 759 ReaderContext context = 760 new ReaderContextBuilder().withFilePath(f).withFileSize(fs.getFileStatus(f).getLen()) 761 .withFileSystem(fs).withInputStreamWrapper(new FSDataInputStreamWrapper(fs, f)).build(); 762 HFileInfo fileInfo = new HFileInfo(context, conf); 763 StoreFileReader reader = 764 new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf); 765 fileInfo.initMetaAndIndex(reader.getHFileReader()); 766 reader.loadFileInfo(); 767 reader.loadBloomfilter(); 768 StoreFileScanner scanner = getStoreFileScanner(reader, false, false); 769 assertEquals(expKeys[x], reader.getGeneralBloomFilter().getKeyCount()); 770 771 HStore store = mock(HStore.class); 772 when(store.getColumnFamilyDescriptor()) 773 .thenReturn(ColumnFamilyDescriptorBuilder.of("family")); 774 // check false positives rate 775 int falsePos = 0; 776 int falseNeg = 0; 777 for (int i = 0; i < rowCount * 2; ++i) { // rows 778 for (int j = 0; j < colCount * 2; ++j) { // column qualifiers 779 String row = String.format(localFormatter, i); 780 String col = String.format(localFormatter, j); 781 TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR); 782 columns.add(Bytes.toBytes("col" + col)); 783 784 Scan scan = 785 new Scan().withStartRow(Bytes.toBytes(row)).withStopRow(Bytes.toBytes(row), true); 786 scan.addColumn(Bytes.toBytes("family"), Bytes.toBytes(("col" + col))); 787 788 boolean exists = scanner.shouldUseScanner(scan, store, Long.MIN_VALUE); 789 boolean shouldRowExist = i % 2 == 0; 790 boolean shouldColExist = j % 2 == 0; 791 shouldColExist = shouldColExist || bt[x] == BloomType.ROW; 792 if (shouldRowExist && shouldColExist) { 793 if (!exists) { 794 falseNeg++; 795 } 796 } else { 797 if (exists) { 798 falsePos++; 799 } 800 } 801 } 802 } 803 reader.close(true); // evict because we are about to delete the file 804 fs.delete(f, true); 805 System.out.println(bt[x].toString()); 806 System.out.println(" False negatives: " + falseNeg); 807 System.out.println(" False positives: " + falsePos); 808 assertEquals(0, falseNeg); 809 assertTrue(falsePos < 2 * expErr[x]); 810 } 811 } 812 813 @Test 814 public void testSeqIdComparator() { 815 assertOrdering(StoreFileComparators.SEQ_ID, mockStoreFile(true, 100, 1000, -1, "/foo/123"), 816 mockStoreFile(true, 100, 1000, -1, "/foo/124"), mockStoreFile(true, 99, 1000, -1, "/foo/126"), 817 mockStoreFile(true, 98, 2000, -1, "/foo/126"), mockStoreFile(false, 3453, -1, 1, "/foo/1"), 818 mockStoreFile(false, 2, -1, 3, "/foo/2"), mockStoreFile(false, 1000, -1, 5, "/foo/2"), 819 mockStoreFile(false, 76, -1, 5, "/foo/3")); 820 } 821 822 /** 823 * Assert that the given comparator orders the given storefiles in the same way that they're 824 * passed. 825 */ 826 private void assertOrdering(Comparator<? super HStoreFile> comparator, HStoreFile... sfs) { 827 ArrayList<HStoreFile> sorted = Lists.newArrayList(sfs); 828 Collections.shuffle(sorted); 829 Collections.sort(sorted, comparator); 830 LOG.debug("sfs: " + Joiner.on(",").join(sfs)); 831 LOG.debug("sorted: " + Joiner.on(",").join(sorted)); 832 assertTrue(Iterables.elementsEqual(Arrays.asList(sfs), sorted)); 833 } 834 835 /** 836 * Create a mock StoreFile with the given attributes. 837 */ 838 private HStoreFile mockStoreFile(boolean bulkLoad, long size, long bulkTimestamp, long seqId, 839 String path) { 840 HStoreFile mock = Mockito.mock(HStoreFile.class); 841 StoreFileReader reader = Mockito.mock(StoreFileReader.class); 842 843 Mockito.doReturn(size).when(reader).length(); 844 845 Mockito.doReturn(reader).when(mock).getReader(); 846 Mockito.doReturn(bulkLoad).when(mock).isBulkLoadResult(); 847 Mockito.doReturn(OptionalLong.of(bulkTimestamp)).when(mock).getBulkLoadTimestamp(); 848 Mockito.doReturn(seqId).when(mock).getMaxSequenceId(); 849 Mockito.doReturn(new Path(path)).when(mock).getPath(); 850 String name = "mock storefile, bulkLoad=" + bulkLoad + " bulkTimestamp=" + bulkTimestamp 851 + " seqId=" + seqId + " path=" + path; 852 Mockito.doReturn(name).when(mock).toString(); 853 return mock; 854 } 855 856 /** 857 * Generate a list of KeyValues for testing based on given parameters 858 * @return the rows key-value list 859 */ 860 List<KeyValue> getKeyValueSet(long[] timestamps, int numRows, byte[] qualifier, byte[] family) { 861 List<KeyValue> kvList = new ArrayList<>(); 862 for (int i = 1; i <= numRows; i++) { 863 byte[] b = Bytes.toBytes(i); 864 LOG.info(Bytes.toString(b)); 865 LOG.info(Bytes.toString(b)); 866 for (long timestamp : timestamps) { 867 kvList.add(new KeyValue(b, family, qualifier, timestamp, b)); 868 } 869 } 870 return kvList; 871 } 872 873 /** 874 * Test to ensure correctness when using StoreFile with multiple timestamps 875 */ 876 @Test 877 public void testMultipleTimestamps() throws IOException { 878 byte[] family = Bytes.toBytes("familyname"); 879 byte[] qualifier = Bytes.toBytes("qualifier"); 880 int numRows = 10; 881 long[] timestamps = new long[] { 20, 10, 5, 1 }; 882 Scan scan = new Scan(); 883 884 // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname. 885 Path storedir = new Path(new Path(testDir, "7e0102"), Bytes.toString(family)); 886 Path dir = new Path(storedir, "1234567890"); 887 HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build(); 888 // Make a store file and write data to it. 889 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs) 890 .withOutputDir(dir).withFileContext(meta).build(); 891 892 List<KeyValue> kvList = getKeyValueSet(timestamps, numRows, qualifier, family); 893 894 for (KeyValue kv : kvList) { 895 writer.append(kv); 896 } 897 writer.appendMetadata(0, false); 898 writer.close(); 899 900 HStoreFile hsf = 901 new HStoreFile(this.fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true); 902 HStore store = mock(HStore.class); 903 when(store.getColumnFamilyDescriptor()).thenReturn(ColumnFamilyDescriptorBuilder.of(family)); 904 hsf.initReader(); 905 StoreFileReader reader = hsf.getReader(); 906 StoreFileScanner scanner = getStoreFileScanner(reader, false, false); 907 TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR); 908 columns.add(qualifier); 909 910 scan.setTimeRange(20, 100); 911 assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE)); 912 913 scan.setTimeRange(1, 2); 914 assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE)); 915 916 scan.setTimeRange(8, 10); 917 assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE)); 918 919 // lets make sure it still works with column family time ranges 920 scan.setColumnFamilyTimeRange(family, 7, 50); 921 assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE)); 922 923 // This test relies on the timestamp range optimization 924 scan = new Scan(); 925 scan.setTimeRange(27, 50); 926 assertTrue(!scanner.shouldUseScanner(scan, store, Long.MIN_VALUE)); 927 928 // should still use the scanner because we override the family time range 929 scan = new Scan(); 930 scan.setTimeRange(27, 50); 931 scan.setColumnFamilyTimeRange(family, 7, 50); 932 assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE)); 933 } 934 935 @Test 936 public void testCacheOnWriteEvictOnClose() throws Exception { 937 Configuration conf = this.conf; 938 939 // Find a home for our files (regiondir ("7e0102") and familyname). 940 Path baseDir = new Path(new Path(testDir, "7e0102"), "twoCOWEOC"); 941 942 // Grab the block cache and get the initial hit/miss counts 943 BlockCache bc = BlockCacheFactory.createBlockCache(conf); 944 assertNotNull(bc); 945 CacheStats cs = bc.getStats(); 946 long startHit = cs.getHitCount(); 947 long startMiss = cs.getMissCount(); 948 long startEvicted = cs.getEvictedCount(); 949 950 // Let's write a StoreFile with three blocks, with cache on write off 951 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false); 952 CacheConfig cacheConf = new CacheConfig(conf, bc); 953 Path pathCowOff = new Path(baseDir, "123456789"); 954 StoreFileWriter writer = writeStoreFile(conf, cacheConf, pathCowOff, 3); 955 HStoreFile hsf = 956 new HStoreFile(this.fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true); 957 LOG.debug(hsf.getPath().toString()); 958 959 // Read this file, we should see 3 misses 960 hsf.initReader(); 961 StoreFileReader reader = hsf.getReader(); 962 reader.loadFileInfo(); 963 StoreFileScanner scanner = getStoreFileScanner(reader, true, true); 964 scanner.seek(KeyValue.LOWESTKEY); 965 while (scanner.next() != null) { 966 continue; 967 } 968 assertEquals(startHit, cs.getHitCount()); 969 assertEquals(startMiss + 3, cs.getMissCount()); 970 assertEquals(startEvicted, cs.getEvictedCount()); 971 startMiss += 3; 972 scanner.close(); 973 reader.close(cacheConf.shouldEvictOnClose()); 974 975 // Now write a StoreFile with three blocks, with cache on write on 976 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true); 977 cacheConf = new CacheConfig(conf, bc); 978 Path pathCowOn = new Path(baseDir, "123456788"); 979 writer = writeStoreFile(conf, cacheConf, pathCowOn, 3); 980 hsf = new HStoreFile(this.fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true); 981 982 // Read this file, we should see 3 hits 983 hsf.initReader(); 984 reader = hsf.getReader(); 985 scanner = getStoreFileScanner(reader, true, true); 986 scanner.seek(KeyValue.LOWESTKEY); 987 while (scanner.next() != null) { 988 continue; 989 } 990 assertEquals(startHit + 3, cs.getHitCount()); 991 assertEquals(startMiss, cs.getMissCount()); 992 assertEquals(startEvicted, cs.getEvictedCount()); 993 startHit += 3; 994 scanner.close(); 995 reader.close(cacheConf.shouldEvictOnClose()); 996 997 // Let's read back the two files to ensure the blocks exactly match 998 hsf = new HStoreFile(this.fs, pathCowOff, conf, cacheConf, BloomType.NONE, true); 999 hsf.initReader(); 1000 StoreFileReader readerOne = hsf.getReader(); 1001 readerOne.loadFileInfo(); 1002 StoreFileScanner scannerOne = getStoreFileScanner(readerOne, true, true); 1003 scannerOne.seek(KeyValue.LOWESTKEY); 1004 hsf = new HStoreFile(this.fs, pathCowOn, conf, cacheConf, BloomType.NONE, true); 1005 hsf.initReader(); 1006 StoreFileReader readerTwo = hsf.getReader(); 1007 readerTwo.loadFileInfo(); 1008 StoreFileScanner scannerTwo = getStoreFileScanner(readerTwo, true, true); 1009 scannerTwo.seek(KeyValue.LOWESTKEY); 1010 Cell kv1 = null; 1011 Cell kv2 = null; 1012 while ((kv1 = scannerOne.next()) != null) { 1013 kv2 = scannerTwo.next(); 1014 assertTrue(kv1.equals(kv2)); 1015 KeyValue keyv1 = KeyValueUtil.ensureKeyValue(kv1); 1016 KeyValue keyv2 = KeyValueUtil.ensureKeyValue(kv2); 1017 assertTrue(Bytes.compareTo(keyv1.getBuffer(), keyv1.getKeyOffset(), keyv1.getKeyLength(), 1018 keyv2.getBuffer(), keyv2.getKeyOffset(), keyv2.getKeyLength()) == 0); 1019 assertTrue(Bytes.compareTo(kv1.getValueArray(), kv1.getValueOffset(), kv1.getValueLength(), 1020 kv2.getValueArray(), kv2.getValueOffset(), kv2.getValueLength()) == 0); 1021 } 1022 assertNull(scannerTwo.next()); 1023 assertEquals(startHit + 6, cs.getHitCount()); 1024 assertEquals(startMiss, cs.getMissCount()); 1025 assertEquals(startEvicted, cs.getEvictedCount()); 1026 startHit += 6; 1027 scannerOne.close(); 1028 readerOne.close(cacheConf.shouldEvictOnClose()); 1029 scannerTwo.close(); 1030 readerTwo.close(cacheConf.shouldEvictOnClose()); 1031 1032 // Let's close the first file with evict on close turned on 1033 conf.setBoolean("hbase.rs.evictblocksonclose", true); 1034 cacheConf = new CacheConfig(conf, bc); 1035 hsf = new HStoreFile(this.fs, pathCowOff, conf, cacheConf, BloomType.NONE, true); 1036 hsf.initReader(); 1037 reader = hsf.getReader(); 1038 reader.close(cacheConf.shouldEvictOnClose()); 1039 1040 // We should have 3 new evictions but the evict count stat should not change. Eviction because 1041 // of HFile invalidation is not counted along with normal evictions 1042 assertEquals(startHit, cs.getHitCount()); 1043 assertEquals(startMiss, cs.getMissCount()); 1044 assertEquals(startEvicted, cs.getEvictedCount()); 1045 1046 // Let's close the second file with evict on close turned off 1047 conf.setBoolean("hbase.rs.evictblocksonclose", false); 1048 cacheConf = new CacheConfig(conf, bc); 1049 hsf = new HStoreFile(this.fs, pathCowOn, conf, cacheConf, BloomType.NONE, true); 1050 hsf.initReader(); 1051 reader = hsf.getReader(); 1052 reader.close(cacheConf.shouldEvictOnClose()); 1053 1054 // We expect no changes 1055 assertEquals(startHit, cs.getHitCount()); 1056 assertEquals(startMiss, cs.getMissCount()); 1057 assertEquals(startEvicted, cs.getEvictedCount()); 1058 } 1059 1060 private Path splitStoreFile(final HRegionFileSystem regionFs, final RegionInfo hri, 1061 final String family, final HStoreFile sf, final byte[] splitKey, boolean isTopRef) 1062 throws IOException { 1063 Path path = regionFs.splitStoreFile(hri, family, sf, splitKey, isTopRef, null); 1064 if (null == path) { 1065 return null; 1066 } 1067 List<Path> splitFiles = new ArrayList<>(); 1068 splitFiles.add(path); 1069 MasterProcedureEnv mockEnv = mock(MasterProcedureEnv.class); 1070 MasterServices mockServices = mock(MasterServices.class); 1071 when(mockEnv.getMasterServices()).thenReturn(mockServices); 1072 when(mockEnv.getMasterConfiguration()).thenReturn(new Configuration()); 1073 TableDescriptors mockTblDescs = mock(TableDescriptors.class); 1074 when(mockServices.getTableDescriptors()).thenReturn(mockTblDescs); 1075 TableDescriptor mockTblDesc = TableDescriptorBuilder.newBuilder(hri.getTable()) 1076 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(family)).build(); 1077 when(mockTblDescs.get(any())).thenReturn(mockTblDesc); 1078 Path regionDir = regionFs.commitDaughterRegion(hri, splitFiles, mockEnv); 1079 return new Path(new Path(regionDir, family), path.getName()); 1080 } 1081 1082 private StoreFileWriter writeStoreFile(Configuration conf, CacheConfig cacheConf, Path path, 1083 int numBlocks) throws IOException { 1084 // Let's put ~5 small KVs in each block, so let's make 5*numBlocks KVs 1085 int numKVs = 5 * numBlocks; 1086 List<KeyValue> kvs = new ArrayList<>(numKVs); 1087 byte[] b = Bytes.toBytes("x"); 1088 int totalSize = 0; 1089 for (int i = numKVs; i > 0; i--) { 1090 KeyValue kv = new KeyValue(b, b, b, i, b); 1091 kvs.add(kv); 1092 // kv has memstoreTS 0, which takes 1 byte to store. 1093 totalSize += kv.getLength() + 1; 1094 } 1095 int blockSize = totalSize / numBlocks; 1096 HFileContext meta = new HFileContextBuilder().withBlockSize(blockSize).withChecksumType(CKTYPE) 1097 .withBytesPerCheckSum(CKBYTES).build(); 1098 // Make a store file and write data to it. 1099 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs) 1100 .withFilePath(path).withMaxKeyCount(2000).withFileContext(meta).build(); 1101 // We'll write N-1 KVs to ensure we don't write an extra block 1102 kvs.remove(kvs.size() - 1); 1103 for (KeyValue kv : kvs) { 1104 writer.append(kv); 1105 } 1106 writer.appendMetadata(0, false); 1107 writer.close(); 1108 return writer; 1109 } 1110 1111 /** 1112 * Check if data block encoding information is saved correctly in HFile's file info. 1113 */ 1114 @Test 1115 public void testDataBlockEncodingMetaData() throws IOException { 1116 // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname. 1117 Path dir = new Path(new Path(testDir, "7e0102"), "familyname"); 1118 Path path = new Path(dir, "1234567890"); 1119 1120 DataBlockEncoding dataBlockEncoderAlgo = DataBlockEncoding.FAST_DIFF; 1121 cacheConf = new CacheConfig(conf); 1122 HFileContext meta = 1123 new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL).withChecksumType(CKTYPE) 1124 .withBytesPerCheckSum(CKBYTES).withDataBlockEncoding(dataBlockEncoderAlgo).build(); 1125 // Make a store file and write data to it. 1126 StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs) 1127 .withFilePath(path).withMaxKeyCount(2000).withFileContext(meta).build(); 1128 writer.close(); 1129 1130 HStoreFile storeFile = 1131 new HStoreFile(fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true); 1132 storeFile.initReader(); 1133 StoreFileReader reader = storeFile.getReader(); 1134 1135 Map<byte[], byte[]> fileInfo = reader.loadFileInfo(); 1136 byte[] value = fileInfo.get(HFileDataBlockEncoder.DATA_BLOCK_ENCODING); 1137 assertArrayEquals(dataBlockEncoderAlgo.getNameInBytes(), value); 1138 } 1139}