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