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