View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations
15   * under the License.
16   */
17  package org.apache.hadoop.hbase.io.encoding;
18  
19  import static org.junit.Assert.assertTrue;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.Random;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.Cell;
32  import org.apache.hadoop.hbase.CellUtil;
33  import org.apache.hadoop.hbase.HBaseTestingUtility;
34  import org.apache.hadoop.hbase.HColumnDescriptor;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HTableDescriptor;
37  import org.apache.hadoop.hbase.testclassification.LargeTests;
38  import org.apache.hadoop.hbase.TableName;
39  import org.apache.hadoop.hbase.client.Admin;
40  import org.apache.hadoop.hbase.client.Connection;
41  import org.apache.hadoop.hbase.client.ConnectionFactory;
42  import org.apache.hadoop.hbase.client.Durability;
43  import org.apache.hadoop.hbase.client.Get;
44  import org.apache.hadoop.hbase.client.HBaseAdmin;
45  import org.apache.hadoop.hbase.client.HTable;
46  import org.apache.hadoop.hbase.client.Put;
47  import org.apache.hadoop.hbase.client.Result;
48  import org.apache.hadoop.hbase.client.Table;
49  import org.apache.hadoop.hbase.regionserver.HRegionServer;
50  import org.apache.hadoop.hbase.util.Bytes;
51  import org.apache.hadoop.hbase.util.Threads;
52  import org.apache.hadoop.hbase.zookeeper.ZKAssign;
53  import org.junit.AfterClass;
54  import org.junit.BeforeClass;
55  import org.junit.Test;
56  import org.junit.experimental.categories.Category;
57  
58  /**
59   * Tests changing data block encoding settings of a column family.
60   */
61  @Category(LargeTests.class)
62  public class TestChangingEncoding {
63    private static final Log LOG = LogFactory.getLog(TestChangingEncoding.class);
64    static final String CF = "EncodingTestCF";
65    static final byte[] CF_BYTES = Bytes.toBytes(CF);
66  
67    private static final int NUM_ROWS_PER_BATCH = 100;
68    private static final int NUM_COLS_PER_ROW = 20;
69  
70    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
71    private static final Configuration conf = TEST_UTIL.getConfiguration();
72  
73    private static final int TIMEOUT_MS = 600000;
74  
75    private HColumnDescriptor hcd;
76  
77    private TableName tableName;
78    private static final List<DataBlockEncoding> ENCODINGS_TO_ITERATE =
79        createEncodingsToIterate();
80  
81    private static final List<DataBlockEncoding> createEncodingsToIterate() {
82      List<DataBlockEncoding> encodings = new ArrayList<DataBlockEncoding>(
83          Arrays.asList(DataBlockEncoding.values()));
84      encodings.add(DataBlockEncoding.NONE);
85      return Collections.unmodifiableList(encodings);
86    }
87  
88    /** A zero-based index of the current batch of test data being written */
89    private int numBatchesWritten;
90  
91    private void prepareTest(String testId) throws IOException {
92      tableName = TableName.valueOf("test_table_" + testId);
93      HTableDescriptor htd = new HTableDescriptor(tableName);
94      hcd = new HColumnDescriptor(CF);
95      htd.addFamily(hcd);
96      try (Admin admin = TEST_UTIL.getConnection().getAdmin()) {
97        admin.createTable(htd);
98      }
99      numBatchesWritten = 0;
100   }
101 
102   @BeforeClass
103   public static void setUpBeforeClass() throws Exception {
104     // Use a small flush size to create more HFiles.
105     conf.setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 1024 * 1024);
106     // ((Log4JLogger)RpcServerImplementation.LOG).getLogger().setLevel(Level.TRACE);
107     // ((Log4JLogger)RpcClient.LOG).getLogger().setLevel(Level.TRACE);
108     // Disabling split to make sure split does not cause modify column to wait which timesout test
109     // sometime
110     conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
111       "org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy");
112     conf.setBoolean("hbase.online.schema.update.enable", true);
113     TEST_UTIL.startMiniCluster();
114   }
115 
116   @AfterClass
117   public static void tearDownAfterClass() throws Exception {
118     TEST_UTIL.shutdownMiniCluster();
119   }
120 
121   private static byte[] getRowKey(int batchId, int i) {
122     return Bytes.toBytes("batch" + batchId + "_row" + i);
123   }
124 
125   private static byte[] getQualifier(int j) {
126     return Bytes.toBytes("col" + j);
127   }
128 
129   private static byte[] getValue(int batchId, int i, int j) {
130     return Bytes.toBytes("value_for_" + Bytes.toString(getRowKey(batchId, i))
131         + "_col" + j);
132   }
133 
134   static void writeTestDataBatch(Configuration conf, TableName tableName,
135       int batchId) throws Exception {
136     LOG.debug("Writing test data batch " + batchId);
137     List<Put> puts = new ArrayList<>();
138     for (int i = 0; i < NUM_ROWS_PER_BATCH; ++i) {
139       Put put = new Put(getRowKey(batchId, i));
140       for (int j = 0; j < NUM_COLS_PER_ROW; ++j) {
141         put.add(CF_BYTES, getQualifier(j),
142             getValue(batchId, i, j));
143       }
144       put.setDurability(Durability.SKIP_WAL);
145       puts.add(put);
146     }
147     try (Connection conn = ConnectionFactory.createConnection(conf);
148         Table table = conn.getTable(tableName)) {
149       table.put(puts);
150     }
151   }
152 
153   static void verifyTestDataBatch(Configuration conf, TableName tableName,
154       int batchId) throws Exception {
155     LOG.debug("Verifying test data batch " + batchId);
156     Table table = new HTable(conf, tableName);
157     for (int i = 0; i < NUM_ROWS_PER_BATCH; ++i) {
158       Get get = new Get(getRowKey(batchId, i));
159       Result result = table.get(get);
160       for (int j = 0; j < NUM_COLS_PER_ROW; ++j) {
161         Cell kv = result.getColumnLatestCell(CF_BYTES, getQualifier(j));
162         assertTrue(CellUtil.matchingValue(kv, getValue(batchId, i, j)));
163       }
164     }
165     table.close();
166   }
167 
168   private void writeSomeNewData() throws Exception {
169     writeTestDataBatch(conf, tableName, numBatchesWritten);
170     ++numBatchesWritten;
171   }
172 
173   private void verifyAllData() throws Exception {
174     for (int i = 0; i < numBatchesWritten; ++i) {
175       verifyTestDataBatch(conf, tableName, i);
176     }
177   }
178 
179   private void setEncodingConf(DataBlockEncoding encoding,
180       boolean onlineChange) throws Exception {
181     LOG.debug("Setting CF encoding to " + encoding + " (ordinal="
182       + encoding.ordinal() + "), onlineChange=" + onlineChange);
183     hcd.setDataBlockEncoding(encoding);
184     try (Admin admin = TEST_UTIL.getConnection().getAdmin()) {
185       if (!onlineChange) {
186         admin.disableTable(tableName);
187       }
188       admin.modifyColumn(tableName, hcd);
189       if (!onlineChange) {
190         admin.enableTable(tableName);
191       }
192     }
193     // This is a unit test, not integration test. So let's
194     // wait for regions out of transition. Otherwise, for online
195     // encoding change, verification phase may be flaky because
196     // regions could be still in transition.
197     ZKAssign.blockUntilNoRIT(TEST_UTIL.getZooKeeperWatcher());
198   }
199 
200   @Test(timeout=TIMEOUT_MS)
201   public void testChangingEncoding() throws Exception {
202     prepareTest("ChangingEncoding");
203     for (boolean onlineChange : new boolean[]{false, true}) {
204       for (DataBlockEncoding encoding : ENCODINGS_TO_ITERATE) {
205         setEncodingConf(encoding, onlineChange);
206         writeSomeNewData();
207         verifyAllData();
208       }
209     }
210   }
211 
212   @Test(timeout=TIMEOUT_MS)
213   public void testChangingEncodingWithCompaction() throws Exception {
214     prepareTest("ChangingEncodingWithCompaction");
215     for (boolean onlineChange : new boolean[]{false, true}) {
216       for (DataBlockEncoding encoding : ENCODINGS_TO_ITERATE) {
217         setEncodingConf(encoding, onlineChange);
218         writeSomeNewData();
219         verifyAllData();
220         compactAndWait();
221         verifyAllData();
222       }
223     }
224   }
225 
226   private void compactAndWait() throws IOException, InterruptedException {
227     LOG.debug("Compacting table " + tableName);
228     HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0);
229     HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
230     admin.majorCompact(tableName);
231 
232     // Waiting for the compaction to start, at least .5s.
233     final long maxWaitime = System.currentTimeMillis() + 500;
234     boolean cont;
235     do {
236       cont = rs.compactSplitThread.getCompactionQueueSize() == 0;
237       Threads.sleep(1);
238     } while (cont && System.currentTimeMillis() < maxWaitime);
239 
240     while (rs.compactSplitThread.getCompactionQueueSize() > 0) {
241       Threads.sleep(1);
242     }
243     LOG.debug("Compaction queue size reached 0, continuing");
244   }
245 
246   @Test
247   public void testCrazyRandomChanges() throws Exception {
248     prepareTest("RandomChanges");
249     Random rand = new Random(2934298742974297L);
250     for (int i = 0; i < 20; ++i) {
251       int encodingOrdinal = rand.nextInt(DataBlockEncoding.values().length);
252       DataBlockEncoding encoding = DataBlockEncoding.values()[encodingOrdinal];
253       setEncodingConf(encoding, rand.nextBoolean());
254       writeSomeNewData();
255       verifyAllData();
256     }
257   }
258 
259 }