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.wal; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import java.io.IOException; 024import java.util.Arrays; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FileSystem; 027import org.apache.hadoop.fs.Path; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HBaseTestingUtility; 030import org.apache.hadoop.hbase.ServerName; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 033import org.apache.hadoop.hbase.client.Durability; 034import org.apache.hadoop.hbase.client.Increment; 035import org.apache.hadoop.hbase.client.Put; 036import org.apache.hadoop.hbase.client.RegionInfo; 037import org.apache.hadoop.hbase.client.RegionInfoBuilder; 038import org.apache.hadoop.hbase.client.Result; 039import org.apache.hadoop.hbase.client.TableDescriptor; 040import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 041import org.apache.hadoop.hbase.regionserver.ChunkCreator; 042import org.apache.hadoop.hbase.regionserver.HRegion; 043import org.apache.hadoop.hbase.regionserver.MemStoreLABImpl; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.testclassification.RegionServerTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.hadoop.hbase.util.FSUtils; 048import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 049import org.apache.hadoop.hbase.wal.WAL; 050import org.apache.hadoop.hbase.wal.WALFactory; 051import org.apache.hadoop.hdfs.MiniDFSCluster; 052import org.junit.After; 053import org.junit.AfterClass; 054import org.junit.Before; 055import org.junit.BeforeClass; 056import org.junit.ClassRule; 057import org.junit.Rule; 058import org.junit.Test; 059import org.junit.experimental.categories.Category; 060import org.junit.rules.TestName; 061import org.junit.runner.RunWith; 062import org.junit.runners.Parameterized; 063import org.junit.runners.Parameterized.Parameter; 064import org.junit.runners.Parameterized.Parameters; 065 066/** 067 * Tests for WAL write durability 068 */ 069@RunWith(Parameterized.class) 070@Category({ RegionServerTests.class, MediumTests.class }) 071public class TestDurability { 072 073 @ClassRule 074 public static final HBaseClassTestRule CLASS_RULE = 075 HBaseClassTestRule.forClass(TestDurability.class); 076 077 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 078 private static FileSystem FS; 079 private static MiniDFSCluster CLUSTER; 080 private static Configuration CONF; 081 private static Path DIR; 082 083 private static byte[] FAMILY = Bytes.toBytes("family"); 084 private static byte[] ROW = Bytes.toBytes("row"); 085 private static byte[] COL = Bytes.toBytes("col"); 086 087 @Parameter 088 public String walProvider; 089 090 @Rule 091 public TestName name = new TestName(); 092 093 @Parameters(name = "{index}: provider={0}") 094 public static Iterable<Object[]> data() { 095 return Arrays.asList(new Object[] { "defaultProvider" }, new Object[] { "asyncfs" }); 096 } 097 098 @BeforeClass 099 public static void setUpBeforeClass() throws Exception { 100 CONF = TEST_UTIL.getConfiguration(); 101 TEST_UTIL.startMiniDFSCluster(1); 102 103 CLUSTER = TEST_UTIL.getDFSCluster(); 104 FS = CLUSTER.getFileSystem(); 105 DIR = TEST_UTIL.getDataTestDirOnTestFS("TestDurability"); 106 FSUtils.setRootDir(CONF, DIR); 107 } 108 109 @AfterClass 110 public static void tearDownAfterClass() throws Exception { 111 TEST_UTIL.shutdownMiniCluster(); 112 } 113 114 @Before 115 public void setUp() { 116 CONF.set(WALFactory.WAL_PROVIDER, walProvider); 117 } 118 119 @After 120 public void tearDown() throws IOException { 121 FS.delete(DIR, true); 122 } 123 124 @Test 125 public void testDurability() throws Exception { 126 WALFactory wals = new WALFactory(CONF, 127 ServerName.valueOf("TestDurability", 16010, System.currentTimeMillis()).toString()); 128 HRegion region = createHRegion(wals, Durability.USE_DEFAULT); 129 WAL wal = region.getWAL(); 130 HRegion deferredRegion = createHRegion(region.getTableDescriptor(), region.getRegionInfo(), 131 "deferredRegion", wal, Durability.ASYNC_WAL); 132 133 region.put(newPut(null)); 134 verifyWALCount(wals, wal, 1); 135 136 // a put through the deferred table does not write to the wal immediately, 137 // but maybe has been successfully sync-ed by the underlying AsyncWriter + 138 // AsyncFlusher thread 139 deferredRegion.put(newPut(null)); 140 // but will after we sync the wal 141 wal.sync(); 142 verifyWALCount(wals, wal, 2); 143 144 // a put through a deferred table will be sync with the put sync'ed put 145 deferredRegion.put(newPut(null)); 146 wal.sync(); 147 verifyWALCount(wals, wal, 3); 148 region.put(newPut(null)); 149 verifyWALCount(wals, wal, 4); 150 151 // a put through a deferred table will be sync with the put sync'ed put 152 deferredRegion.put(newPut(Durability.USE_DEFAULT)); 153 wal.sync(); 154 verifyWALCount(wals, wal, 5); 155 region.put(newPut(Durability.USE_DEFAULT)); 156 verifyWALCount(wals, wal, 6); 157 158 // SKIP_WAL never writes to the wal 159 region.put(newPut(Durability.SKIP_WAL)); 160 deferredRegion.put(newPut(Durability.SKIP_WAL)); 161 verifyWALCount(wals, wal, 6); 162 wal.sync(); 163 verifyWALCount(wals, wal, 6); 164 165 // Async overrides sync table default 166 region.put(newPut(Durability.ASYNC_WAL)); 167 deferredRegion.put(newPut(Durability.ASYNC_WAL)); 168 wal.sync(); 169 verifyWALCount(wals, wal, 8); 170 171 // sync overrides async table default 172 region.put(newPut(Durability.SYNC_WAL)); 173 deferredRegion.put(newPut(Durability.SYNC_WAL)); 174 verifyWALCount(wals, wal, 10); 175 176 // fsync behaves like sync 177 region.put(newPut(Durability.FSYNC_WAL)); 178 deferredRegion.put(newPut(Durability.FSYNC_WAL)); 179 verifyWALCount(wals, wal, 12); 180 } 181 182 @Test 183 public void testIncrement() throws Exception { 184 byte[] row1 = Bytes.toBytes("row1"); 185 byte[] col1 = Bytes.toBytes("col1"); 186 byte[] col2 = Bytes.toBytes("col2"); 187 byte[] col3 = Bytes.toBytes("col3"); 188 189 // Setting up region 190 WALFactory wals = new WALFactory(CONF, 191 ServerName.valueOf("TestIncrement", 16010, System.currentTimeMillis()).toString()); 192 HRegion region = createHRegion(wals, Durability.USE_DEFAULT); 193 WAL wal = region.getWAL(); 194 195 // col1: amount = 0, 1 write back to WAL 196 Increment inc1 = new Increment(row1); 197 inc1.addColumn(FAMILY, col1, 0); 198 Result res = region.increment(inc1); 199 assertEquals(1, res.size()); 200 assertEquals(0, Bytes.toLong(res.getValue(FAMILY, col1))); 201 verifyWALCount(wals, wal, 1); 202 203 // col1: amount = 1, 1 write back to WAL 204 inc1 = new Increment(row1); 205 inc1.addColumn(FAMILY, col1, 1); 206 res = region.increment(inc1); 207 assertEquals(1, res.size()); 208 assertEquals(1, Bytes.toLong(res.getValue(FAMILY, col1))); 209 verifyWALCount(wals, wal, 2); 210 211 // col1: amount = 0, 0 write back to WAL 212 inc1 = new Increment(row1); 213 inc1.addColumn(FAMILY, col1, 0); 214 res = region.increment(inc1); 215 assertEquals(1, res.size()); 216 assertEquals(1, Bytes.toLong(res.getValue(FAMILY, col1))); 217 verifyWALCount(wals, wal, 2); 218 219 // col1: amount = 0, col2: amount = 0, col3: amount = 0 220 // 1 write back to WAL 221 inc1 = new Increment(row1); 222 inc1.addColumn(FAMILY, col1, 0); 223 inc1.addColumn(FAMILY, col2, 0); 224 inc1.addColumn(FAMILY, col3, 0); 225 res = region.increment(inc1); 226 assertEquals(3, res.size()); 227 assertEquals(1, Bytes.toLong(res.getValue(FAMILY, col1))); 228 assertEquals(0, Bytes.toLong(res.getValue(FAMILY, col2))); 229 assertEquals(0, Bytes.toLong(res.getValue(FAMILY, col3))); 230 verifyWALCount(wals, wal, 3); 231 232 // col1: amount = 5, col2: amount = 4, col3: amount = 3 233 // 1 write back to WAL 234 inc1 = new Increment(row1); 235 inc1.addColumn(FAMILY, col1, 5); 236 inc1.addColumn(FAMILY, col2, 4); 237 inc1.addColumn(FAMILY, col3, 3); 238 res = region.increment(inc1); 239 assertEquals(3, res.size()); 240 assertEquals(6, Bytes.toLong(res.getValue(FAMILY, col1))); 241 assertEquals(4, Bytes.toLong(res.getValue(FAMILY, col2))); 242 assertEquals(3, Bytes.toLong(res.getValue(FAMILY, col3))); 243 verifyWALCount(wals, wal, 4); 244 } 245 246 /** 247 * Test when returnResults set to false in increment it should not return the result instead it 248 * resturn null. 249 */ 250 @Test 251 public void testIncrementWithReturnResultsSetToFalse() throws Exception { 252 byte[] row1 = Bytes.toBytes("row1"); 253 byte[] col1 = Bytes.toBytes("col1"); 254 255 // Setting up region 256 WALFactory wals = new WALFactory(CONF, 257 ServerName 258 .valueOf("testIncrementWithReturnResultsSetToFalse", 16010, System.currentTimeMillis()) 259 .toString()); 260 HRegion region = createHRegion(wals, Durability.USE_DEFAULT); 261 262 Increment inc1 = new Increment(row1); 263 inc1.setReturnResults(false); 264 inc1.addColumn(FAMILY, col1, 1); 265 Result res = region.increment(inc1); 266 assertTrue(res.isEmpty()); 267 } 268 269 private Put newPut(Durability durability) { 270 Put p = new Put(ROW); 271 p.addColumn(FAMILY, COL, COL); 272 if (durability != null) { 273 p.setDurability(durability); 274 } 275 return p; 276 } 277 278 private void verifyWALCount(WALFactory wals, WAL log, int expected) throws Exception { 279 Path walPath = AbstractFSWALProvider.getCurrentFileName(log); 280 WAL.Reader reader = wals.createReader(FS, walPath); 281 int count = 0; 282 WAL.Entry entry = new WAL.Entry(); 283 while (reader.next(entry) != null) { 284 count++; 285 } 286 reader.close(); 287 assertEquals(expected, count); 288 } 289 290 // lifted from TestAtomicOperation 291 private HRegion createHRegion(WALFactory wals, Durability durability) throws IOException { 292 TableName tableName = TableName.valueOf(name.getMethodName().replaceAll("[^A-Za-z0-9-_]", "_")); 293 TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName) 294 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).build(); 295 RegionInfo info = RegionInfoBuilder.newBuilder(tableName).build(); 296 Path path = new Path(DIR, tableName.getNameAsString()); 297 if (FS.exists(path)) { 298 if (!FS.delete(path, true)) { 299 throw new IOException("Failed delete of " + path); 300 } 301 } 302 ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null); 303 return HRegion.createHRegion(info, path, CONF, htd, wals.getWAL(info)); 304 } 305 306 private HRegion createHRegion(TableDescriptor td, RegionInfo info, String dir, WAL wal, 307 Durability durability) throws IOException { 308 Path path = new Path(DIR, dir); 309 if (FS.exists(path)) { 310 if (!FS.delete(path, true)) { 311 throw new IOException("Failed delete of " + path); 312 } 313 } 314 ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null); 315 return HRegion.createHRegion(info, path, CONF, td, wal); 316 } 317}