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.regionserver; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import java.io.IOException; 024import java.util.List; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FileSystem; 027import org.apache.hadoop.fs.Path; 028import org.apache.hadoop.hbase.Cell; 029import org.apache.hadoop.hbase.CellComparatorImpl; 030import org.apache.hadoop.hbase.CellScanner; 031import org.apache.hadoop.hbase.CellUtil; 032import org.apache.hadoop.hbase.HBaseClassTestRule; 033import org.apache.hadoop.hbase.HBaseTestingUtility; 034import org.apache.hadoop.hbase.HColumnDescriptor; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.HRegionInfo; 037import org.apache.hadoop.hbase.HTableDescriptor; 038import org.apache.hadoop.hbase.MemoryCompactionPolicy; 039import org.apache.hadoop.hbase.PrivateCellUtil; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.client.Get; 042import org.apache.hadoop.hbase.client.Result; 043import org.apache.hadoop.hbase.io.hfile.CacheConfig; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.apache.hadoop.hbase.util.FSUtils; 047import org.apache.hadoop.hbase.wal.WAL; 048import org.apache.hadoop.hbase.wal.WALEdit; 049import org.apache.hadoop.hbase.wal.WALFactory; 050import org.apache.hadoop.hbase.wal.WALKey; 051import org.apache.hadoop.hbase.wal.WALSplitter; 052import org.junit.ClassRule; 053import org.junit.Rule; 054import org.junit.Test; 055import org.junit.experimental.categories.Category; 056import org.junit.rules.TestName; 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060/** 061 * Tests around replay of recovered.edits content. 062 */ 063@Category({MediumTests.class}) 064public class TestRecoveredEdits { 065 066 @ClassRule 067 public static final HBaseClassTestRule CLASS_RULE = 068 HBaseClassTestRule.forClass(TestRecoveredEdits.class); 069 070 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 071 private static final Logger LOG = LoggerFactory.getLogger(TestRecoveredEdits.class); 072 @Rule public TestName testName = new TestName(); 073 074 /** 075 * HBASE-12782 ITBLL fails for me if generator does anything but 5M per maptask. 076 * Create a region. Close it. Then copy into place a file to replay, one that is bigger than 077 * configured flush size so we bring on lots of flushes. Then reopen and confirm all edits 078 * made it in. 079 * @throws IOException 080 */ 081 @Test 082 public void testReplayWorksThoughLotsOfFlushing() throws 083 IOException { 084 CacheConfig.instantiateBlockCache(TEST_UTIL.getConfiguration()); 085 for(MemoryCompactionPolicy policy : MemoryCompactionPolicy.values()) { 086 testReplayWorksWithMemoryCompactionPolicy(policy); 087 } 088 } 089 090 private void testReplayWorksWithMemoryCompactionPolicy(MemoryCompactionPolicy policy) throws 091 IOException { 092 Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); 093 // Set it so we flush every 1M or so. Thats a lot. 094 conf.setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 1024*1024); 095 conf.set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY, String.valueOf(policy)); 096 // The file of recovered edits has a column family of 'meta'. Also has an encoded regionname 097 // of 4823016d8fca70b25503ee07f4c6d79f which needs to match on replay. 098 final String encodedRegionName = "4823016d8fca70b25503ee07f4c6d79f"; 099 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(testName.getMethodName())); 100 final String columnFamily = "meta"; 101 byte [][] columnFamilyAsByteArray = new byte [][] {Bytes.toBytes(columnFamily)}; 102 htd.addFamily(new HColumnDescriptor(columnFamily)); 103 HRegionInfo hri = new HRegionInfo(htd.getTableName()) { 104 @Override 105 public synchronized String getEncodedName() { 106 return encodedRegionName; 107 } 108 109 // Cache the name because lots of lookups. 110 private byte [] encodedRegionNameAsBytes = null; 111 @Override 112 public synchronized byte[] getEncodedNameAsBytes() { 113 if (encodedRegionNameAsBytes == null) { 114 this.encodedRegionNameAsBytes = Bytes.toBytes(getEncodedName()); 115 } 116 return this.encodedRegionNameAsBytes; 117 } 118 }; 119 Path hbaseRootDir = TEST_UTIL.getDataTestDir(); 120 ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null); 121 FileSystem fs = FileSystem.get(TEST_UTIL.getConfiguration()); 122 Path tableDir = FSUtils.getTableDir(hbaseRootDir, htd.getTableName()); 123 HRegionFileSystem hrfs = 124 new HRegionFileSystem(TEST_UTIL.getConfiguration(), fs, tableDir, hri); 125 if (fs.exists(hrfs.getRegionDir())) { 126 LOG.info("Region directory already exists. Deleting."); 127 fs.delete(hrfs.getRegionDir(), true); 128 } 129 HRegion region = HRegion.createHRegion(hri, hbaseRootDir, conf, htd, null); 130 assertEquals(encodedRegionName, region.getRegionInfo().getEncodedName()); 131 List<String> storeFiles = region.getStoreFileList(columnFamilyAsByteArray); 132 // There should be no store files. 133 assertTrue(storeFiles.isEmpty()); 134 region.close(); 135 Path regionDir = region.getRegionDir(hbaseRootDir, hri); 136 Path recoveredEditsDir = WALSplitter.getRegionDirRecoveredEditsDir(regionDir); 137 // This is a little fragile getting this path to a file of 10M of edits. 138 Path recoveredEditsFile = new Path( 139 System.getProperty("test.build.classes", "target/test-classes"), 140 "0000000000000016310"); 141 // Copy this file under the region's recovered.edits dir so it is replayed on reopen. 142 Path destination = new Path(recoveredEditsDir, recoveredEditsFile.getName()); 143 fs.copyToLocalFile(recoveredEditsFile, destination); 144 assertTrue(fs.exists(destination)); 145 // Now the file 0000000000000016310 is under recovered.edits, reopen the region to replay. 146 region = HRegion.openHRegion(region, null); 147 assertEquals(encodedRegionName, region.getRegionInfo().getEncodedName()); 148 storeFiles = region.getStoreFileList(columnFamilyAsByteArray); 149 // Our 0000000000000016310 is 10MB. Most of the edits are for one region. Lets assume that if 150 // we flush at 1MB, that there are at least 3 flushed files that are there because of the 151 // replay of edits. 152 if(policy == MemoryCompactionPolicy.EAGER || policy == MemoryCompactionPolicy.ADAPTIVE) { 153 assertTrue("Files count=" + storeFiles.size(), storeFiles.size() >= 1); 154 } else { 155 assertTrue("Files count=" + storeFiles.size(), storeFiles.size() > 10); 156 } 157 // Now verify all edits made it into the region. 158 int count = verifyAllEditsMadeItIn(fs, conf, recoveredEditsFile, region); 159 LOG.info("Checked " + count + " edits made it in"); 160 } 161 162 /** 163 * @param fs 164 * @param conf 165 * @param edits 166 * @param region 167 * @return Return how many edits seen. 168 * @throws IOException 169 */ 170 private int verifyAllEditsMadeItIn(final FileSystem fs, final Configuration conf, 171 final Path edits, final HRegion region) 172 throws IOException { 173 int count = 0; 174 // Based on HRegion#replayRecoveredEdits 175 WAL.Reader reader = null; 176 try { 177 reader = WALFactory.createReader(fs, edits, conf); 178 WAL.Entry entry; 179 while ((entry = reader.next()) != null) { 180 WALKey key = entry.getKey(); 181 WALEdit val = entry.getEdit(); 182 count++; 183 // Check this edit is for this region. 184 if (!Bytes.equals(key.getEncodedRegionName(), 185 region.getRegionInfo().getEncodedNameAsBytes())) { 186 continue; 187 } 188 Cell previous = null; 189 for (Cell cell: val.getCells()) { 190 if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) continue; 191 if (previous != null && CellComparatorImpl.COMPARATOR.compareRows(previous, cell) == 0) 192 continue; 193 previous = cell; 194 Get g = new Get(CellUtil.cloneRow(cell)); 195 Result r = region.get(g); 196 boolean found = false; 197 for (CellScanner scanner = r.cellScanner(); scanner.advance();) { 198 Cell current = scanner.current(); 199 if (PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, cell, 200 current) == 0) { 201 found = true; 202 break; 203 } 204 } 205 assertTrue("Failed to find " + cell, found); 206 } 207 } 208 } finally { 209 if (reader != null) reader.close(); 210 } 211 return count; 212 } 213}