View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.procedure2.store.wal;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Random;
27  import java.util.Set;
28  
29  import org.apache.commons.cli.CommandLine;
30  import org.apache.commons.cli.Option;
31  import org.apache.hadoop.fs.FileSystem;
32  import org.apache.hadoop.fs.Path;
33  import org.apache.hadoop.hbase.HBaseCommonTestingUtility;
34  import org.apache.hadoop.hbase.ProcedureInfo;
35  import org.apache.hadoop.hbase.procedure2.Procedure;
36  import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
37  import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.TestProcedure;
38  import org.apache.hadoop.hbase.procedure2.store.ProcedureStore;
39  import org.apache.hadoop.hbase.procedure2.store.ProcedureStore.ProcedureIterator;
40  import org.apache.hadoop.hbase.procedure2.util.StringUtils;
41  import org.apache.hadoop.hbase.util.AbstractHBaseTool;
42  
43  import static java.lang.System.currentTimeMillis;
44  
45  public class ProcedureWALLoaderPerformanceEvaluation extends AbstractHBaseTool {
46    protected static final HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility();
47  
48    // Command line options and defaults.
49    public static int DEFAULT_NUM_PROCS = 1000000;  // 1M
50    public static Option NUM_PROCS_OPTION = new Option("procs", true,
51        "Total number of procedures. Default: " + DEFAULT_NUM_PROCS);
52    public static int DEFAULT_NUM_WALS = 0;
53    public static Option NUM_WALS_OPTION = new Option("wals", true,
54        "Number of WALs to write. If -ve or 0, uses " + WALProcedureStore.ROLL_THRESHOLD_CONF_KEY +
55            " conf to roll the logs. Default: " + DEFAULT_NUM_WALS);
56    public static int DEFAULT_STATE_SIZE = 1024;  // 1KB
57    public static Option STATE_SIZE_OPTION = new Option("size", true,
58        "Size of serialized state in bytes to write on update. Default: " + DEFAULT_STATE_SIZE
59            + " bytes");
60    public static int DEFAULT_UPDATES_PER_PROC = 5;
61    public static Option UPDATES_PER_PROC_OPTION = new Option("updates_per_proc", true,
62        "Number of update states to write for each proc. Default: " + DEFAULT_UPDATES_PER_PROC);
63    public static double DEFAULT_DELETE_PROCS_FRACTION = 0.50;
64    public static Option DELETE_PROCS_FRACTION_OPTION = new Option("delete_procs_fraction", true,
65        "Fraction of procs for which to write delete state. Distribution of procs chosen for "
66            + "delete is uniform across all procs. Default: " + DEFAULT_DELETE_PROCS_FRACTION);
67  
68    public int numProcs;
69    public int updatesPerProc;
70    public double deleteProcsFraction;
71    public int numWals;
72    private WALProcedureStore store;
73    static byte[] serializedState;
74  
75    private static class LoadCounter implements ProcedureStore.ProcedureLoader {
76      public LoadCounter() {}
77  
78      @Override
79      public void setMaxProcId(long maxProcId) {
80      }
81  
82      @Override
83      public void load(ProcedureIterator procIter) throws IOException {
84        while (procIter.hasNext()) {
85          if (procIter.isNextCompleted()) {
86            ProcedureInfo proc = procIter.nextAsProcedureInfo();
87          } else {
88            Procedure<?> proc = procIter.nextAsProcedure();
89          }
90        }
91      }
92  
93      @Override
94      public void handleCorrupted(ProcedureIterator procIter) throws IOException {
95        while (procIter.hasNext()) {
96          Procedure<?> proc = procIter.nextAsProcedure();
97        }
98      }
99    }
100 
101   @Override
102   protected void addOptions() {
103     addOption(NUM_PROCS_OPTION);
104     addOption(UPDATES_PER_PROC_OPTION);
105     addOption(DELETE_PROCS_FRACTION_OPTION);
106     addOption(NUM_WALS_OPTION);
107     addOption(STATE_SIZE_OPTION);
108   }
109 
110   @Override
111   protected void processOptions(CommandLine cmd) {
112     numProcs = getOptionAsInt(cmd, NUM_PROCS_OPTION.getOpt(), DEFAULT_NUM_PROCS);
113     numWals = getOptionAsInt(cmd, NUM_WALS_OPTION.getOpt(), DEFAULT_NUM_WALS);
114     int stateSize = getOptionAsInt(cmd, STATE_SIZE_OPTION.getOpt(), DEFAULT_STATE_SIZE);
115     serializedState = new byte[stateSize];
116     updatesPerProc = getOptionAsInt(cmd, UPDATES_PER_PROC_OPTION.getOpt(),
117         DEFAULT_UPDATES_PER_PROC);
118     deleteProcsFraction = getOptionAsDouble(cmd, DELETE_PROCS_FRACTION_OPTION.getOpt(),
119         DEFAULT_DELETE_PROCS_FRACTION);
120     setupConf();
121   }
122 
123   private void setupConf() {
124     if (numWals > 0) {
125       conf.setLong(WALProcedureStore.ROLL_THRESHOLD_CONF_KEY, Long.MAX_VALUE);
126     }
127   }
128 
129   public void setUpProcedureStore() throws IOException {
130     Path testDir = UTIL.getDataTestDir();
131     FileSystem fs = testDir.getFileSystem(conf);
132     Path logDir = new Path(testDir, "proc-logs");
133     System.out.println("\n\nLogs directory : " + logDir.toString() + "\n\n");
134     fs.delete(logDir, true);
135     store = ProcedureTestingUtility.createWalStore(conf, logDir);
136     store.start(1);
137     store.recoverLease();
138     store.load(new LoadCounter());
139   }
140 
141   /**
142    * @return a list of shuffled integers which represent state of proc id. First occurrence of a
143    * number denotes insert state, consecutive occurrences denote update states, and -ve value
144    * denotes delete state.
145    */
146   private List<Integer> shuffleProcWriteSequence() {
147     Random rand = new Random();
148     List<Integer> procStatesSequence = new ArrayList<>();
149     Set<Integer> toBeDeletedProcs = new HashSet<>();
150     // Add n + 1 entries of the proc id for insert + updates. If proc is chosen for delete, add
151     // extra entry which is marked -ve in the loop after shuffle.
152     for (int procId  = 1; procId <= numProcs; ++procId) {
153       procStatesSequence.addAll(Collections.nCopies(updatesPerProc + 1, procId));
154       if (rand.nextFloat() < deleteProcsFraction) {
155         procStatesSequence.add(procId);
156         toBeDeletedProcs.add(procId);
157       }
158     }
159     Collections.shuffle(procStatesSequence);
160     // Mark last occurrences of proc ids in toBeDeletedProcs with -ve to denote it's a delete state.
161     for (int i = procStatesSequence.size() - 1; i >= 0; --i) {
162       int procId = procStatesSequence.get(i);
163       if (toBeDeletedProcs.contains(procId)) {
164         procStatesSequence.set(i, -1 * procId);
165         toBeDeletedProcs.remove(procId);
166       }
167     }
168     return procStatesSequence;
169   }
170 
171   private void writeWals() throws IOException {
172     List<Integer> procStates = shuffleProcWriteSequence();
173     TestProcedure[] procs = new TestProcedure[numProcs + 1];  // 0 is not used.
174     int numProcsPerWal = numWals > 0 ? procStates.size() / numWals : Integer.MAX_VALUE;
175     long startTime = currentTimeMillis();
176     long lastTime = startTime;
177     for (int i = 0; i < procStates.size(); ++i) {
178       int procId = procStates.get(i);
179       if (procId < 0) {
180         store.delete(procs[-procId].getProcId());
181         procs[-procId] = null;
182       } else if (procs[procId] == null) {
183         procs[procId] = new TestProcedure(procId, 0);
184         procs[procId].setData(serializedState);
185         store.insert(procs[procId], null);
186       } else {
187         store.update(procs[procId]);
188       }
189       if (i > 0 && i % numProcsPerWal == 0) {
190         long currentTime = currentTimeMillis();
191         System.out.println("Forcing wall roll. Time taken on last WAL: " +
192             (currentTime - lastTime) / 1000.0f + " sec");
193         store.rollWriterForTesting();
194         lastTime = currentTime;
195       }
196     }
197     long timeTaken = currentTimeMillis() - startTime;
198     System.out.println("\n\nDone writing WALs.\nNum procs : " + numProcs + "\nTotal time taken : "
199         + StringUtils.humanTimeDiff(timeTaken) + "\n\n");
200   }
201 
202   private void storeRestart(ProcedureStore.ProcedureLoader loader) throws IOException {
203     System.out.println("Restarting procedure store to read back the WALs");
204     store.stop(false);
205     store.start(1);
206     store.recoverLease();
207 
208     long startTime = currentTimeMillis();
209     store.load(loader);
210     long timeTaken = System.currentTimeMillis() - startTime;
211     System.out.println("******************************************");
212     System.out.println("Load time : " + (timeTaken / 1000.0f) + "sec");
213     System.out.println("******************************************");
214   }
215 
216   public void tearDownProcedureStore() {
217     store.stop(false);
218     try {
219       store.getFileSystem().delete(store.getWALDir(), true);
220     } catch (IOException e) {
221       System.err.println("Error: Couldn't delete log dir. You can delete it manually to free up "
222           + "disk space. Location: " + store.getWALDir().toString());
223       System.err.println(e.toString());
224     }
225   }
226 
227   @Override
228   protected int doWork() {
229     try {
230       setUpProcedureStore();
231       writeWals();
232       storeRestart(new LoadCounter());
233       return EXIT_SUCCESS;
234     } catch (IOException e) {
235       e.printStackTrace();
236       return EXIT_FAILURE;
237     } finally {
238       tearDownProcedureStore();
239     }
240   }
241 
242   public static void main(String[] args) throws IOException {
243     ProcedureWALLoaderPerformanceEvaluation tool = new ProcedureWALLoaderPerformanceEvaluation();
244     tool.setConf(UTIL.getConfiguration());
245     tool.run(args);
246   }
247 }