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.io.hfile; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022import static org.junit.jupiter.api.Assertions.fail; 023 024import java.io.DataOutputStream; 025import java.io.IOException; 026import java.nio.ByteBuffer; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.stream.Stream; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.ExtendedCell; 032import org.apache.hadoop.hbase.HBaseConfiguration; 033import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate; 034import org.apache.hadoop.hbase.HConstants; 035import org.apache.hadoop.hbase.KeyValue; 036import org.apache.hadoop.hbase.io.ByteArrayOutputStream; 037import org.apache.hadoop.hbase.io.ByteBuffAllocator; 038import org.apache.hadoop.hbase.io.HeapSize; 039import org.apache.hadoop.hbase.io.compress.Compression.Algorithm; 040import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 041import org.apache.hadoop.hbase.io.encoding.HFileBlockDefaultEncodingContext; 042import org.apache.hadoop.hbase.io.encoding.HFileBlockEncodingContext; 043import org.apache.hadoop.hbase.nio.ByteBuff; 044import org.apache.hadoop.hbase.testclassification.IOTests; 045import org.apache.hadoop.hbase.testclassification.MediumTests; 046import org.apache.hadoop.hbase.util.ChecksumType; 047import org.apache.hadoop.hbase.util.RedundantKVGenerator; 048import org.junit.jupiter.api.Tag; 049import org.junit.jupiter.api.TestTemplate; 050import org.junit.jupiter.params.provider.Arguments; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054@Tag(IOTests.TAG) 055@Tag(MediumTests.TAG) 056@HBaseParameterizedTestTemplate(name = "{index}: dataBlockEncoding={0}, includesMemstoreTS={1}") 057public class TestHFileDataBlockEncoder { 058 059 private static final Logger LOG = LoggerFactory.getLogger(TestHFileDataBlockEncoder.class); 060 061 private final Configuration conf = HBaseConfiguration.create(); 062 private final RedundantKVGenerator generator = new RedundantKVGenerator(); 063 private HFileDataBlockEncoder blockEncoder; 064 private boolean includesMemstoreTS; 065 066 /** 067 * Create test for given data block encoding configuration. 068 * @param blockEncoder What kind of encoding policy will be used. 069 */ 070 public TestHFileDataBlockEncoder(HFileDataBlockEncoder blockEncoder, boolean includesMemstoreTS) { 071 this.blockEncoder = blockEncoder; 072 this.includesMemstoreTS = includesMemstoreTS; 073 LOG.info("Encoding: " + blockEncoder.getDataBlockEncoding() + ", includesMemstoreTS: " 074 + includesMemstoreTS); 075 } 076 077 /** 078 * Test putting and taking out blocks into cache with different encoding options. 079 */ 080 @TestTemplate 081 public void testEncodingWithCache() throws IOException { 082 testEncodingWithCacheInternals(false); 083 testEncodingWithCacheInternals(true); 084 } 085 086 private void testEncodingWithCacheInternals(boolean useTag) throws IOException { 087 List<KeyValue> kvs = generator.generateTestKeyValues(60, useTag); 088 HFileBlock block = getSampleHFileBlock(kvs, useTag); 089 HFileBlock cacheBlock = createBlockOnDisk(conf, kvs, block, useTag); 090 091 LruBlockCache blockCache = new LruBlockCache(8 * 1024 * 1024, 32 * 1024); 092 BlockCacheKey cacheKey = new BlockCacheKey("test", 0); 093 blockCache.cacheBlock(cacheKey, cacheBlock); 094 095 HeapSize heapSize = blockCache.getBlock(cacheKey, false, false, true); 096 assertTrue(heapSize instanceof HFileBlock); 097 098 HFileBlock returnedBlock = (HFileBlock) heapSize; 099 100 if (blockEncoder.getDataBlockEncoding() == DataBlockEncoding.NONE) { 101 assertEquals(block.getBufferReadOnly(), returnedBlock.getBufferReadOnly()); 102 } else { 103 if (BlockType.ENCODED_DATA != returnedBlock.getBlockType()) { 104 LOG.info("" + blockEncoder); 105 } 106 assertEquals(BlockType.ENCODED_DATA, returnedBlock.getBlockType()); 107 } 108 } 109 110 /** Test for HBASE-5746. */ 111 @TestTemplate 112 public void testHeaderSizeInCacheWithoutChecksum() throws Exception { 113 testHeaderSizeInCacheWithoutChecksumInternals(false); 114 testHeaderSizeInCacheWithoutChecksumInternals(true); 115 } 116 117 private void testHeaderSizeInCacheWithoutChecksumInternals(boolean useTags) throws IOException { 118 int headerSize = HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM; 119 // Create some KVs and create the block with old-style header. 120 List<KeyValue> kvs = generator.generateTestKeyValues(60, useTags); 121 ByteBuffer keyValues = RedundantKVGenerator.convertKvToByteBuffer(kvs, includesMemstoreTS); 122 int size = keyValues.limit(); 123 ByteBuffer buf = ByteBuffer.allocate(size + headerSize); 124 buf.position(headerSize); 125 keyValues.rewind(); 126 buf.put(keyValues); 127 HFileContext hfileContext = 128 new HFileContextBuilder().withHBaseCheckSum(false).withIncludesMvcc(includesMemstoreTS) 129 .withIncludesTags(useTags).withBlockSize(0).withChecksumType(ChecksumType.NULL).build(); 130 HFileBlock block = new HFileBlock(BlockType.DATA, size, size, -1, ByteBuff.wrap(buf), 131 HFileBlock.FILL_HEADER, 0, 0, -1, hfileContext, ByteBuffAllocator.HEAP); 132 HFileBlock cacheBlock = createBlockOnDisk(conf, kvs, block, useTags); 133 assertEquals(headerSize, cacheBlock.getDummyHeaderForVersion().length); 134 } 135 136 /** 137 * Test encoding. 138 */ 139 @TestTemplate 140 public void testEncoding() throws IOException { 141 testEncodingInternals(false); 142 testEncodingInternals(true); 143 } 144 145 /** 146 * Test encoding with offheap keyvalue. This test just verifies if the encoders work with DBB and 147 * does not use the getXXXArray() API 148 */ 149 @TestTemplate 150 public void testEncodingWithOffheapKeyValue() throws IOException { 151 // usually we have just block without headers, but don't complicate that 152 try { 153 List<ExtendedCell> kvs = generator.generateTestExtendedOffheapKeyValues(60, true); 154 HFileContext meta = new HFileContextBuilder().withIncludesMvcc(includesMemstoreTS) 155 .withIncludesTags(true).withHBaseCheckSum(true).withCompression(Algorithm.NONE) 156 .withBlockSize(0).withChecksumType(ChecksumType.NULL).build(); 157 writeBlock(conf, kvs, meta, true); 158 } catch (IllegalArgumentException e) { 159 fail("No exception should have been thrown"); 160 } 161 } 162 163 private void testEncodingInternals(boolean useTag) throws IOException { 164 // usually we have just block without headers, but don't complicate that 165 List<KeyValue> kvs = generator.generateTestKeyValues(60, useTag); 166 HFileBlock block = getSampleHFileBlock(kvs, useTag); 167 HFileBlock blockOnDisk = createBlockOnDisk(conf, kvs, block, useTag); 168 169 if (blockEncoder.getDataBlockEncoding() != DataBlockEncoding.NONE) { 170 assertEquals(BlockType.ENCODED_DATA, blockOnDisk.getBlockType()); 171 assertEquals(blockEncoder.getDataBlockEncoding().getId(), 172 blockOnDisk.getDataBlockEncodingId()); 173 } else { 174 assertEquals(BlockType.DATA, blockOnDisk.getBlockType()); 175 } 176 } 177 178 private HFileBlock getSampleHFileBlock(List<KeyValue> kvs, boolean useTag) { 179 ByteBuffer keyValues = RedundantKVGenerator.convertKvToByteBuffer(kvs, includesMemstoreTS); 180 int size = keyValues.limit(); 181 ByteBuffer buf = ByteBuffer.allocate(size + HConstants.HFILEBLOCK_HEADER_SIZE); 182 buf.position(HConstants.HFILEBLOCK_HEADER_SIZE); 183 keyValues.rewind(); 184 buf.put(keyValues); 185 HFileContext meta = new HFileContextBuilder().withIncludesMvcc(includesMemstoreTS) 186 .withIncludesTags(useTag).withHBaseCheckSum(true).withCompression(Algorithm.NONE) 187 .withBlockSize(0).withChecksumType(ChecksumType.NULL).build(); 188 HFileBlock b = new HFileBlock(BlockType.DATA, size, size, -1, ByteBuff.wrap(buf), 189 HFileBlock.FILL_HEADER, 0, 0, -1, meta, ByteBuffAllocator.HEAP); 190 return b; 191 } 192 193 private HFileBlock createBlockOnDisk(Configuration conf, List<KeyValue> kvs, HFileBlock block, 194 boolean useTags) throws IOException { 195 int size; 196 HFileBlockEncodingContext context = 197 new HFileBlockDefaultEncodingContext(conf, blockEncoder.getDataBlockEncoding(), 198 HConstants.HFILEBLOCK_DUMMY_HEADER, block.getHFileContext()); 199 200 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 201 baos.write(block.getDummyHeaderForVersion()); 202 DataOutputStream dos = new DataOutputStream(baos); 203 blockEncoder.startBlockEncoding(context, dos); 204 for (KeyValue kv : kvs) { 205 blockEncoder.encode(kv, context, dos); 206 } 207 blockEncoder.endBlockEncoding(context, dos, baos.getBuffer(), BlockType.DATA); 208 byte[] encodedBytes = baos.toByteArray(); 209 size = encodedBytes.length - block.getDummyHeaderForVersion().length; 210 return new HFileBlock(context.getBlockType(), size, size, -1, 211 ByteBuff.wrap(ByteBuffer.wrap(encodedBytes)), HFileBlock.FILL_HEADER, 0, 212 block.getOnDiskDataSizeWithHeader(), -1, block.getHFileContext(), ByteBuffAllocator.HEAP); 213 } 214 215 private void writeBlock(Configuration conf, List<ExtendedCell> kvs, HFileContext fileContext, 216 boolean useTags) throws IOException { 217 HFileBlockEncodingContext context = new HFileBlockDefaultEncodingContext(conf, 218 blockEncoder.getDataBlockEncoding(), HConstants.HFILEBLOCK_DUMMY_HEADER, fileContext); 219 220 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 221 baos.write(HConstants.HFILEBLOCK_DUMMY_HEADER); 222 DataOutputStream dos = new DataOutputStream(baos); 223 blockEncoder.startBlockEncoding(context, dos); 224 for (ExtendedCell kv : kvs) { 225 blockEncoder.encode(kv, context, dos); 226 } 227 } 228 229 public static Stream<Arguments> parameters() { 230 List<Arguments> configurations = new ArrayList<>(); 231 232 for (DataBlockEncoding diskAlgo : DataBlockEncoding.values()) { 233 for (boolean includesMemstoreTS : new boolean[] { false, true }) { 234 HFileDataBlockEncoder dbe = (diskAlgo == DataBlockEncoding.NONE) 235 ? NoOpDataBlockEncoder.INSTANCE 236 : new HFileDataBlockEncoderImpl(diskAlgo); 237 configurations.add(Arguments.of(dbe, includesMemstoreTS)); 238 } 239 } 240 241 return configurations.stream(); 242 } 243}