1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.hadoop.hbase.io.encoding;
18
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.fail;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.DataInputStream;
25 import java.io.DataOutputStream;
26 import java.io.IOException;
27 import java.nio.ByteBuffer;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.List;
31 import java.util.Random;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.hadoop.hbase.CategoryBasedTimeout;
36 import org.apache.hadoop.hbase.HBaseTestingUtility;
37 import org.apache.hadoop.hbase.HConstants;
38 import org.apache.hadoop.hbase.KeyValue;
39 import org.apache.hadoop.hbase.KeyValue.Type;
40 import org.apache.hadoop.hbase.KeyValueUtil;
41 import org.apache.hadoop.hbase.Tag;
42 import org.apache.hadoop.hbase.io.compress.Compression;
43 import org.apache.hadoop.hbase.io.hfile.HFileBlock.Writer.BufferGrabbingByteArrayOutputStream;
44 import org.apache.hadoop.hbase.io.hfile.HFileContext;
45 import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
46 import org.apache.hadoop.hbase.testclassification.LargeTests;
47 import org.apache.hadoop.hbase.util.Bytes;
48 import org.apache.hadoop.hbase.util.test.RedundantKVGenerator;
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.experimental.categories.Category;
52 import org.junit.rules.TestRule;
53 import org.junit.runner.RunWith;
54 import org.junit.runners.Parameterized;
55 import org.junit.runners.Parameterized.Parameters;
56
57
58
59
60
61 @Category(LargeTests.class)
62 @RunWith(Parameterized.class)
63 public class TestDataBlockEncoders {
64
65 private static final Log LOG = LogFactory.getLog(TestDataBlockEncoders.class);
66
67 @Rule public final TestRule timeout = CategoryBasedTimeout.builder().
68 withTimeout(this.getClass()).withLookingForStuckThread(true).build();
69
70 private static int NUMBER_OF_KV = 10000;
71 private static int NUM_RANDOM_SEEKS = 1000;
72
73 private static int ENCODED_DATA_OFFSET = HConstants.HFILEBLOCK_HEADER_SIZE
74 + DataBlockEncoding.ID_SIZE;
75 static final byte[] HFILEBLOCK_DUMMY_HEADER = new byte[HConstants.HFILEBLOCK_HEADER_SIZE];
76
77 private RedundantKVGenerator generator = new RedundantKVGenerator();
78 private Random randomizer = new Random(42l);
79
80 private final boolean includesMemstoreTS;
81 private final boolean includesTags;
82
83 @Parameters
84 public static Collection<Object[]> parameters() {
85 return HBaseTestingUtility.MEMSTORETS_TAGS_PARAMETRIZED;
86 }
87 public TestDataBlockEncoders(boolean includesMemstoreTS, boolean includesTag) {
88 this.includesMemstoreTS = includesMemstoreTS;
89 this.includesTags = includesTag;
90 }
91
92 private HFileBlockEncodingContext getEncodingContext(Compression.Algorithm algo,
93 DataBlockEncoding encoding) {
94 DataBlockEncoder encoder = encoding.getEncoder();
95 HFileContext meta = new HFileContextBuilder()
96 .withHBaseCheckSum(false)
97 .withIncludesMvcc(includesMemstoreTS)
98 .withIncludesTags(includesTags)
99 .withCompression(algo).build();
100 if (encoder != null) {
101 return encoder.newDataBlockEncodingContext(encoding, HFILEBLOCK_DUMMY_HEADER, meta);
102 } else {
103 return new HFileBlockDefaultEncodingContext(encoding, HFILEBLOCK_DUMMY_HEADER, meta);
104 }
105 }
106
107
108
109
110
111
112
113 @Test
114 public void testEmptyKeyValues() throws IOException {
115 List<KeyValue> kvList = new ArrayList<KeyValue>();
116 byte[] row = new byte[0];
117 byte[] family = new byte[0];
118 byte[] qualifier = new byte[0];
119 byte[] value = new byte[0];
120 if (!includesTags) {
121 kvList.add(new KeyValue(row, family, qualifier, 0l, value));
122 kvList.add(new KeyValue(row, family, qualifier, 0l, value));
123 } else {
124 byte[] metaValue1 = Bytes.toBytes("metaValue1");
125 byte[] metaValue2 = Bytes.toBytes("metaValue2");
126 kvList.add(new KeyValue(row, family, qualifier, 0l, value, new Tag[] { new Tag((byte) 1,
127 metaValue1) }));
128 kvList.add(new KeyValue(row, family, qualifier, 0l, value, new Tag[] { new Tag((byte) 1,
129 metaValue2) }));
130 }
131 testEncodersOnDataset(kvList, includesMemstoreTS, includesTags);
132 }
133
134
135
136
137
138
139
140 @Test
141 public void testNegativeTimestamps() throws IOException {
142 List<KeyValue> kvList = new ArrayList<KeyValue>();
143 byte[] row = new byte[0];
144 byte[] family = new byte[0];
145 byte[] qualifier = new byte[0];
146 byte[] value = new byte[0];
147 if (includesTags) {
148 byte[] metaValue1 = Bytes.toBytes("metaValue1");
149 byte[] metaValue2 = Bytes.toBytes("metaValue2");
150 kvList.add(new KeyValue(row, family, qualifier, 0l, value, new Tag[] { new Tag((byte) 1,
151 metaValue1) }));
152 kvList.add(new KeyValue(row, family, qualifier, 0l, value, new Tag[] { new Tag((byte) 1,
153 metaValue2) }));
154 } else {
155 kvList.add(new KeyValue(row, family, qualifier, -1l, Type.Put, value));
156 kvList.add(new KeyValue(row, family, qualifier, -2l, Type.Put, value));
157 }
158 testEncodersOnDataset(kvList, includesMemstoreTS, includesTags);
159 }
160
161
162
163
164
165
166
167 @Test
168 public void testExecutionOnSample() throws IOException {
169 List<KeyValue> kvList = generator.generateTestKeyValues(NUMBER_OF_KV, includesTags);
170 testEncodersOnDataset(kvList, includesMemstoreTS, includesTags);
171 }
172
173
174
175
176 @Test
177 public void testSeekingOnSample() throws IOException {
178 List<KeyValue> sampleKv = generator.generateTestKeyValues(NUMBER_OF_KV, includesTags);
179
180
181 List<DataBlockEncoder.EncodedSeeker> encodedSeekers =
182 new ArrayList<DataBlockEncoder.EncodedSeeker>();
183 for (DataBlockEncoding encoding : DataBlockEncoding.values()) {
184 LOG.info("Encoding: " + encoding);
185
186
187 if (encoding == DataBlockEncoding.PREFIX_TREE) continue;
188 DataBlockEncoder encoder = encoding.getEncoder();
189 if (encoder == null) {
190 continue;
191 }
192 LOG.info("Encoder: " + encoder);
193 ByteBuffer encodedBuffer = encodeKeyValues(encoding, sampleKv,
194 getEncodingContext(Compression.Algorithm.NONE, encoding));
195 HFileContext meta = new HFileContextBuilder()
196 .withHBaseCheckSum(false)
197 .withIncludesMvcc(includesMemstoreTS)
198 .withIncludesTags(includesTags)
199 .withCompression(Compression.Algorithm.NONE)
200 .build();
201 DataBlockEncoder.EncodedSeeker seeker = encoder.createSeeker(KeyValue.COMPARATOR,
202 encoder.newDataBlockDecodingContext(meta));
203 seeker.setCurrentBuffer(encodedBuffer);
204 encodedSeekers.add(seeker);
205 }
206 LOG.info("Testing it!");
207
208
209 for (boolean seekBefore : new boolean[] { false, true }) {
210 for (int i = 0; i < NUM_RANDOM_SEEKS; ++i) {
211 int keyValueId;
212 if (!seekBefore) {
213 keyValueId = randomizer.nextInt(sampleKv.size());
214 } else {
215 keyValueId = randomizer.nextInt(sampleKv.size() - 1) + 1;
216 }
217
218 KeyValue keyValue = sampleKv.get(keyValueId);
219 checkSeekingConsistency(encodedSeekers, seekBefore, keyValue);
220 }
221 }
222
223
224 LOG.info("Checking edge cases");
225 checkSeekingConsistency(encodedSeekers, false, sampleKv.get(0));
226 for (boolean seekBefore : new boolean[] { false, true }) {
227 checkSeekingConsistency(encodedSeekers, seekBefore, sampleKv.get(sampleKv.size() - 1));
228 KeyValue midKv = sampleKv.get(sampleKv.size() / 2);
229 KeyValue lastMidKv =KeyValueUtil.createLastOnRowCol(midKv);
230 checkSeekingConsistency(encodedSeekers, seekBefore, lastMidKv);
231 }
232 LOG.info("Done");
233 }
234
235 static ByteBuffer encodeKeyValues(DataBlockEncoding encoding, List<KeyValue> kvs,
236 HFileBlockEncodingContext encodingContext) throws IOException {
237 DataBlockEncoder encoder = encoding.getEncoder();
238 ByteArrayOutputStream baos = new ByteArrayOutputStream();
239 baos.write(HFILEBLOCK_DUMMY_HEADER);
240 DataOutputStream dos = new DataOutputStream(baos);
241 encoder.startBlockEncoding(encodingContext, dos);
242 for (KeyValue kv : kvs) {
243 encoder.encode(kv, encodingContext, dos);
244 }
245 BufferGrabbingByteArrayOutputStream stream = new BufferGrabbingByteArrayOutputStream();
246 baos.writeTo(stream);
247 encoder.endBlockEncoding(encodingContext, dos, stream.getBuffer());
248 byte[] encodedData = new byte[baos.size() - ENCODED_DATA_OFFSET];
249 System.arraycopy(baos.toByteArray(), ENCODED_DATA_OFFSET, encodedData, 0, encodedData.length);
250 return ByteBuffer.wrap(encodedData);
251 }
252
253 @Test
254 public void testNextOnSample() throws IOException {
255 List<KeyValue> sampleKv = generator.generateTestKeyValues(NUMBER_OF_KV, includesTags);
256
257 for (DataBlockEncoding encoding : DataBlockEncoding.values()) {
258 if (encoding.getEncoder() == null) {
259 continue;
260 }
261 DataBlockEncoder encoder = encoding.getEncoder();
262 ByteBuffer encodedBuffer = encodeKeyValues(encoding, sampleKv,
263 getEncodingContext(Compression.Algorithm.NONE, encoding));
264 HFileContext meta = new HFileContextBuilder()
265 .withHBaseCheckSum(false)
266 .withIncludesMvcc(includesMemstoreTS)
267 .withIncludesTags(includesTags)
268 .withCompression(Compression.Algorithm.NONE)
269 .build();
270 DataBlockEncoder.EncodedSeeker seeker = encoder.createSeeker(KeyValue.COMPARATOR,
271 encoder.newDataBlockDecodingContext(meta));
272 seeker.setCurrentBuffer(encodedBuffer);
273 int i = 0;
274 do {
275 KeyValue expectedKeyValue = sampleKv.get(i);
276 KeyValue keyValue = KeyValueUtil.copyToNewKeyValue(seeker.getKeyValue());
277 if (0 != Bytes.compareTo(keyValue.getBuffer(), keyValue.getOffset(), keyValue.getLength(),
278 expectedKeyValue.getBuffer(), expectedKeyValue.getOffset(),
279 expectedKeyValue.getLength())) {
280
281 int commonPrefix = 0;
282 byte[] left = keyValue.getBuffer();
283 byte[] right = expectedKeyValue.getBuffer();
284 int leftOff = keyValue.getOffset();
285 int rightOff = expectedKeyValue.getOffset();
286 int length = Math.min(keyValue.getLength(), expectedKeyValue.getLength());
287 while (commonPrefix < length
288 && left[commonPrefix + leftOff] == right[commonPrefix + rightOff]) {
289 commonPrefix++;
290 }
291
292 fail(String.format("next() produces wrong results "
293 + "encoder: %s i: %d commonPrefix: %d" + "\n expected %s\n actual %s", encoder
294 .toString(), i, commonPrefix, Bytes.toStringBinary(expectedKeyValue.getBuffer(),
295 expectedKeyValue.getOffset(), expectedKeyValue.getLength()), Bytes
296 .toStringBinary(keyValue.getBuffer())));
297 }
298 i++;
299 } while (seeker.next());
300 }
301 }
302
303
304
305
306
307 @Test
308 public void testFirstKeyInBlockOnSample() throws IOException {
309 List<KeyValue> sampleKv = generator.generateTestKeyValues(NUMBER_OF_KV, includesTags);
310
311 for (DataBlockEncoding encoding : DataBlockEncoding.values()) {
312 if (encoding.getEncoder() == null) {
313 continue;
314 }
315 DataBlockEncoder encoder = encoding.getEncoder();
316 ByteBuffer encodedBuffer = encodeKeyValues(encoding, sampleKv,
317 getEncodingContext(Compression.Algorithm.NONE, encoding));
318 ByteBuffer keyBuffer = encoder.getFirstKeyInBlock(encodedBuffer);
319 KeyValue firstKv = sampleKv.get(0);
320 if (0 != Bytes.compareTo(keyBuffer.array(), keyBuffer.arrayOffset(), keyBuffer.limit(),
321 firstKv.getBuffer(), firstKv.getKeyOffset(), firstKv.getKeyLength())) {
322
323 int commonPrefix = 0;
324 int length = Math.min(keyBuffer.limit(), firstKv.getKeyLength());
325 while (commonPrefix < length
326 && keyBuffer.array()[keyBuffer.arrayOffset() + commonPrefix] == firstKv.getBuffer()[firstKv
327 .getKeyOffset() + commonPrefix]) {
328 commonPrefix++;
329 }
330 fail(String.format("Bug in '%s' commonPrefix %d", encoder.toString(), commonPrefix));
331 }
332 }
333 }
334
335 private void checkSeekingConsistency(List<DataBlockEncoder.EncodedSeeker> encodedSeekers,
336 boolean seekBefore, KeyValue keyValue) {
337 ByteBuffer expectedKeyValue = null;
338 ByteBuffer expectedKey = null;
339 ByteBuffer expectedValue = null;
340 for (DataBlockEncoder.EncodedSeeker seeker : encodedSeekers) {
341 seeker.seekToKeyInBlock(keyValue, seekBefore);
342 seeker.rewind();
343
344 ByteBuffer actualKeyValue = KeyValueUtil.copyKeyToNewByteBuffer(seeker.getKeyValue());
345 ByteBuffer actualKey = seeker.getKeyDeepCopy();
346 ByteBuffer actualValue = seeker.getValueShallowCopy();
347
348 if (expectedKeyValue != null) {
349 assertEquals(expectedKeyValue, actualKeyValue);
350 } else {
351 expectedKeyValue = actualKeyValue;
352 }
353
354 if (expectedKey != null) {
355 assertEquals(expectedKey, actualKey);
356 } else {
357 expectedKey = actualKey;
358 }
359
360 if (expectedValue != null) {
361 assertEquals(expectedValue, actualValue);
362 } else {
363 expectedValue = actualValue;
364 }
365 }
366 }
367
368 private void testEncodersOnDataset(List<KeyValue> kvList, boolean includesMemstoreTS,
369 boolean includesTags) throws IOException {
370 ByteBuffer unencodedDataBuf = RedundantKVGenerator.convertKvToByteBuffer(kvList,
371 includesMemstoreTS);
372 HFileContext fileContext = new HFileContextBuilder().withIncludesMvcc(includesMemstoreTS)
373 .withIncludesTags(includesTags).build();
374 for (DataBlockEncoding encoding : DataBlockEncoding.values()) {
375 DataBlockEncoder encoder = encoding.getEncoder();
376 if (encoder == null) {
377 continue;
378 }
379 HFileBlockEncodingContext encodingContext = new HFileBlockDefaultEncodingContext(encoding,
380 HFILEBLOCK_DUMMY_HEADER, fileContext);
381
382 ByteArrayOutputStream baos = new ByteArrayOutputStream();
383 baos.write(HFILEBLOCK_DUMMY_HEADER);
384 DataOutputStream dos = new DataOutputStream(baos);
385 encoder.startBlockEncoding(encodingContext, dos);
386 for (KeyValue kv : kvList) {
387 encoder.encode(kv, encodingContext, dos);
388 }
389 BufferGrabbingByteArrayOutputStream stream = new BufferGrabbingByteArrayOutputStream();
390 baos.writeTo(stream);
391 encoder.endBlockEncoding(encodingContext, dos, stream.getBuffer());
392 byte[] encodedData = baos.toByteArray();
393
394 testAlgorithm(encodedData, unencodedDataBuf, encoder);
395 }
396 }
397
398 @Test
399 public void testZeroByte() throws IOException {
400 List<KeyValue> kvList = new ArrayList<KeyValue>();
401 byte[] row = Bytes.toBytes("abcd");
402 byte[] family = new byte[] { 'f' };
403 byte[] qualifier0 = new byte[] { 'b' };
404 byte[] qualifier1 = new byte[] { 'c' };
405 byte[] value0 = new byte[] { 'd' };
406 byte[] value1 = new byte[] { 0x00 };
407 if (includesTags) {
408 kvList.add(new KeyValue(row, family, qualifier0, 0, value0, new Tag[] { new Tag((byte) 1,
409 "value1") }));
410 kvList.add(new KeyValue(row, family, qualifier1, 0, value1, new Tag[] { new Tag((byte) 1,
411 "value1") }));
412 } else {
413 kvList.add(new KeyValue(row, family, qualifier0, 0, Type.Put, value0));
414 kvList.add(new KeyValue(row, family, qualifier1, 0, Type.Put, value1));
415 }
416 testEncodersOnDataset(kvList, includesMemstoreTS, includesTags);
417 }
418
419 private void testAlgorithm(byte[] encodedData, ByteBuffer unencodedDataBuf,
420 DataBlockEncoder encoder) throws IOException {
421
422 ByteArrayInputStream bais = new ByteArrayInputStream(encodedData, ENCODED_DATA_OFFSET,
423 encodedData.length - ENCODED_DATA_OFFSET);
424 DataInputStream dis = new DataInputStream(bais);
425 ByteBuffer actualDataset;
426 HFileContext meta = new HFileContextBuilder().withHBaseCheckSum(false)
427 .withIncludesMvcc(includesMemstoreTS).withIncludesTags(includesTags)
428 .withCompression(Compression.Algorithm.NONE).build();
429 actualDataset = encoder.decodeKeyValues(dis, encoder.newDataBlockDecodingContext(meta));
430 actualDataset.rewind();
431
432
433
434
435 assertEquals("Encoding -> decoding gives different results for " + encoder,
436 Bytes.toStringBinary(unencodedDataBuf), Bytes.toStringBinary(actualDataset));
437 }
438 }