/* * Copyright 2011 The Apache Software Foundation * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.regionserver; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseTestCase; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.io.hfile.BlockCache; import org.apache.hadoop.hbase.io.hfile.CacheConfig; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper; import org.junit.Test; public class TestBlocksRead extends HBaseTestCase { static final Log LOG = LogFactory.getLog(TestBlocksRead.class); private static BlockCache blockCache; private HBaseConfiguration getConf() { HBaseConfiguration conf = new HBaseConfiguration(); // disable compactions in this test. conf.setInt("hbase.hstore.compactionThreshold", 10000); return conf; } HRegion region = null; private HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private final String DIR = TEST_UTIL.getDataTestDir("TestBlocksRead").toString(); /** * @see org.apache.hadoop.hbase.HBaseTestCase#setUp() */ @SuppressWarnings("deprecation") @Override protected void setUp() throws Exception { super.setUp(); } @SuppressWarnings("deprecation") @Override protected void tearDown() throws Exception { super.tearDown(); EnvironmentEdgeManagerTestHelper.reset(); } private void initHRegion (byte [] tableName, String callingMethod, HBaseConfiguration conf, byte [] ... families) throws IOException { HTableDescriptor htd = new HTableDescriptor(tableName); for(byte [] family : families) { HColumnDescriptor familyDesc = new HColumnDescriptor( family, HColumnDescriptor.DEFAULT_VERSIONS, HColumnDescriptor.DEFAULT_COMPRESSION, HColumnDescriptor.DEFAULT_IN_MEMORY, HColumnDescriptor.DEFAULT_BLOCKCACHE, 1, // small block size deliberate; each kv on its own block HColumnDescriptor.DEFAULT_TTL, HColumnDescriptor.DEFAULT_BLOOMFILTER, HColumnDescriptor.DEFAULT_REPLICATION_SCOPE); htd.addFamily(familyDesc); } HRegionInfo info = new HRegionInfo(htd.getName(), null, null, false); Path path = new Path(DIR + callingMethod); region = HRegion.createHRegion(info, path, conf, htd); blockCache = new CacheConfig(conf).getBlockCache(); } private void putData(byte[] cf, String row, String col, long version) throws IOException { putData(cf, row, col, version, version); } // generates a value to put for a row/col/version. private static byte[] genValue(String row, String col, long version) { return Bytes.toBytes("Value:" + row + "#" + col + "#" + version); } private void putData(byte[] cf, String row, String col, long versionStart, long versionEnd) throws IOException { byte columnBytes[] = Bytes.toBytes(col); Put put = new Put(Bytes.toBytes(row)); put.setWriteToWAL(false); for (long version = versionStart; version <= versionEnd; version++) { put.add(cf, columnBytes, version, genValue(row, col, version)); } region.put(put); } private KeyValue[] getData(byte[] cf, String row, List<String> columns, int expBlocks) throws IOException { long blocksStart = getBlkAccessCount(cf); Get get = new Get(Bytes.toBytes(row)); for (String column : columns) { get.addColumn(cf, Bytes.toBytes(column)); } KeyValue[] kvs = region.get(get, null).raw(); long blocksEnd = getBlkAccessCount(cf); if (expBlocks != -1) { assertEquals("Blocks Read Check", expBlocks, blocksEnd - blocksStart); } System.out.println("Blocks Read = " + (blocksEnd - blocksStart) + "Expected = " + expBlocks); return kvs; } private KeyValue[] getData(byte[] cf, String row, String column, int expBlocks) throws IOException { return getData(cf, row, Arrays.asList(column), expBlocks); } private void deleteFamily(byte[] cf, String row, long version) throws IOException { Delete del = new Delete(Bytes.toBytes(row)); del.deleteFamily(cf, version); region.delete(del, null, true); } private void deleteFamily(byte[] cf, String row, String column, long version) throws IOException { Delete del = new Delete(Bytes.toBytes(row)); del.deleteColumns(cf, Bytes.toBytes(column), version); region.delete(del, null, true); } private static void verifyData(KeyValue kv, String expectedRow, String expectedCol, long expectedVersion) { assertEquals("RowCheck", expectedRow, Bytes.toString(kv.getRow())); assertEquals("ColumnCheck", expectedCol, Bytes.toString(kv.getQualifier())); assertEquals("TSCheck", expectedVersion, kv.getTimestamp()); assertEquals("ValueCheck", Bytes.toString(genValue(expectedRow, expectedCol, expectedVersion)), Bytes.toString(kv.getValue())); } private static long getBlkAccessCount(byte[] cf) { return blockCache.getStats().getRequestCount(); } private static long getBlkCount() { return blockCache.getBlockCount(); } /** * Test # of blocks read for some simple seek cases. * @throws Exception */ @Test public void testBlocksRead() throws Exception { byte [] TABLE = Bytes.toBytes("testBlocksRead"); byte [] FAMILY = Bytes.toBytes("cf1"); byte [][] FAMILIES = new byte[][] { FAMILY }; KeyValue kvs[]; HBaseConfiguration conf = getConf(); initHRegion(TABLE, getName(), conf, FAMILIES); putData(FAMILY, "row", "col1", 1); putData(FAMILY, "row", "col2", 2); putData(FAMILY, "row", "col3", 3); putData(FAMILY, "row", "col4", 4); putData(FAMILY, "row", "col5", 5); putData(FAMILY, "row", "col6", 6); putData(FAMILY, "row", "col7", 7); region.flushcache(); // Expected block reads: 1 kvs = getData(FAMILY, "row", "col1", 1); assertEquals(1, kvs.length); verifyData(kvs[0], "row", "col1", 1); // Expected block reads: 2 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2"), 2); assertEquals(2, kvs.length); verifyData(kvs[0], "row", "col1", 1); verifyData(kvs[1], "row", "col2", 2); // Expected block reads: 3 kvs = getData(FAMILY, "row", Arrays.asList("col2", "col3"), 3); assertEquals(2, kvs.length); verifyData(kvs[0], "row", "col2", 2); verifyData(kvs[1], "row", "col3", 3); // Expected block reads: 3 kvs = getData(FAMILY, "row", Arrays.asList("col5"), 3); assertEquals(1, kvs.length); verifyData(kvs[0], "row", "col5", 5); } /** * Test # of blocks read (targetted at some of the cases Lazy Seek optimizes). * @throws Exception */ @Test public void testLazySeekBlocksRead() throws Exception { byte [] TABLE = Bytes.toBytes("testLazySeekBlocksRead"); byte [] FAMILY = Bytes.toBytes("cf1"); byte [][] FAMILIES = new byte[][] { FAMILY }; KeyValue kvs[]; HBaseConfiguration conf = getConf(); initHRegion(TABLE, getName(), conf, FAMILIES); // File 1 putData(FAMILY, "row", "col1", 1); putData(FAMILY, "row", "col2", 2); region.flushcache(); // File 2 putData(FAMILY, "row", "col1", 3); putData(FAMILY, "row", "col2", 4); region.flushcache(); // Baseline expected blocks read: 2 kvs = getData(FAMILY, "row", Arrays.asList("col1"), 2); assertEquals(1, kvs.length); verifyData(kvs[0], "row", "col1", 3); // Baseline expected blocks read: 4 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2"), 4); assertEquals(2, kvs.length); verifyData(kvs[0], "row", "col1", 3); verifyData(kvs[1], "row", "col2", 4); // File 3: Add another column putData(FAMILY, "row", "col3", 5); region.flushcache(); // Baseline expected blocks read: 5 kvs = getData(FAMILY, "row", "col3", 5); assertEquals(1, kvs.length); verifyData(kvs[0], "row", "col3", 5); // Get a column from older file. // Baseline expected blocks read: 3 kvs = getData(FAMILY, "row", Arrays.asList("col1"), 3); assertEquals(1, kvs.length); verifyData(kvs[0], "row", "col1", 3); // File 4: Delete the entire row. deleteFamily(FAMILY, "row", 6); region.flushcache(); // Baseline expected blocks read: 6. kvs = getData(FAMILY, "row", "col1", 6); assertEquals(0, kvs.length); kvs = getData(FAMILY, "row", "col2", 6); assertEquals(0, kvs.length); kvs = getData(FAMILY, "row", "col3", 6); assertEquals(0, kvs.length); kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 6); assertEquals(0, kvs.length); // File 5: Delete deleteFamily(FAMILY, "row", 10); region.flushcache(); // File 6: some more puts, but with timestamps older than the // previous delete. putData(FAMILY, "row", "col1", 7); putData(FAMILY, "row", "col2", 8); putData(FAMILY, "row", "col3", 9); region.flushcache(); // Baseline expected blocks read: 10 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 10); assertEquals(0, kvs.length); // File 7: Put back new data putData(FAMILY, "row", "col1", 11); putData(FAMILY, "row", "col2", 12); putData(FAMILY, "row", "col3", 13); region.flushcache(); // Baseline expected blocks read: 13 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 13); assertEquals(3, kvs.length); verifyData(kvs[0], "row", "col1", 11); verifyData(kvs[1], "row", "col2", 12); verifyData(kvs[2], "row", "col3", 13); } /** * Test # of blocks read to ensure disabling cache-fill on Scan works. * @throws Exception */ @Test public void testBlocksStoredWhenCachingDisabled() throws Exception { byte [] TABLE = Bytes.toBytes("testBlocksReadWhenCachingDisabled"); byte [] FAMILY = Bytes.toBytes("cf1"); byte [][] FAMILIES = new byte[][] { FAMILY }; HBaseConfiguration conf = getConf(); initHRegion(TABLE, getName(), conf, FAMILIES); putData(FAMILY, "row", "col1", 1); putData(FAMILY, "row", "col2", 2); region.flushcache(); // Execute a scan with caching turned off // Expected blocks stored: 0 long blocksStart = getBlkCount(); Scan scan = new Scan(); scan.setCacheBlocks(false); RegionScanner rs = region.getScanner(scan); List<KeyValue> result = new ArrayList<KeyValue>(2); rs.next(result); assertEquals(2, result.size()); rs.close(); long blocksEnd = getBlkCount(); assertEquals(blocksStart, blocksEnd); // Execute with caching turned on // Expected blocks stored: 2 blocksStart = blocksEnd; scan.setCacheBlocks(true); rs = region.getScanner(scan); result = new ArrayList<KeyValue>(2); rs.next(result); assertEquals(2, result.size()); rs.close(); blocksEnd = getBlkCount(); assertEquals(2, blocksEnd - blocksStart); } }