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 junit.framework.TestCase.assertTrue;
021import static org.junit.Assert.assertEquals;
022
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.List;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.hbase.Cell;
030import org.apache.hadoop.hbase.CellUtil;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.HBaseTestingUtility;
033import org.apache.hadoop.hbase.HColumnDescriptor;
034import org.apache.hadoop.hbase.HRegionInfo;
035import org.apache.hadoop.hbase.HTableDescriptor;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.client.Delete;
038import org.apache.hadoop.hbase.client.Durability;
039import org.apache.hadoop.hbase.client.Get;
040import org.apache.hadoop.hbase.client.Put;
041import org.apache.hadoop.hbase.client.Scan;
042import org.apache.hadoop.hbase.io.hfile.BlockCache;
043import org.apache.hadoop.hbase.io.hfile.CacheConfig;
044import org.apache.hadoop.hbase.io.hfile.HFile;
045import org.apache.hadoop.hbase.testclassification.MediumTests;
046import org.apache.hadoop.hbase.testclassification.RegionServerTests;
047import org.apache.hadoop.hbase.util.Bytes;
048import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
049import org.junit.*;
050import org.junit.ClassRule;
051import org.junit.experimental.categories.Category;
052import org.junit.rules.TestName;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056@Category({RegionServerTests.class, MediumTests.class})
057public class TestBlocksRead  {
058
059  @ClassRule
060  public static final HBaseClassTestRule CLASS_RULE =
061      HBaseClassTestRule.forClass(TestBlocksRead.class);
062
063  private static final Logger LOG = LoggerFactory.getLogger(TestBlocksRead.class);
064  @Rule public TestName testName = new TestName();
065
066  static final BloomType[] BLOOM_TYPE = new BloomType[] { BloomType.ROWCOL,
067      BloomType.ROW, BloomType.NONE };
068
069  private static BlockCache blockCache;
070  HRegion region = null;
071  private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
072  private final String DIR = TEST_UTIL.getDataTestDir("TestBlocksRead").toString();
073  private Configuration conf = TEST_UTIL.getConfiguration();
074
075  @BeforeClass
076  public static void setUp() throws Exception {
077    // disable compactions in this test.
078    TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 10000);
079    CacheConfig.instantiateBlockCache(TEST_UTIL.getConfiguration());
080  }
081
082  @AfterClass
083  public static void tearDown() throws Exception {
084    EnvironmentEdgeManagerTestHelper.reset();
085  }
086
087  /**
088   * Callers must afterward call {@link HBaseTestingUtility#closeRegionAndWAL(HRegion)}
089   * @param tableName
090   * @param callingMethod
091   * @param conf
092   * @param family
093   * @throws IOException
094   * @return created and initialized region.
095   */
096  private HRegion initHRegion(byte[] tableName, String callingMethod,
097      Configuration conf, String family) throws IOException {
098    HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
099    HColumnDescriptor familyDesc;
100    for (int i = 0; i < BLOOM_TYPE.length; i++) {
101      BloomType bloomType = BLOOM_TYPE[i];
102      familyDesc = new HColumnDescriptor(family + "_" + bloomType)
103          .setBlocksize(1)
104          .setBloomFilterType(BLOOM_TYPE[i]);
105      htd.addFamily(familyDesc);
106    }
107
108    HRegionInfo info = new HRegionInfo(htd.getTableName(), null, null, false);
109    Path path = new Path(DIR + callingMethod);
110    HRegion r = HBaseTestingUtility.createRegionAndWAL(info, path, conf, htd);
111    blockCache = new CacheConfig(conf).getBlockCache();
112    return r;
113  }
114
115  private void putData(String family, String row, String col, long version)
116      throws IOException {
117    for (int i = 0; i < BLOOM_TYPE.length; i++) {
118      putData(Bytes.toBytes(family + "_" + BLOOM_TYPE[i]), row, col, version,
119          version);
120    }
121  }
122
123  // generates a value to put for a row/col/version.
124  private static byte[] genValue(String row, String col, long version) {
125    return Bytes.toBytes("Value:" + row + "#" + col + "#" + version);
126  }
127
128  private void putData(byte[] cf, String row, String col, long versionStart,
129      long versionEnd) throws IOException {
130    byte columnBytes[] = Bytes.toBytes(col);
131    Put put = new Put(Bytes.toBytes(row));
132    put.setDurability(Durability.SKIP_WAL);
133
134    for (long version = versionStart; version <= versionEnd; version++) {
135      put.addColumn(cf, columnBytes, version, genValue(row, col, version));
136    }
137    region.put(put);
138  }
139
140  private Cell[] getData(String family, String row, List<String> columns,
141      int expBlocks) throws IOException {
142    return getData(family, row, columns, expBlocks, expBlocks, expBlocks);
143  }
144
145  private Cell[] getData(String family, String row, List<String> columns,
146      int expBlocksRowCol, int expBlocksRow, int expBlocksNone)
147      throws IOException {
148    int[] expBlocks = new int[] { expBlocksRowCol, expBlocksRow, expBlocksNone };
149    Cell[] kvs = null;
150
151    for (int i = 0; i < BLOOM_TYPE.length; i++) {
152      BloomType bloomType = BLOOM_TYPE[i];
153      byte[] cf = Bytes.toBytes(family + "_" + bloomType);
154      long blocksStart = getBlkAccessCount(cf);
155      Get get = new Get(Bytes.toBytes(row));
156
157      for (String column : columns) {
158        get.addColumn(cf, Bytes.toBytes(column));
159      }
160
161      kvs = region.get(get).rawCells();
162      long blocksEnd = getBlkAccessCount(cf);
163      if (expBlocks[i] != -1) {
164        assertEquals("Blocks Read Check for Bloom: " + bloomType, expBlocks[i],
165            blocksEnd - blocksStart);
166      }
167      System.out.println("Blocks Read for Bloom: " + bloomType + " = "
168          + (blocksEnd - blocksStart) + "Expected = " + expBlocks[i]);
169    }
170    return kvs;
171  }
172
173  private Cell[] getData(String family, String row, String column,
174      int expBlocks) throws IOException {
175    return getData(family, row, Arrays.asList(column), expBlocks, expBlocks,
176        expBlocks);
177  }
178
179  private Cell[] getData(String family, String row, String column,
180      int expBlocksRowCol, int expBlocksRow, int expBlocksNone)
181      throws IOException {
182    return getData(family, row, Arrays.asList(column), expBlocksRowCol,
183        expBlocksRow, expBlocksNone);
184  }
185
186  private void deleteFamily(String family, String row, long version)
187      throws IOException {
188    Delete del = new Delete(Bytes.toBytes(row));
189    del.addFamily(Bytes.toBytes(family + "_ROWCOL"), version);
190    del.addFamily(Bytes.toBytes(family + "_ROW"), version);
191    del.addFamily(Bytes.toBytes(family + "_NONE"), version);
192    region.delete(del);
193  }
194
195  private static void verifyData(Cell kv, String expectedRow,
196      String expectedCol, long expectedVersion) {
197    assertTrue("RowCheck", CellUtil.matchingRows(kv,  Bytes.toBytes(expectedRow)));
198    assertTrue("ColumnCheck", CellUtil.matchingQualifier(kv, Bytes.toBytes(expectedCol)));
199    assertEquals("TSCheck", expectedVersion, kv.getTimestamp());
200    assertTrue("ValueCheck", CellUtil.matchingValue(kv, genValue(expectedRow, expectedCol, expectedVersion)));
201  }
202
203  private static long getBlkAccessCount(byte[] cf) {
204      return HFile.DATABLOCK_READ_COUNT.sum();
205  }
206
207  private static long getBlkCount() {
208    return blockCache.getBlockCount();
209  }
210
211  /**
212   * Test # of blocks read for some simple seek cases.
213   *
214   * @throws Exception
215   */
216  @Test
217  public void testBlocksRead() throws Exception {
218    byte[] TABLE = Bytes.toBytes("testBlocksRead");
219    String FAMILY = "cf1";
220    Cell kvs[];
221    this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY);
222
223    try {
224      putData(FAMILY, "row", "col1", 1);
225      putData(FAMILY, "row", "col2", 2);
226      putData(FAMILY, "row", "col3", 3);
227      putData(FAMILY, "row", "col4", 4);
228      putData(FAMILY, "row", "col5", 5);
229      putData(FAMILY, "row", "col6", 6);
230      putData(FAMILY, "row", "col7", 7);
231      region.flush(true);
232
233      // Expected block reads: 1
234      // The top block has the KV we are
235      // interested. So only 1 seek is needed.
236      kvs = getData(FAMILY, "row", "col1", 1);
237      assertEquals(1, kvs.length);
238      verifyData(kvs[0], "row", "col1", 1);
239
240      // Expected block reads: 2
241      // The top block and next block has the KVs we are
242      // interested. So only 2 seek is needed.
243      kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2"), 2);
244      assertEquals(2, kvs.length);
245      verifyData(kvs[0], "row", "col1", 1);
246      verifyData(kvs[1], "row", "col2", 2);
247
248      // Expected block reads: 3
249      // The first 2 seeks is to find out col2. [HBASE-4443]
250      // One additional seek for col3
251      // So 3 seeks are needed.
252      kvs = getData(FAMILY, "row", Arrays.asList("col2", "col3"), 2);
253      assertEquals(2, kvs.length);
254      verifyData(kvs[0], "row", "col2", 2);
255      verifyData(kvs[1], "row", "col3", 3);
256
257      // Expected block reads: 1. [HBASE-4443]&[HBASE-7845]
258      kvs = getData(FAMILY, "row", Arrays.asList("col5"), 1);
259      assertEquals(1, kvs.length);
260      verifyData(kvs[0], "row", "col5", 5);
261    } finally {
262      HBaseTestingUtility.closeRegionAndWAL(this.region);
263      this.region = null;
264    }
265  }
266
267  /**
268   * Test # of blocks read (targeted at some of the cases Lazy Seek optimizes).
269   *
270   * @throws Exception
271   */
272  @Test
273  public void testLazySeekBlocksRead() throws Exception {
274    byte[] TABLE = Bytes.toBytes("testLazySeekBlocksRead");
275    String FAMILY = "cf1";
276    Cell kvs[];
277    this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY);
278
279    try {
280      // File 1
281      putData(FAMILY, "row", "col1", 1);
282      putData(FAMILY, "row", "col2", 2);
283      region.flush(true);
284
285      // File 2
286      putData(FAMILY, "row", "col1", 3);
287      putData(FAMILY, "row", "col2", 4);
288      region.flush(true);
289
290      // Expected blocks read: 1.
291      // File 2's top block is also the KV we are
292      // interested. So only 1 seek is needed.
293      kvs = getData(FAMILY, "row", Arrays.asList("col1"), 1);
294      assertEquals(1, kvs.length);
295      verifyData(kvs[0], "row", "col1", 3);
296
297      // Expected blocks read: 2
298      // File 2's top block has the "col1" KV we are
299      // interested. We also need "col2" which is in a block
300      // of its own. So, we need that block as well.
301      kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2"), 2);
302      assertEquals(2, kvs.length);
303      verifyData(kvs[0], "row", "col1", 3);
304      verifyData(kvs[1], "row", "col2", 4);
305
306      // File 3: Add another column
307      putData(FAMILY, "row", "col3", 5);
308      region.flush(true);
309
310      // Expected blocks read: 1
311      // File 3's top block has the "col3" KV we are
312      // interested. So only 1 seek is needed.
313      kvs = getData(FAMILY, "row", "col3", 1);
314      assertEquals(1, kvs.length);
315      verifyData(kvs[0], "row", "col3", 5);
316
317      // Get a column from older file.
318      // For ROWCOL Bloom filter: Expected blocks read: 1.
319      // For ROW Bloom filter: Expected blocks read: 2.
320      // For NONE Bloom filter: Expected blocks read: 2.
321      kvs = getData(FAMILY, "row", Arrays.asList("col1"), 1, 2, 2);
322      assertEquals(1, kvs.length);
323      verifyData(kvs[0], "row", "col1", 3);
324
325      // File 4: Delete the entire row.
326      deleteFamily(FAMILY, "row", 6);
327      region.flush(true);
328
329      // For ROWCOL Bloom filter: Expected blocks read: 2.
330      // For ROW Bloom filter: Expected blocks read: 3.
331      // For NONE Bloom filter: Expected blocks read: 3.
332      kvs = getData(FAMILY, "row", "col1", 2, 3, 3);
333      assertEquals(0, kvs.length);
334      kvs = getData(FAMILY, "row", "col2", 2, 3, 3);
335      assertEquals(0, kvs.length);
336      kvs = getData(FAMILY, "row", "col3", 2);
337      assertEquals(0, kvs.length);
338      kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 4);
339      assertEquals(0, kvs.length);
340
341      // File 5: Delete
342      deleteFamily(FAMILY, "row", 10);
343      region.flush(true);
344
345      // File 6: some more puts, but with timestamps older than the
346      // previous delete.
347      putData(FAMILY, "row", "col1", 7);
348      putData(FAMILY, "row", "col2", 8);
349      putData(FAMILY, "row", "col3", 9);
350      region.flush(true);
351
352      // Baseline expected blocks read: 6. [HBASE-4532]
353      kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 6, 7, 7);
354      assertEquals(0, kvs.length);
355
356      // File 7: Put back new data
357      putData(FAMILY, "row", "col1", 11);
358      putData(FAMILY, "row", "col2", 12);
359      putData(FAMILY, "row", "col3", 13);
360      region.flush(true);
361
362
363      // Expected blocks read: 8. [HBASE-4585, HBASE-13109]
364      kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 8, 9, 9);
365      assertEquals(3, kvs.length);
366      verifyData(kvs[0], "row", "col1", 11);
367      verifyData(kvs[1], "row", "col2", 12);
368      verifyData(kvs[2], "row", "col3", 13);
369    } finally {
370      HBaseTestingUtility.closeRegionAndWAL(this.region);
371      this.region = null;
372    }
373  }
374
375  /**
376   * Test # of blocks read to ensure disabling cache-fill on Scan works.
377   * @throws Exception
378   */
379  @Test
380  public void testBlocksStoredWhenCachingDisabled() throws Exception {
381    byte [] TABLE = Bytes.toBytes("testBlocksReadWhenCachingDisabled");
382    String FAMILY = "cf1";
383
384    this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY);
385
386    try {
387      putData(FAMILY, "row", "col1", 1);
388      putData(FAMILY, "row", "col2", 2);
389      region.flush(true);
390
391      // Execute a scan with caching turned off
392      // Expected blocks stored: 0
393      long blocksStart = getBlkCount();
394      Scan scan = new Scan();
395      scan.setCacheBlocks(false);
396      RegionScanner rs = region.getScanner(scan);
397      List<Cell> result = new ArrayList<>(2);
398      rs.next(result);
399      assertEquals(2 * BLOOM_TYPE.length, result.size());
400      rs.close();
401      long blocksEnd = getBlkCount();
402
403      assertEquals(blocksStart, blocksEnd);
404
405      // Execute with caching turned on
406      // Expected blocks stored: 2
407      blocksStart = blocksEnd;
408      scan.setCacheBlocks(true);
409      rs = region.getScanner(scan);
410      result = new ArrayList<>(2);
411      rs.next(result);
412      assertEquals(2 * BLOOM_TYPE.length, result.size());
413      rs.close();
414      blocksEnd = getBlkCount();
415
416      assertEquals(2 * BLOOM_TYPE.length, blocksEnd - blocksStart);
417    } finally {
418      HBaseTestingUtility.closeRegionAndWAL(this.region);
419      this.region = null;
420    }
421  }
422
423  @Test
424  public void testLazySeekBlocksReadWithDelete() throws Exception {
425    byte[] TABLE = Bytes.toBytes("testLazySeekBlocksReadWithDelete");
426    String FAMILY = "cf1";
427    Cell kvs[];
428    this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY);
429    try {
430      deleteFamily(FAMILY, "row", 200);
431      for (int i = 0; i < 100; i++) {
432        putData(FAMILY, "row", "col" + i, i);
433      }
434      putData(FAMILY, "row", "col99", 201);
435      region.flush(true);
436
437      kvs = getData(FAMILY, "row", Arrays.asList("col0"), 2);
438      assertEquals(0, kvs.length);
439
440      kvs = getData(FAMILY, "row", Arrays.asList("col99"), 2);
441      assertEquals(1, kvs.length);
442      verifyData(kvs[0], "row", "col99", 201);
443    } finally {
444      HBaseTestingUtility.closeRegionAndWAL(this.region);
445      this.region = null;
446    }
447  }
448
449}