View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.TreeSet;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.fs.FileSystem;
35  import org.apache.hadoop.fs.Path;
36  import org.apache.hadoop.hbase.Cell;
37  import org.apache.hadoop.hbase.HBaseTestCase;
38  import org.apache.hadoop.hbase.HBaseTestingUtility;
39  import org.apache.hadoop.hbase.HColumnDescriptor;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.HRegionInfo;
42  import org.apache.hadoop.hbase.KeyValue;
43  import org.apache.hadoop.hbase.KeyValueUtil;
44  import org.apache.hadoop.hbase.testclassification.SmallTests;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.hbase.client.Scan;
47  import org.apache.hadoop.hbase.io.HFileLink;
48  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
49  import org.apache.hadoop.hbase.io.hfile.BlockCache;
50  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
51  import org.apache.hadoop.hbase.io.hfile.CacheStats;
52  import org.apache.hadoop.hbase.io.hfile.HFileContext;
53  import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
54  import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder;
55  import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoderImpl;
56  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
57  import org.apache.hadoop.hbase.util.BloomFilterFactory;
58  import org.apache.hadoop.hbase.util.Bytes;
59  import org.apache.hadoop.hbase.util.ChecksumType;
60  import org.apache.hadoop.hbase.util.FSUtils;
61  import org.junit.Test;
62  import org.junit.experimental.categories.Category;
63  import org.mockito.Mockito;
64  
65  import com.google.common.base.Joiner;
66  import com.google.common.collect.Iterables;
67  import com.google.common.collect.Lists;
68  
69  import static org.mockito.Mockito.mock;
70  import static org.mockito.Mockito.spy;
71  import static org.mockito.Mockito.when;
72  
73  /**
74   * Test HStoreFile
75   */
76  @Category(SmallTests.class)
77  public class TestStoreFile extends HBaseTestCase {
78    private static final Log LOG = LogFactory.getLog(TestStoreFile.class);
79    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
80    private CacheConfig cacheConf =  new CacheConfig(TEST_UTIL.getConfiguration());
81    private static String ROOT_DIR = TEST_UTIL.getDataTestDir("TestStoreFile").toString();
82    private static final ChecksumType CKTYPE = ChecksumType.CRC32C;
83    private static final int CKBYTES = 512;
84    private static String TEST_FAMILY = "cf";
85  
86    @Override
87    public void setUp() throws Exception {
88      super.setUp();
89    }
90  
91    @Override
92    public void tearDown() throws Exception {
93      super.tearDown();
94    }
95  
96    /**
97     * Write a file and then assert that we can read from top and bottom halves
98     * using two HalfMapFiles.
99     * @throws Exception
100    */
101   public void testBasicHalfMapFile() throws Exception {
102     final HRegionInfo hri =
103         new HRegionInfo(TableName.valueOf("testBasicHalfMapFileTb"));
104     HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
105       conf, fs, new Path(this.testDir, hri.getTable().getNameAsString()), hri);
106 
107     HFileContext meta = new HFileContextBuilder().withBlockSize(2*1024).build();
108     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
109             .withFilePath(regionFs.createTempName())
110             .withFileContext(meta)
111             .build();
112     writeStoreFile(writer);
113 
114     Path sfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
115     StoreFile sf = new StoreFile(this.fs, sfPath, conf, cacheConf,
116       BloomType.NONE);
117     checkHalfHFile(regionFs, sf);
118   }
119 
120   private void writeStoreFile(final StoreFile.Writer writer) throws IOException {
121     writeStoreFile(writer, Bytes.toBytes(getName()), Bytes.toBytes(getName()));
122   }
123 
124   // pick an split point (roughly halfway)
125   byte[] SPLITKEY = new byte[] { (LAST_CHAR + FIRST_CHAR)/2, FIRST_CHAR};
126 
127   /*
128    * Writes HStoreKey and ImmutableBytes data to passed writer and
129    * then closes it.
130    * @param writer
131    * @throws IOException
132    */
133   public static void writeStoreFile(final StoreFile.Writer writer, byte[] fam, byte[] qualifier)
134   throws IOException {
135     long now = System.currentTimeMillis();
136     try {
137       for (char d = FIRST_CHAR; d <= LAST_CHAR; d++) {
138         for (char e = FIRST_CHAR; e <= LAST_CHAR; e++) {
139           byte[] b = new byte[] { (byte) d, (byte) e };
140           writer.append(new KeyValue(b, fam, qualifier, now, b));
141         }
142       }
143     } finally {
144       writer.close();
145     }
146   }
147 
148   /**
149    * Test that our mechanism of writing store files in one region to reference
150    * store files in other regions works.
151    * @throws IOException
152    */
153   public void testReference() throws IOException {
154     final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testReferenceTb"));
155     HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
156       conf, fs, new Path(this.testDir, hri.getTable().getNameAsString()), hri);
157 
158     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
159     // Make a store file and write data to it.
160     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
161             .withFilePath(regionFs.createTempName())
162             .withFileContext(meta)
163             .build();
164     writeStoreFile(writer);
165 
166     Path hsfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
167     StoreFile hsf = new StoreFile(this.fs, hsfPath, conf, cacheConf,
168       BloomType.NONE);
169     StoreFile.Reader reader = hsf.createReader();
170     // Split on a row, not in middle of row.  Midkey returned by reader
171     // may be in middle of row.  Create new one with empty column and
172     // timestamp.
173 
174     KeyValue kv = KeyValue.createKeyValueFromKey(reader.midkey());
175     byte [] midRow = kv.getRow();
176     kv = KeyValue.createKeyValueFromKey(reader.getLastKey());
177     byte [] finalRow = kv.getRow();
178     hsf.closeReader(true);
179 
180     // Make a reference
181     HRegionInfo splitHri = new HRegionInfo(hri.getTable(), null, midRow);
182     Path refPath = splitStoreFile(regionFs, splitHri, TEST_FAMILY, hsf, midRow, true);
183     StoreFile refHsf = new StoreFile(this.fs, refPath, conf, cacheConf,
184       BloomType.NONE);
185     // Now confirm that I can read from the reference and that it only gets
186     // keys from top half of the file.
187     HFileScanner s = refHsf.createReader().getScanner(false, false);
188     for(boolean first = true; (!s.isSeeked() && s.seekTo()) || s.next();) {
189       ByteBuffer bb = s.getKey();
190       kv = KeyValue.createKeyValueFromKey(bb);
191       if (first) {
192         assertTrue(Bytes.equals(kv.getRow(), midRow));
193         first = false;
194       }
195     }
196     assertTrue(Bytes.equals(kv.getRow(), finalRow));
197   }
198 
199   public void testHFileLink() throws IOException {
200     final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testHFileLinkTb"));
201     // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/
202     Configuration testConf = new Configuration(this.conf);
203     FSUtils.setRootDir(testConf, this.testDir);
204     HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
205       testConf, fs, FSUtils.getTableDir(this.testDir, hri.getTable()), hri);
206     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
207 
208     // Make a store file and write data to it.
209     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
210             .withFilePath(regionFs.createTempName())
211             .withFileContext(meta)
212             .build();
213     writeStoreFile(writer);
214 
215     Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
216     Path dstPath = new Path(regionFs.getTableDir(), new Path("test-region", TEST_FAMILY));
217     HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName());
218     Path linkFilePath = new Path(dstPath,
219                   HFileLink.createHFileLinkName(hri, storeFilePath.getName()));
220 
221     // Try to open store file from link
222     StoreFileInfo storeFileInfo = new StoreFileInfo(testConf, this.fs, linkFilePath);
223     StoreFile hsf = new StoreFile(this.fs, storeFileInfo, testConf, cacheConf,
224       BloomType.NONE);
225     assertTrue(storeFileInfo.isLink());
226 
227     // Now confirm that I can read from the link
228     int count = 1;
229     HFileScanner s = hsf.createReader().getScanner(false, false);
230     s.seekTo();
231     while (s.next()) {
232       count++;
233     }
234     assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count);
235   }
236 
237   @Test
238   public void testStoreFileReference() throws Exception {
239     Path f = new Path(ROOT_DIR, getName());
240     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
241     // Make a store file and write data to it.
242     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs).withFilePath(f)
243         .withFileContext(meta).build();
244 
245     writeStoreFile(writer);
246     writer.close();
247 
248     // Creates a reader for StoreFile
249     StoreFile.Reader reader = new StoreFile.Reader(this.fs, f, cacheConf, conf);
250     StoreFileScanner scanner =
251         new StoreFileScanner(reader, mock(HFileScanner.class), false, false, 0, 0, false);
252 
253     // Verify after instantiating scanner refCount is increased
254     assertTrue(scanner.getReader().isReferencedInReads());
255     scanner.close();
256     // Verify after closing scanner refCount is decreased
257     assertFalse(scanner.getReader().isReferencedInReads());
258   }
259 
260   @Test
261   public void testEmptyStoreFileRestrictKeyRanges() throws Exception {
262     StoreFile.Reader reader = mock(StoreFile.Reader.class);
263     Store store = mock(Store.class);
264     HColumnDescriptor hcd = mock(HColumnDescriptor.class);
265     byte[] cf = Bytes.toBytes("ty");
266     when(hcd.getName()).thenReturn(cf);
267     when(store.getFamily()).thenReturn(hcd);
268     StoreFileScanner scanner =
269         new StoreFileScanner(reader, mock(HFileScanner.class), false, false, 0, 0, false);
270     Scan scan = new Scan();
271     scan.setColumnFamilyTimeRange(cf, 0, 1);
272     assertFalse(scanner.shouldUseScanner(scan, store, 0));
273   }
274 
275   /**
276    * This test creates an hfile and then the dir structures and files to verify that references
277    * to hfilelinks (created by snapshot clones) can be properly interpreted.
278    */
279   public void testReferenceToHFileLink() throws IOException {
280     // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/
281     Configuration testConf = new Configuration(this.conf);
282     FSUtils.setRootDir(testConf, this.testDir);
283 
284     // adding legal table name chars to verify regex handles it.
285     HRegionInfo hri = new HRegionInfo(TableName.valueOf("_original-evil-name"));
286     HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
287       testConf, fs, FSUtils.getTableDir(this.testDir, hri.getTable()), hri);
288 
289     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
290     // Make a store file and write data to it. <root>/<tablename>/<rgn>/<cf>/<file>
291     StoreFile.Writer writer = new StoreFile.WriterBuilder(testConf, cacheConf, this.fs)
292             .withFilePath(regionFs.createTempName())
293             .withFileContext(meta)
294             .build();
295     writeStoreFile(writer);
296     Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
297 
298     // create link to store file. <root>/clone/region/<cf>/<hfile>-<region>-<table>
299     HRegionInfo hriClone = new HRegionInfo(TableName.valueOf("clone"));
300     HRegionFileSystem cloneRegionFs = HRegionFileSystem.createRegionOnFileSystem(
301       testConf, fs, FSUtils.getTableDir(this.testDir, hri.getTable()),
302         hriClone);
303     Path dstPath = cloneRegionFs.getStoreDir(TEST_FAMILY);
304     HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName());
305     Path linkFilePath = new Path(dstPath,
306                   HFileLink.createHFileLinkName(hri, storeFilePath.getName()));
307 
308     // create splits of the link.
309     // <root>/clone/splitA/<cf>/<reftohfilelink>,
310     // <root>/clone/splitB/<cf>/<reftohfilelink>
311     HRegionInfo splitHriA = new HRegionInfo(hri.getTable(), null, SPLITKEY);
312     HRegionInfo splitHriB = new HRegionInfo(hri.getTable(), SPLITKEY, null);
313     StoreFile f = new StoreFile(fs, linkFilePath, testConf, cacheConf, BloomType.NONE);
314     f.createReader();
315     Path pathA = splitStoreFile(cloneRegionFs, splitHriA, TEST_FAMILY, f, SPLITKEY, true); // top
316     Path pathB = splitStoreFile(cloneRegionFs, splitHriB, TEST_FAMILY, f, SPLITKEY, false);// bottom
317     f.closeReader(true);
318     // OK test the thing
319     FSUtils.logFileSystemState(fs, this.testDir, LOG);
320 
321     // There is a case where a file with the hfilelink pattern is actually a daughter
322     // reference to a hfile link.  This code in StoreFile that handles this case.
323 
324     // Try to open store file from link
325     StoreFile hsfA = new StoreFile(this.fs, pathA, testConf, cacheConf,
326       BloomType.NONE);
327 
328     // Now confirm that I can read from the ref to link
329     int count = 1;
330     HFileScanner s = hsfA.createReader().getScanner(false, false);
331     s.seekTo();
332     while (s.next()) {
333       count++;
334     }
335     assertTrue(count > 0); // read some rows here
336 
337     // Try to open store file from link
338     StoreFile hsfB = new StoreFile(this.fs, pathB, testConf, cacheConf,
339       BloomType.NONE);
340 
341     // Now confirm that I can read from the ref to link
342     HFileScanner sB = hsfB.createReader().getScanner(false, false);
343     sB.seekTo();
344     
345     //count++ as seekTo() will advance the scanner
346     count++;
347     while (sB.next()) {
348       count++;
349     }
350 
351     // read the rest of the rows
352     assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count);
353   }
354 
355   private void checkHalfHFile(final HRegionFileSystem regionFs, final StoreFile f)
356       throws IOException {
357     byte [] midkey = f.createReader().midkey();
358     KeyValue midKV = KeyValue.createKeyValueFromKey(midkey);
359     byte [] midRow = midKV.getRow();
360     // Create top split.
361     HRegionInfo topHri = new HRegionInfo(regionFs.getRegionInfo().getTable(),
362         null, midRow);
363     Path topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, midRow, true);
364     // Create bottom split.
365     HRegionInfo bottomHri = new HRegionInfo(regionFs.getRegionInfo().getTable(),
366         midRow, null);
367     Path bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, midRow, false);
368     // Make readers on top and bottom.
369     StoreFile.Reader top = new StoreFile(
370       this.fs, topPath, conf, cacheConf, BloomType.NONE).createReader();
371     StoreFile.Reader bottom = new StoreFile(
372       this.fs, bottomPath, conf, cacheConf, BloomType.NONE).createReader();
373     ByteBuffer previous = null;
374     LOG.info("Midkey: " + midKV.toString());
375     ByteBuffer bbMidkeyBytes = ByteBuffer.wrap(midkey);
376     try {
377       // Now make two HalfMapFiles and assert they can read the full backing
378       // file, one from the top and the other from the bottom.
379       // Test bottom half first.
380       // Now test reading from the top.
381       boolean first = true;
382       ByteBuffer key = null;
383       HFileScanner topScanner = top.getScanner(false, false);
384       while ((!topScanner.isSeeked() && topScanner.seekTo()) ||
385              (topScanner.isSeeked() && topScanner.next())) {
386         key = topScanner.getKey();
387 
388         if (topScanner.getReader().getComparator().compareFlatKey(key.array(),
389           key.arrayOffset(), key.limit(), midkey, 0, midkey.length) < 0) {
390           fail("key=" + Bytes.toStringBinary(key) + " < midkey=" +
391               Bytes.toStringBinary(midkey));
392         }
393         if (first) {
394           first = false;
395           LOG.info("First in top: " + Bytes.toString(Bytes.toBytes(key)));
396         }
397       }
398       LOG.info("Last in top: " + Bytes.toString(Bytes.toBytes(key)));
399 
400       first = true;
401       HFileScanner bottomScanner = bottom.getScanner(false, false);
402       while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) ||
403           bottomScanner.next()) {
404         previous = bottomScanner.getKey();
405         key = bottomScanner.getKey();
406         if (first) {
407           first = false;
408           LOG.info("First in bottom: " +
409             Bytes.toString(Bytes.toBytes(previous)));
410         }
411         assertTrue(key.compareTo(bbMidkeyBytes) < 0);
412       }
413       if (previous != null) {
414         LOG.info("Last in bottom: " + Bytes.toString(Bytes.toBytes(previous)));
415       }
416       // Remove references.
417       regionFs.cleanupDaughterRegion(topHri);
418       regionFs.cleanupDaughterRegion(bottomHri);
419 
420       // Next test using a midkey that does not exist in the file.
421       // First, do a key that is < than first key. Ensure splits behave
422       // properly.
423       byte [] badmidkey = Bytes.toBytes("  .");
424       assertTrue(fs.exists(f.getPath()));
425       topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, badmidkey, true);
426       bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false);
427       
428       assertNull(bottomPath);
429       
430       top = new StoreFile(this.fs, topPath, conf, cacheConf, BloomType.NONE).createReader();
431       // Now read from the top.
432       first = true;
433       topScanner = top.getScanner(false, false);
434       while ((!topScanner.isSeeked() && topScanner.seekTo()) ||
435           topScanner.next()) {
436         key = topScanner.getKey();
437         assertTrue(topScanner.getReader().getComparator().compareFlatKey(key.array(),
438             key.arrayOffset(), key.limit(), badmidkey, 0, badmidkey.length) >= 0);
439         if (first) {
440           first = false;
441           KeyValue keyKV = KeyValue.createKeyValueFromKey(key);
442           LOG.info("First top when key < bottom: " + keyKV);
443           String tmp = Bytes.toString(keyKV.getRow());
444           for (int i = 0; i < tmp.length(); i++) {
445             assertTrue(tmp.charAt(i) == 'a');
446           }
447         }
448       }
449       KeyValue keyKV = KeyValue.createKeyValueFromKey(key);
450       LOG.info("Last top when key < bottom: " + keyKV);
451       String tmp = Bytes.toString(keyKV.getRow());
452       for (int i = 0; i < tmp.length(); i++) {
453         assertTrue(tmp.charAt(i) == 'z');
454       }
455       // Remove references.
456       regionFs.cleanupDaughterRegion(topHri);
457       regionFs.cleanupDaughterRegion(bottomHri);
458 
459       // Test when badkey is > than last key in file ('||' > 'zz').
460       badmidkey = Bytes.toBytes("|||");
461       topPath = splitStoreFile(regionFs,topHri, TEST_FAMILY, f, badmidkey, true);
462       bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false);
463       assertNull(topPath);
464       bottom = new StoreFile(this.fs, bottomPath, conf, cacheConf,
465         BloomType.NONE).createReader();
466       first = true;
467       bottomScanner = bottom.getScanner(false, false);
468       while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) ||
469           bottomScanner.next()) {
470         key = bottomScanner.getKey();
471         if (first) {
472           first = false;
473           keyKV = KeyValue.createKeyValueFromKey(key);
474           LOG.info("First bottom when key > top: " + keyKV);
475           tmp = Bytes.toString(keyKV.getRow());
476           for (int i = 0; i < tmp.length(); i++) {
477             assertTrue(tmp.charAt(i) == 'a');
478           }
479         }
480       }
481       keyKV = KeyValue.createKeyValueFromKey(key);
482       LOG.info("Last bottom when key > top: " + keyKV);
483       for (int i = 0; i < tmp.length(); i++) {
484         assertTrue(Bytes.toString(keyKV.getRow()).charAt(i) == 'z');
485       }
486     } finally {
487       if (top != null) {
488         top.close(true); // evict since we are about to delete the file
489       }
490       if (bottom != null) {
491         bottom.close(true); // evict since we are about to delete the file
492       }
493       fs.delete(f.getPath(), true);
494     }
495   }
496 
497   private static StoreFileScanner getStoreFileScanner(StoreFile.Reader reader, boolean cacheBlocks,
498       boolean pread) {
499     return reader.getStoreFileScanner(cacheBlocks, pread, false, 0, 0, false);
500   }
501 
502   private static final String localFormatter = "%010d";
503 
504   private void bloomWriteRead(StoreFile.Writer writer, FileSystem fs) throws Exception {
505     float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0);
506     Path f = writer.getPath();
507     long now = System.currentTimeMillis();
508     for (int i = 0; i < 2000; i += 2) {
509       String row = String.format(localFormatter, i);
510       KeyValue kv = new KeyValue(row.getBytes(), "family".getBytes(),
511         "col".getBytes(), now, "value".getBytes());
512       writer.append(kv);
513     }
514     writer.close();
515 
516     StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
517     reader.loadFileInfo();
518     reader.loadBloomfilter();
519     StoreFileScanner scanner = getStoreFileScanner(reader, false, false);
520 
521     // check false positives rate
522     int falsePos = 0;
523     int falseNeg = 0;
524     for (int i = 0; i < 2000; i++) {
525       String row = String.format(localFormatter, i);
526       TreeSet<byte[]> columns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
527       columns.add("family:col".getBytes());
528 
529       Scan scan = new Scan(row.getBytes(),row.getBytes());
530       scan.addColumn("family".getBytes(), "family:col".getBytes());
531       Store store = mock(Store.class);
532       HColumnDescriptor hcd = mock(HColumnDescriptor.class);
533       when(hcd.getName()).thenReturn(Bytes.toBytes("family"));
534       when(store.getFamily()).thenReturn(hcd);
535       boolean exists = scanner.shouldUseScanner(scan, store, Long.MIN_VALUE);
536       if (i % 2 == 0) {
537         if (!exists) falseNeg++;
538       } else {
539         if (exists) falsePos++;
540       }
541     }
542     reader.close(true); // evict because we are about to delete the file
543     fs.delete(f, true);
544     assertEquals("False negatives: " + falseNeg, 0, falseNeg);
545     int maxFalsePos = (int) (2 * 2000 * err);
546     assertTrue("Too many false positives: " + falsePos + " (err=" + err
547         + ", expected no more than " + maxFalsePos + ")",
548         falsePos <= maxFalsePos);
549   }
550   
551   private static final int BLOCKSIZE_SMALL = 8192;
552 
553   public void testBloomFilter() throws Exception {
554     FileSystem fs = FileSystem.getLocal(conf);
555     conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01);
556     conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
557 
558     // write the file
559     Path f = new Path(ROOT_DIR, getName());
560     HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
561                         .withChecksumType(CKTYPE)
562                         .withBytesPerCheckSum(CKBYTES).build();
563     // Make a store file and write data to it.
564     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
565             .withFilePath(f)
566             .withBloomType(BloomType.ROW)
567             .withMaxKeyCount(2000)
568             .withFileContext(meta)
569             .build();
570     bloomWriteRead(writer, fs);
571   }
572 
573   public void testDeleteFamilyBloomFilter() throws Exception {
574     FileSystem fs = FileSystem.getLocal(conf);
575     conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01);
576     conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
577     float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0);
578 
579     // write the file
580     Path f = new Path(ROOT_DIR, getName());
581 
582     HFileContext meta = new HFileContextBuilder()
583                         .withBlockSize(BLOCKSIZE_SMALL)
584                         .withChecksumType(CKTYPE)
585                         .withBytesPerCheckSum(CKBYTES).build();
586     // Make a store file and write data to it.
587     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
588             .withFilePath(f)
589             .withMaxKeyCount(2000)
590             .withFileContext(meta)
591             .build();
592 
593     // add delete family
594     long now = System.currentTimeMillis();
595     for (int i = 0; i < 2000; i += 2) {
596       String row = String.format(localFormatter, i);
597       KeyValue kv = new KeyValue(row.getBytes(), "family".getBytes(),
598           "col".getBytes(), now, KeyValue.Type.DeleteFamily, "value".getBytes());
599       writer.append(kv);
600     }
601     writer.close();
602 
603     StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
604     reader.loadFileInfo();
605     reader.loadBloomfilter();
606 
607     // check false positives rate
608     int falsePos = 0;
609     int falseNeg = 0;
610     for (int i = 0; i < 2000; i++) {
611       String row = String.format(localFormatter, i);
612       byte[] rowKey = Bytes.toBytes(row);
613       boolean exists = reader.passesDeleteFamilyBloomFilter(rowKey, 0,
614           rowKey.length);
615       if (i % 2 == 0) {
616         if (!exists)
617           falseNeg++;
618       } else {
619         if (exists)
620           falsePos++;
621       }
622     }
623     assertEquals(1000, reader.getDeleteFamilyCnt());
624     reader.close(true); // evict because we are about to delete the file
625     fs.delete(f, true);
626     assertEquals("False negatives: " + falseNeg, 0, falseNeg);
627     int maxFalsePos = (int) (2 * 2000 * err);
628     assertTrue("Too many false positives: " + falsePos + " (err=" + err
629         + ", expected no more than " + maxFalsePos, falsePos <= maxFalsePos);
630   }
631 
632   /**
633    * Test for HBASE-8012
634    */
635   public void testReseek() throws Exception {
636     // write the file
637     Path f = new Path(ROOT_DIR, getName());
638     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
639     // Make a store file and write data to it.
640     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
641             .withFilePath(f)
642             .withFileContext(meta)
643             .build();
644 
645     writeStoreFile(writer);
646     writer.close();
647 
648     StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
649 
650     // Now do reseek with empty KV to position to the beginning of the file
651 
652     KeyValue k = KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY);
653     StoreFileScanner s = getStoreFileScanner(reader, false, false);
654     s.reseek(k);
655 
656     assertNotNull("Intial reseek should position at the beginning of the file", s.peek());
657   }
658 
659   public void testBloomTypes() throws Exception {
660     float err = (float) 0.01;
661     FileSystem fs = FileSystem.getLocal(conf);
662     conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, err);
663     conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
664 
665     int rowCount = 50;
666     int colCount = 10;
667     int versions = 2;
668 
669     // run once using columns and once using rows
670     BloomType[] bt = {BloomType.ROWCOL, BloomType.ROW};
671     int[] expKeys  = {rowCount*colCount, rowCount};
672     // below line deserves commentary.  it is expected bloom false positives
673     //  column = rowCount*2*colCount inserts
674     //  row-level = only rowCount*2 inserts, but failures will be magnified by
675     //              2nd for loop for every column (2*colCount)
676     float[] expErr   = {2*rowCount*colCount*err, 2*rowCount*2*colCount*err};
677 
678     for (int x : new int[]{0,1}) {
679       // write the file
680       Path f = new Path(ROOT_DIR, getName() + x);
681       HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
682           .withChecksumType(CKTYPE)
683           .withBytesPerCheckSum(CKBYTES).build();
684       // Make a store file and write data to it.
685       StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
686               .withFilePath(f)
687               .withBloomType(bt[x])
688               .withMaxKeyCount(expKeys[x])
689               .withFileContext(meta)
690               .build();
691 
692       long now = System.currentTimeMillis();
693       for (int i = 0; i < rowCount*2; i += 2) { // rows
694         for (int j = 0; j < colCount*2; j += 2) {   // column qualifiers
695           String row = String.format(localFormatter, i);
696           String col = String.format(localFormatter, j);
697           for (int k= 0; k < versions; ++k) { // versions
698             KeyValue kv = new KeyValue(row.getBytes(),
699               "family".getBytes(), ("col" + col).getBytes(),
700                 now-k, Bytes.toBytes((long)-1));
701             writer.append(kv);
702           }
703         }
704       }
705       writer.close();
706 
707       StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
708       reader.loadFileInfo();
709       reader.loadBloomfilter();
710       StoreFileScanner scanner = getStoreFileScanner(reader, false, false);
711       assertEquals(expKeys[x], reader.generalBloomFilter.getKeyCount());
712 
713       Store store = mock(Store.class);
714       HColumnDescriptor hcd = mock(HColumnDescriptor.class);
715       when(hcd.getName()).thenReturn(Bytes.toBytes("family"));
716       when(store.getFamily()).thenReturn(hcd);
717       // check false positives rate
718       int falsePos = 0;
719       int falseNeg = 0;
720       for (int i = 0; i < rowCount*2; ++i) { // rows
721         for (int j = 0; j < colCount*2; ++j) {   // column qualifiers
722           String row = String.format(localFormatter, i);
723           String col = String.format(localFormatter, j);
724           TreeSet<byte[]> columns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
725           columns.add(("col" + col).getBytes());
726 
727           Scan scan = new Scan(row.getBytes(),row.getBytes());
728           scan.addColumn("family".getBytes(), ("col"+col).getBytes());
729           boolean exists =
730               scanner.shouldUseScanner(scan, store, Long.MIN_VALUE);
731           boolean shouldRowExist = i % 2 == 0;
732           boolean shouldColExist = j % 2 == 0;
733           shouldColExist = shouldColExist || bt[x] == BloomType.ROW;
734           if (shouldRowExist && shouldColExist) {
735             if (!exists) falseNeg++;
736           } else {
737             if (exists) falsePos++;
738           }
739         }
740       }
741       reader.close(true); // evict because we are about to delete the file
742       fs.delete(f, true);
743       System.out.println(bt[x].toString());
744       System.out.println("  False negatives: " + falseNeg);
745       System.out.println("  False positives: " + falsePos);
746       assertEquals(0, falseNeg);
747       assertTrue(falsePos < 2*expErr[x]);
748     }
749   }
750 
751   public void testSeqIdComparator() {
752     assertOrdering(StoreFile.Comparators.SEQ_ID, mockStoreFile(true, 100, 1000, -1, "/foo/123"),
753         mockStoreFile(true, 100, 1000, -1, "/foo/124"),
754         mockStoreFile(true, 99, 1000, -1, "/foo/126"),
755         mockStoreFile(true, 98, 2000, -1, "/foo/126"), mockStoreFile(false, 3453, -1, 1, "/foo/1"),
756         mockStoreFile(false, 2, -1, 3, "/foo/2"), mockStoreFile(false, 1000, -1, 5, "/foo/2"),
757         mockStoreFile(false, 76, -1, 5, "/foo/3"));
758   }
759 
760   /**
761    * Assert that the given comparator orders the given storefiles in the
762    * same way that they're passed.
763    */
764   private void assertOrdering(Comparator<StoreFile> comparator, StoreFile ... sfs) {
765     ArrayList<StoreFile> sorted = Lists.newArrayList(sfs);
766     Collections.shuffle(sorted);
767     Collections.sort(sorted, comparator);
768     LOG.debug("sfs: " + Joiner.on(",").join(sfs));
769     LOG.debug("sorted: " + Joiner.on(",").join(sorted));
770     assertTrue(Iterables.elementsEqual(Arrays.asList(sfs), sorted));
771   }
772 
773   /**
774    * Create a mock StoreFile with the given attributes.
775    */
776   private StoreFile mockStoreFile(boolean bulkLoad,
777                                   long size,
778                                   long bulkTimestamp,
779                                   long seqId,
780                                   String path) {
781     StoreFile mock = Mockito.mock(StoreFile.class);
782     StoreFile.Reader reader = Mockito.mock(StoreFile.Reader.class);
783 
784     Mockito.doReturn(size).when(reader).length();
785 
786     Mockito.doReturn(reader).when(mock).getReader();
787     Mockito.doReturn(bulkLoad).when(mock).isBulkLoadResult();
788     Mockito.doReturn(bulkTimestamp).when(mock).getBulkLoadTimestamp();
789     Mockito.doReturn(seqId).when(mock).getMaxSequenceId();
790     Mockito.doReturn(new Path(path)).when(mock).getPath();
791     String name = "mock storefile, bulkLoad=" + bulkLoad +
792       " bulkTimestamp=" + bulkTimestamp +
793       " seqId=" + seqId +
794       " path=" + path;
795     Mockito.doReturn(name).when(mock).toString();
796     return mock;
797   }
798 
799   /**
800    * Generate a list of KeyValues for testing based on given parameters
801    * @param timestamps
802    * @param numRows
803    * @param qualifier
804    * @param family
805    * @return
806    */
807   List<KeyValue> getKeyValueSet(long[] timestamps, int numRows,
808       byte[] qualifier, byte[] family) {
809     List<KeyValue> kvList = new ArrayList<KeyValue>();
810     for (int i=1;i<=numRows;i++) {
811       byte[] b = Bytes.toBytes(i) ;
812       LOG.info(Bytes.toString(b));
813       LOG.info(Bytes.toString(b));
814       for (long timestamp: timestamps)
815       {
816         kvList.add(new KeyValue(b, family, qualifier, timestamp, b));
817       }
818     }
819     return kvList;
820   }
821 
822   /**
823    * Test to ensure correctness when using StoreFile with multiple timestamps
824    * @throws IOException
825    */
826   public void testMultipleTimestamps() throws IOException {
827     byte[] family = Bytes.toBytes("familyname");
828     byte[] qualifier = Bytes.toBytes("qualifier");
829     int numRows = 10;
830     long[] timestamps = new long[] {20,10,5,1};
831     Scan scan = new Scan();
832 
833     // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname.
834     Path storedir = new Path(new Path(this.testDir, "7e0102"), "familyname");
835     Path dir = new Path(storedir, "1234567890");
836     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
837     // Make a store file and write data to it.
838     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
839             .withOutputDir(dir)
840             .withFileContext(meta)
841             .build();
842 
843     List<KeyValue> kvList = getKeyValueSet(timestamps,numRows,
844         qualifier, family);
845 
846     for (KeyValue kv : kvList) {
847       writer.append(kv);
848     }
849     writer.appendMetadata(0, false);
850     writer.close();
851 
852     StoreFile hsf = new StoreFile(this.fs, writer.getPath(), conf, cacheConf,
853       BloomType.NONE);
854     Store store = mock(Store.class);
855     HColumnDescriptor hcd = mock(HColumnDescriptor.class);
856     when(hcd.getName()).thenReturn(family);
857     when(store.getFamily()).thenReturn(hcd);
858     StoreFile.Reader reader = hsf.createReader();
859     StoreFileScanner scanner = getStoreFileScanner(reader, false, false);
860     TreeSet<byte[]> columns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
861     columns.add(qualifier);
862 
863     scan.setTimeRange(20, 100);
864     assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
865 
866     scan.setTimeRange(1, 2);
867     // lets make sure it still works with column family time ranges
868     scan.setColumnFamilyTimeRange(family, 7, 50);
869     assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
870 
871     scan.setTimeRange(8, 10);
872     assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
873 
874     scan.setTimeRange(7, 50);
875     assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
876 
877     // This test relies on the timestamp range optimization
878     scan = new Scan();
879     scan.setTimeRange(27, 50);
880     assertTrue(!scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
881 
882     // should still use the scanner because we override the family time range
883     scan = new Scan();
884     scan.setTimeRange(27, 50);
885     scan.setColumnFamilyTimeRange(family, 7, 50);
886     assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
887 
888   }
889 
890   public void testCacheOnWriteEvictOnClose() throws Exception {
891     Configuration conf = this.conf;
892 
893     // Find a home for our files (regiondir ("7e0102") and familyname).
894     Path baseDir = new Path(new Path(this.testDir, "7e0102"),"twoCOWEOC");
895 
896     // Grab the block cache and get the initial hit/miss counts
897     BlockCache bc = new CacheConfig(conf).getBlockCache();
898     assertNotNull(bc);
899     CacheStats cs = bc.getStats();
900     long startHit = cs.getHitCount();
901     long startMiss = cs.getMissCount();
902     long startEvicted = cs.getEvictedCount();
903 
904     // Let's write a StoreFile with three blocks, with cache on write off
905     conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
906     CacheConfig cacheConf = new CacheConfig(conf);
907     Path pathCowOff = new Path(baseDir, "123456789");
908     StoreFile.Writer writer = writeStoreFile(conf, cacheConf, pathCowOff, 3);
909     StoreFile hsf = new StoreFile(this.fs, writer.getPath(), conf, cacheConf,
910       BloomType.NONE);
911     LOG.debug(hsf.getPath().toString());
912 
913     // Read this file, we should see 3 misses
914     StoreFile.Reader reader = hsf.createReader();
915     reader.loadFileInfo();
916     StoreFileScanner scanner = getStoreFileScanner(reader, true, true);
917     scanner.seek(KeyValue.LOWESTKEY);
918     while (scanner.next() != null);
919     assertEquals(startHit, cs.getHitCount());
920     assertEquals(startMiss + 3, cs.getMissCount());
921     assertEquals(startEvicted, cs.getEvictedCount());
922     startMiss += 3;
923     scanner.close();
924     reader.close(cacheConf.shouldEvictOnClose());
925 
926     // Now write a StoreFile with three blocks, with cache on write on
927     conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true);
928     cacheConf = new CacheConfig(conf);
929     Path pathCowOn = new Path(baseDir, "123456788");
930     writer = writeStoreFile(conf, cacheConf, pathCowOn, 3);
931     hsf = new StoreFile(this.fs, writer.getPath(), conf, cacheConf,
932       BloomType.NONE);
933 
934     // Read this file, we should see 3 hits
935     reader = hsf.createReader();
936     scanner = getStoreFileScanner(reader, true, true);
937     scanner.seek(KeyValue.LOWESTKEY);
938     while (scanner.next() != null);
939     assertEquals(startHit + 3, cs.getHitCount());
940     assertEquals(startMiss, cs.getMissCount());
941     assertEquals(startEvicted, cs.getEvictedCount());
942     startHit += 3;
943     scanner.close();
944     reader.close(cacheConf.shouldEvictOnClose());
945 
946     // Let's read back the two files to ensure the blocks exactly match
947     hsf = new StoreFile(this.fs, pathCowOff, conf, cacheConf,
948       BloomType.NONE);
949     StoreFile.Reader readerOne = hsf.createReader();
950     readerOne.loadFileInfo();
951     StoreFileScanner scannerOne = getStoreFileScanner(readerOne, true, true);
952     scannerOne.seek(KeyValue.LOWESTKEY);
953     hsf = new StoreFile(this.fs, pathCowOn, conf, cacheConf,
954       BloomType.NONE);
955     StoreFile.Reader readerTwo = hsf.createReader();
956     readerTwo.loadFileInfo();
957     StoreFileScanner scannerTwo = getStoreFileScanner(readerTwo, true, true);
958     scannerTwo.seek(KeyValue.LOWESTKEY);
959     Cell kv1 = null;
960     Cell kv2 = null;
961     while ((kv1 = scannerOne.next()) != null) {
962       kv2 = scannerTwo.next();
963       assertTrue(kv1.equals(kv2));
964       KeyValue keyv1 = KeyValueUtil.ensureKeyValue(kv1);
965       KeyValue keyv2 = KeyValueUtil.ensureKeyValue(kv2);
966       assertTrue(Bytes.compareTo(
967           keyv1.getBuffer(), keyv1.getKeyOffset(), keyv1.getKeyLength(), 
968           keyv2.getBuffer(), keyv2.getKeyOffset(), keyv2.getKeyLength()) == 0);
969       assertTrue(Bytes.compareTo(
970           kv1.getValueArray(), kv1.getValueOffset(), kv1.getValueLength(),
971           kv2.getValueArray(), kv2.getValueOffset(), kv2.getValueLength()) == 0);
972     }
973     assertNull(scannerTwo.next());
974     assertEquals(startHit + 6, cs.getHitCount());
975     assertEquals(startMiss, cs.getMissCount());
976     assertEquals(startEvicted, cs.getEvictedCount());
977     startHit += 6;
978     scannerOne.close();
979     readerOne.close(cacheConf.shouldEvictOnClose());
980     scannerTwo.close();
981     readerTwo.close(cacheConf.shouldEvictOnClose());
982 
983     // Let's close the first file with evict on close turned on
984     conf.setBoolean("hbase.rs.evictblocksonclose", true);
985     cacheConf = new CacheConfig(conf);
986     hsf = new StoreFile(this.fs, pathCowOff, conf, cacheConf,
987       BloomType.NONE);
988     reader = hsf.createReader();
989     reader.close(cacheConf.shouldEvictOnClose());
990 
991     // We should have 3 new evictions
992     assertEquals(startHit, cs.getHitCount());
993     assertEquals(startMiss, cs.getMissCount());
994     assertEquals(startEvicted + 3, cs.getEvictedCount());
995     startEvicted += 3;
996 
997     // Let's close the second file with evict on close turned off
998     conf.setBoolean("hbase.rs.evictblocksonclose", false);
999     cacheConf = new CacheConfig(conf);
1000     hsf = new StoreFile(this.fs, pathCowOn, conf, cacheConf,
1001       BloomType.NONE);
1002     reader = hsf.createReader();
1003     reader.close(cacheConf.shouldEvictOnClose());
1004 
1005     // We expect no changes
1006     assertEquals(startHit, cs.getHitCount());
1007     assertEquals(startMiss, cs.getMissCount());
1008     assertEquals(startEvicted, cs.getEvictedCount());
1009   }
1010 
1011   private Path splitStoreFile(final HRegionFileSystem regionFs, final HRegionInfo hri,
1012       final String family, final StoreFile sf, final byte[] splitKey, boolean isTopRef)
1013       throws IOException {
1014     FileSystem fs = regionFs.getFileSystem();
1015     Path path = regionFs.splitStoreFile(hri, family, sf, splitKey, isTopRef, null);
1016     if (null == path) {
1017       return null;
1018     }
1019     Path regionDir = regionFs.commitDaughterRegion(hri);
1020     return new Path(new Path(regionDir, family), path.getName());
1021   }
1022 
1023   private StoreFile.Writer writeStoreFile(Configuration conf,
1024       CacheConfig cacheConf, Path path, int numBlocks)
1025   throws IOException {
1026     // Let's put ~5 small KVs in each block, so let's make 5*numBlocks KVs
1027     int numKVs = 5 * numBlocks;
1028     List<KeyValue> kvs = new ArrayList<KeyValue>(numKVs);
1029     byte [] b = Bytes.toBytes("x");
1030     int totalSize = 0;
1031     for (int i=numKVs;i>0;i--) {
1032       KeyValue kv = new KeyValue(b, b, b, i, b);
1033       kvs.add(kv);
1034       // kv has memstoreTS 0, which takes 1 byte to store.
1035       totalSize += kv.getLength() + 1;
1036     }
1037     int blockSize = totalSize / numBlocks;
1038     HFileContext meta = new HFileContextBuilder().withBlockSize(blockSize)
1039                         .withChecksumType(CKTYPE)
1040                         .withBytesPerCheckSum(CKBYTES)
1041                         .build();
1042     // Make a store file and write data to it.
1043     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
1044             .withFilePath(path)
1045             .withMaxKeyCount(2000)
1046             .withFileContext(meta)
1047             .build();
1048     // We'll write N-1 KVs to ensure we don't write an extra block
1049     kvs.remove(kvs.size()-1);
1050     for (KeyValue kv : kvs) {
1051       writer.append(kv);
1052     }
1053     writer.appendMetadata(0, false);
1054     writer.close();
1055     return writer;
1056   }
1057 
1058   /**
1059    * Check if data block encoding information is saved correctly in HFile's
1060    * file info.
1061    */
1062   public void testDataBlockEncodingMetaData() throws IOException {
1063     // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname.
1064     Path dir = new Path(new Path(this.testDir, "7e0102"), "familyname");
1065     Path path = new Path(dir, "1234567890");
1066 
1067     DataBlockEncoding dataBlockEncoderAlgo =
1068         DataBlockEncoding.FAST_DIFF;
1069     cacheConf = new CacheConfig(conf);
1070     HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
1071         .withChecksumType(CKTYPE)
1072         .withBytesPerCheckSum(CKBYTES)
1073         .withDataBlockEncoding(dataBlockEncoderAlgo)
1074         .build();
1075     // Make a store file and write data to it.
1076     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
1077             .withFilePath(path)
1078             .withMaxKeyCount(2000)
1079             .withFileContext(meta)
1080             .build();
1081     writer.close();
1082 
1083     StoreFile storeFile = new StoreFile(fs, writer.getPath(), conf,
1084       cacheConf, BloomType.NONE);
1085     StoreFile.Reader reader = storeFile.createReader();
1086 
1087     Map<byte[], byte[]> fileInfo = reader.loadFileInfo();
1088     byte[] value = fileInfo.get(HFileDataBlockEncoder.DATA_BLOCK_ENCODING);
1089     assertEquals(dataBlockEncoderAlgo.getNameInBytes(), value);
1090   }
1091 }
1092