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.io.PrintWriter;
23  import java.io.StringWriter;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.concurrent.BlockingQueue;
28  import java.util.concurrent.Executors;
29  import java.util.concurrent.RejectedExecutionException;
30  import java.util.concurrent.RejectedExecutionHandler;
31  import java.util.concurrent.ThreadFactory;
32  import java.util.concurrent.ThreadPoolExecutor;
33  import java.util.concurrent.TimeUnit;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.hbase.RemoteExceptionHandler;
39  import org.apache.hadoop.hbase.classification.InterfaceAudience;
40  import org.apache.hadoop.hbase.conf.ConfigurationManager;
41  import org.apache.hadoop.hbase.conf.PropagatingConfigurationObserver;
42  import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
43  import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest;
44  import org.apache.hadoop.hbase.regionserver.throttle.CompactionThroughputControllerFactory;
45  import org.apache.hadoop.hbase.regionserver.throttle.ThroughputController;
46  import org.apache.hadoop.hbase.security.User;
47  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
48  import org.apache.hadoop.hbase.util.Pair;
49  import org.apache.hadoop.hbase.util.StealJobQueue;
50  import org.apache.hadoop.util.StringUtils;
51  
52  import com.google.common.annotations.VisibleForTesting;
53  import com.google.common.base.Preconditions;
54  
55  /**
56   * Compact region on request and then run split if appropriate
57   */
58  @InterfaceAudience.Private
59  public class CompactSplitThread implements CompactionRequestor, PropagatingConfigurationObserver {
60    private static final Log LOG = LogFactory.getLog(CompactSplitThread.class);
61  
62    // Configuration key for the large compaction threads.
63    public final static String LARGE_COMPACTION_THREADS =
64        "hbase.regionserver.thread.compaction.large";
65    public final static int LARGE_COMPACTION_THREADS_DEFAULT = 1;
66    
67    // Configuration key for the small compaction threads.
68    public final static String SMALL_COMPACTION_THREADS =
69        "hbase.regionserver.thread.compaction.small";
70    public final static int SMALL_COMPACTION_THREADS_DEFAULT = 1;
71    
72    // Configuration key for split threads
73    public final static String SPLIT_THREADS = "hbase.regionserver.thread.split";
74    public final static int SPLIT_THREADS_DEFAULT = 1;
75    
76    // Configuration keys for merge threads
77    public final static String MERGE_THREADS = "hbase.regionserver.thread.merge";
78    public final static int MERGE_THREADS_DEFAULT = 1;
79  
80    public static final String REGION_SERVER_REGION_SPLIT_LIMIT =
81        "hbase.regionserver.regionSplitLimit";
82    public static final int DEFAULT_REGION_SERVER_REGION_SPLIT_LIMIT= 1000;
83    
84    private final HRegionServer server;
85    private final Configuration conf;
86  
87    private final ThreadPoolExecutor longCompactions;
88    private final ThreadPoolExecutor shortCompactions;
89    private final ThreadPoolExecutor splits;
90    private final ThreadPoolExecutor mergePool;
91  
92    private volatile ThroughputController compactionThroughputController;
93  
94    /**
95     * Splitting should not take place if the total number of regions exceed this.
96     * This is not a hard limit to the number of regions but it is a guideline to
97     * stop splitting after number of online regions is greater than this.
98     */
99    private int regionSplitLimit;
100 
101   /** @param server */
102   CompactSplitThread(HRegionServer server) {
103     super();
104     this.server = server;
105     this.conf = server.getConfiguration();
106     this.regionSplitLimit = conf.getInt(REGION_SERVER_REGION_SPLIT_LIMIT,
107         DEFAULT_REGION_SERVER_REGION_SPLIT_LIMIT);
108 
109     int largeThreads = Math.max(1, conf.getInt(
110         LARGE_COMPACTION_THREADS, LARGE_COMPACTION_THREADS_DEFAULT));
111     int smallThreads = conf.getInt(
112         SMALL_COMPACTION_THREADS, SMALL_COMPACTION_THREADS_DEFAULT);
113 
114     int splitThreads = conf.getInt(SPLIT_THREADS, SPLIT_THREADS_DEFAULT);
115 
116     // if we have throttle threads, make sure the user also specified size
117     Preconditions.checkArgument(largeThreads > 0 && smallThreads > 0);
118 
119     final String n = Thread.currentThread().getName();
120 
121     StealJobQueue<Runnable> stealJobQueue = new StealJobQueue<>();
122     this.longCompactions = new ThreadPoolExecutor(largeThreads, largeThreads,
123         60, TimeUnit.SECONDS, stealJobQueue,
124         new ThreadFactory() {
125           @Override
126           public Thread newThread(Runnable r) {
127             String name = n + "-longCompactions-" + System.currentTimeMillis();
128             return new Thread(r, name);
129           }
130       });
131     this.longCompactions.setRejectedExecutionHandler(new Rejection());
132     this.longCompactions.prestartAllCoreThreads();
133     this.shortCompactions = new ThreadPoolExecutor(smallThreads, smallThreads,
134         60, TimeUnit.SECONDS, stealJobQueue.getStealFromQueue(),
135         new ThreadFactory() {
136           @Override
137           public Thread newThread(Runnable r) {
138             String name = n + "-shortCompactions-" + System.currentTimeMillis();
139             return new Thread(r, name);
140           }
141       });
142     this.shortCompactions
143         .setRejectedExecutionHandler(new Rejection());
144     this.splits = (ThreadPoolExecutor)
145         Executors.newFixedThreadPool(splitThreads,
146             new ThreadFactory() {
147           @Override
148           public Thread newThread(Runnable r) {
149             String name = n + "-splits-" + System.currentTimeMillis();
150             return new Thread(r, name);
151           }
152       });
153     int mergeThreads = conf.getInt(MERGE_THREADS, MERGE_THREADS_DEFAULT);
154     this.mergePool = (ThreadPoolExecutor) Executors.newFixedThreadPool(
155         mergeThreads, new ThreadFactory() {
156           @Override
157           public Thread newThread(Runnable r) {
158             String name = n + "-merges-" + System.currentTimeMillis();
159             return new Thread(r, name);
160           }
161         });
162 
163     // compaction throughput controller
164     this.compactionThroughputController =
165         CompactionThroughputControllerFactory.create(server, conf);
166   }
167 
168   @Override
169   public String toString() {
170     return "compaction_queue=("
171         + longCompactions.getQueue().size() + ":"
172         + shortCompactions.getQueue().size() + ")"
173         + ", split_queue=" + splits.getQueue().size()
174         + ", merge_queue=" + mergePool.getQueue().size();
175   }
176   
177   public String dumpQueue() {
178     StringBuffer queueLists = new StringBuffer();
179     queueLists.append("Compaction/Split Queue dump:\n");
180     queueLists.append("  LargeCompation Queue:\n");
181     BlockingQueue<Runnable> lq = longCompactions.getQueue();
182     Iterator<Runnable> it = lq.iterator();
183     while (it.hasNext()) {
184       queueLists.append("    " + it.next().toString());
185       queueLists.append("\n");
186     }
187 
188     if (shortCompactions != null) {
189       queueLists.append("\n");
190       queueLists.append("  SmallCompation Queue:\n");
191       lq = shortCompactions.getQueue();
192       it = lq.iterator();
193       while (it.hasNext()) {
194         queueLists.append("    " + it.next().toString());
195         queueLists.append("\n");
196       }
197     }
198 
199     queueLists.append("\n");
200     queueLists.append("  Split Queue:\n");
201     lq = splits.getQueue();
202     it = lq.iterator();
203     while (it.hasNext()) {
204       queueLists.append("    " + it.next().toString());
205       queueLists.append("\n");
206     }
207 
208     queueLists.append("\n");
209     queueLists.append("  Region Merge Queue:\n");
210     lq = mergePool.getQueue();
211     it = lq.iterator();
212     while (it.hasNext()) {
213       queueLists.append("    " + it.next().toString());
214       queueLists.append("\n");
215     }
216 
217     return queueLists.toString();
218   }
219 
220   public synchronized void requestRegionsMerge(final Region a,
221       final Region b, final boolean forcible, long masterSystemTime, User user) {
222     try {
223       mergePool.execute(new RegionMergeRequest(a, b, this.server, forcible, masterSystemTime,user));
224       if (LOG.isDebugEnabled()) {
225         LOG.debug("Region merge requested for " + a + "," + b + ", forcible="
226             + forcible + ".  " + this);
227       }
228     } catch (RejectedExecutionException ree) {
229       LOG.warn("Could not execute merge for " + a + "," + b + ", forcible="
230           + forcible, ree);
231     }
232   }
233 
234   public synchronized boolean requestSplit(final Region r) {
235     // don't split regions that are blocking
236     if (shouldSplitRegion() && ((HRegion)r).getCompactPriority() >= Store.PRIORITY_USER) {
237       byte[] midKey = ((HRegion)r).checkSplit();
238       if (midKey != null) {
239         requestSplit(r, midKey);
240         return true;
241       }
242     }
243     return false;
244   }
245 
246   public synchronized void requestSplit(final Region r, byte[] midKey) {
247     requestSplit(r, midKey, null);
248   }
249 
250   /*
251    * The User parameter allows the split thread to assume the correct user identity
252    */
253   public synchronized void requestSplit(final Region r, byte[] midKey, User user) {
254     if (midKey == null) {
255       LOG.debug("Region " + r.getRegionInfo().getRegionNameAsString() +
256         " not splittable because midkey=null");
257       if (((HRegion)r).shouldForceSplit()) {
258         ((HRegion)r).clearSplit();
259       }
260       return;
261     }
262     try {
263       this.splits.execute(new SplitRequest(r, midKey, this.server, user));
264       if (LOG.isDebugEnabled()) {
265         LOG.debug("Split requested for " + r + ".  " + this);
266       }
267     } catch (RejectedExecutionException ree) {
268       LOG.info("Could not execute split for " + r, ree);
269     }
270   }
271 
272   @Override
273   public synchronized List<CompactionRequest> requestCompaction(final Region r, final String why)
274       throws IOException {
275     return requestCompaction(r, why, null);
276   }
277 
278   @Override
279   public synchronized List<CompactionRequest> requestCompaction(final Region r, final String why,
280       List<Pair<CompactionRequest, Store>> requests) throws IOException {
281     return requestCompaction(r, why, Store.NO_PRIORITY, requests, null);
282   }
283 
284   @Override
285   public synchronized CompactionRequest requestCompaction(final Region r, final Store s,
286       final String why, CompactionRequest request) throws IOException {
287     return requestCompaction(r, s, why, Store.NO_PRIORITY, request, null);
288   }
289 
290   @Override
291   public synchronized List<CompactionRequest> requestCompaction(final Region r, final String why,
292       int p, List<Pair<CompactionRequest, Store>> requests, User user) throws IOException {
293     return requestCompactionInternal(r, why, p, requests, true, user);
294   }
295 
296   private List<CompactionRequest> requestCompactionInternal(final Region r, final String why,
297       int p, List<Pair<CompactionRequest, Store>> requests, boolean selectNow, User user)
298           throws IOException {
299     // not a special compaction request, so make our own list
300     List<CompactionRequest> ret = null;
301     if (requests == null) {
302       ret = selectNow ? new ArrayList<CompactionRequest>(r.getStores().size()) : null;
303       for (Store s : r.getStores()) {
304         CompactionRequest cr = requestCompactionInternal(r, s, why, p, null, selectNow, user);
305         if (selectNow) ret.add(cr);
306       }
307     } else {
308       Preconditions.checkArgument(selectNow); // only system requests have selectNow == false
309       ret = new ArrayList<CompactionRequest>(requests.size());
310       for (Pair<CompactionRequest, Store> pair : requests) {
311         ret.add(requestCompaction(r, pair.getSecond(), why, p, pair.getFirst(), user));
312       }
313     }
314     return ret;
315   }
316 
317   @Override
318   public CompactionRequest requestCompaction(final Region r, final Store s,
319       final String why, int priority, CompactionRequest request, User user) throws IOException {
320     return requestCompactionInternal(r, s, why, priority, request, true, user);
321   }
322 
323   public synchronized void requestSystemCompaction(
324       final Region r, final String why) throws IOException {
325     requestCompactionInternal(r, why, Store.NO_PRIORITY, null, false, null);
326   }
327 
328   public void requestSystemCompaction(
329       final Region r, final Store s, final String why) throws IOException {
330     requestCompactionInternal(r, s, why, Store.NO_PRIORITY, null, false, null);
331   }
332 
333   /**
334    * @param r region store belongs to
335    * @param s Store to request compaction on
336    * @param why Why compaction requested -- used in debug messages
337    * @param priority override the default priority (NO_PRIORITY == decide)
338    * @param request custom compaction request. Can be <tt>null</tt> in which case a simple
339    *          compaction will be used.
340    */
341   private synchronized CompactionRequest requestCompactionInternal(final Region r, final Store s,
342       final String why, int priority, CompactionRequest request, boolean selectNow, User user)
343           throws IOException {
344     if (this.server.isStopped()
345         || (r.getTableDesc() != null && !r.getTableDesc().isCompactionEnabled())) {
346       return null;
347     }
348 
349     CompactionContext compaction = null;
350     if (selectNow) {
351       compaction = selectCompaction(r, s, priority, request, user);
352       if (compaction == null) return null; // message logged inside
353     }
354 
355     // We assume that most compactions are small. So, put system compactions into small
356     // pool; we will do selection there, and move to large pool if necessary.
357     ThreadPoolExecutor pool = (selectNow && s.throttleCompaction(compaction.getRequest().getSize()))
358       ? longCompactions : shortCompactions;
359     pool.execute(new CompactionRunner(s, r, compaction, pool, user));
360     ((HRegion)r).incrementCompactionsQueuedCount();
361     if (LOG.isDebugEnabled()) {
362       String type = (pool == shortCompactions) ? "Small " : "Large ";
363       LOG.debug(type + "Compaction requested: " + (selectNow ? compaction.toString() : "system")
364           + (why != null && !why.isEmpty() ? "; Because: " + why : "") + "; " + this);
365     }
366     return selectNow ? compaction.getRequest() : null;
367   }
368 
369   private CompactionContext selectCompaction(final Region r, final Store s,
370       int priority, CompactionRequest request, User user) throws IOException {
371     CompactionContext compaction = s.requestCompaction(priority, request, user);
372     if (compaction == null) {
373       if(LOG.isDebugEnabled() && r.getRegionInfo() != null) {
374         LOG.debug("Not compacting " + r.getRegionInfo().getRegionNameAsString() +
375             " because compaction request was cancelled");
376       }
377       return null;
378     }
379     assert compaction.hasSelection();
380     if (priority != Store.NO_PRIORITY) {
381       compaction.getRequest().setPriority(priority);
382     }
383     return compaction;
384   }
385 
386   /**
387    * Only interrupt once it's done with a run through the work loop.
388    */
389   void interruptIfNecessary() {
390     splits.shutdown();
391     mergePool.shutdown();
392     longCompactions.shutdown();
393     shortCompactions.shutdown();
394   }
395 
396   private void waitFor(ThreadPoolExecutor t, String name) {
397     boolean done = false;
398     while (!done) {
399       try {
400         done = t.awaitTermination(60, TimeUnit.SECONDS);
401         LOG.info("Waiting for " + name + " to finish...");
402         if (!done) {
403           t.shutdownNow();
404         }
405       } catch (InterruptedException ie) {
406         LOG.warn("Interrupted waiting for " + name + " to finish...");
407       }
408     }
409   }
410 
411   void join() {
412     waitFor(splits, "Split Thread");
413     waitFor(mergePool, "Merge Thread");
414     waitFor(longCompactions, "Large Compaction Thread");
415     waitFor(shortCompactions, "Small Compaction Thread");
416   }
417 
418   /**
419    * Returns the current size of the queue containing regions that are
420    * processed.
421    *
422    * @return The current size of the regions queue.
423    */
424   public int getCompactionQueueSize() {
425     return longCompactions.getQueue().size() + shortCompactions.getQueue().size();
426   }
427 
428   public int getLargeCompactionQueueSize() {
429     return longCompactions.getQueue().size();
430   }
431 
432 
433   public int getSmallCompactionQueueSize() {
434     return shortCompactions.getQueue().size();
435   }
436 
437   public int getSplitQueueSize() {
438     return splits.getQueue().size();
439   }
440 
441   private boolean shouldSplitRegion() {
442     if(server.getNumberOfOnlineRegions() > 0.9*regionSplitLimit) {
443       LOG.warn("Total number of regions is approaching the upper limit " + regionSplitLimit + ". "
444           + "Please consider taking a look at http://hbase.apache.org/book.html#ops.regionmgt");
445     }
446     return (regionSplitLimit > server.getNumberOfOnlineRegions());
447   }
448 
449   /**
450    * @return the regionSplitLimit
451    */
452   public int getRegionSplitLimit() {
453     return this.regionSplitLimit;
454   }
455 
456   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="EQ_COMPARETO_USE_OBJECT_EQUALS",
457       justification="Contrived use of compareTo")
458   private class CompactionRunner implements Runnable, Comparable<CompactionRunner> {
459     private final Store store;
460     private final HRegion region;
461     private CompactionContext compaction;
462     private int queuedPriority;
463     private ThreadPoolExecutor parent;
464     private User user;
465     private long time;
466 
467     public CompactionRunner(Store store, Region region,
468         CompactionContext compaction, ThreadPoolExecutor parent, User user) {
469       super();
470       this.store = store;
471       this.region = (HRegion)region;
472       this.compaction = compaction;
473       this.queuedPriority = (this.compaction == null)
474           ? store.getCompactPriority() : compaction.getRequest().getPriority();
475       this.parent = parent;
476       this.user = user;
477       this.time =  System.currentTimeMillis();
478     }
479 
480     @Override
481     public String toString() {
482       return (this.compaction != null) ? ("Request = " + compaction.getRequest())
483           : ("regionName = " + region.toString() + ", storeName = " + store.toString() +
484              ", priority = " + queuedPriority + ", time = " + time);
485     }
486 
487     private void doCompaction(User user) {
488       // Common case - system compaction without a file selection. Select now.
489       if (this.compaction == null) {
490         int oldPriority = this.queuedPriority;
491         this.queuedPriority = this.store.getCompactPriority();
492         if (this.queuedPriority > oldPriority) {
493           // Store priority decreased while we were in queue (due to some other compaction?),
494           // requeue with new priority to avoid blocking potential higher priorities.
495           this.parent.execute(this);
496           return;
497         }
498         try {
499           this.compaction = selectCompaction(this.region, this.store, queuedPriority, null, user);
500         } catch (IOException ex) {
501           LOG.error("Compaction selection failed " + this, ex);
502           server.checkFileSystem();
503           region.decrementCompactionsQueuedCount();
504           return;
505         }
506         if (this.compaction == null) {
507           region.decrementCompactionsQueuedCount();
508           return; // nothing to do
509         }
510         // Now see if we are in correct pool for the size; if not, go to the correct one.
511         // We might end up waiting for a while, so cancel the selection.
512         assert this.compaction.hasSelection();
513         ThreadPoolExecutor pool = store.throttleCompaction(
514             compaction.getRequest().getSize()) ? longCompactions : shortCompactions;
515 
516         // Long compaction pool can process small job
517         // Short compaction pool should not process large job
518         if (this.parent == shortCompactions && pool == longCompactions) {
519           this.store.cancelRequestedCompaction(this.compaction);
520           this.compaction = null;
521           this.parent = pool;
522           this.parent.execute(this);
523           return;
524         }
525       }
526       // Finally we can compact something.
527       assert this.compaction != null;
528 
529       this.compaction.getRequest().beforeExecute();
530       try {
531         // Note: please don't put single-compaction logic here;
532         //       put it into region/store/etc. This is CST logic.
533         long start = EnvironmentEdgeManager.currentTime();
534         boolean completed =
535             region.compact(compaction, store, compactionThroughputController, user);
536         long now = EnvironmentEdgeManager.currentTime();
537         LOG.info(((completed) ? "Completed" : "Aborted") + " compaction: " +
538               this + "; duration=" + StringUtils.formatTimeDiff(now, start));
539         if (completed) {
540           // degenerate case: blocked regions require recursive enqueues
541           if (store.getCompactPriority() <= 0) {
542             requestSystemCompaction(region, store, "Recursive enqueue");
543           } else {
544             // see if the compaction has caused us to exceed max region size
545             requestSplit(region);
546           }
547         }
548       } catch (IOException ex) {
549         IOException remoteEx = RemoteExceptionHandler.checkIOException(ex);
550         LOG.error("Compaction failed " + this, remoteEx);
551         if (remoteEx != ex) {
552           LOG.info("Compaction failed at original callstack: " + formatStackTrace(ex));
553         }
554         region.reportCompactionRequestFailure();
555         server.checkFileSystem();
556       } catch (Exception ex) {
557         LOG.error("Compaction failed " + this, ex);
558         region.reportCompactionRequestFailure();
559         server.checkFileSystem();
560       } finally {
561         region.decrementCompactionsQueuedCount();
562         LOG.debug("CompactSplitThread Status: " + CompactSplitThread.this);
563       }
564       this.compaction.getRequest().afterExecute();
565     }
566 
567     @Override
568     public void run() {
569       Preconditions.checkNotNull(server);
570       if (server.isStopped()
571           || (region.getTableDesc() != null && !region.getTableDesc().isCompactionEnabled())) {
572         region.decrementCompactionsQueuedCount();
573         return;
574       }
575       doCompaction(user);
576     }
577 
578     private String formatStackTrace(Exception ex) {
579       StringWriter sw = new StringWriter();
580       PrintWriter pw = new PrintWriter(sw);
581       ex.printStackTrace(pw);
582       pw.flush();
583       return sw.toString();
584     }
585 
586     @Override
587     public int compareTo(CompactionRunner o) {
588       // Only compare the underlying request (if any), for queue sorting purposes.
589       int compareVal = queuedPriority - o.queuedPriority; // compare priority
590       if (compareVal != 0) return compareVal;
591       CompactionContext tc = this.compaction, oc = o.compaction;
592       // Sort pre-selected (user?) compactions before system ones with equal priority.
593       return (tc == null) ? ((oc == null) ? 0 : 1)
594           : ((oc == null) ? -1 : tc.getRequest().compareTo(oc.getRequest()));
595     }
596   }
597 
598   /**
599    * Cleanup class to use when rejecting a compaction request from the queue.
600    */
601   private static class Rejection implements RejectedExecutionHandler {
602     @Override
603     public void rejectedExecution(Runnable runnable, ThreadPoolExecutor pool) {
604       if (runnable instanceof CompactionRunner) {
605         CompactionRunner runner = (CompactionRunner)runnable;
606         LOG.debug("Compaction Rejected: " + runner);
607         runner.store.cancelRequestedCompaction(runner.compaction);
608       }
609     }
610   }
611 
612   /**
613    * {@inheritDoc}
614    */
615   @Override
616   public void onConfigurationChange(Configuration newConf) {
617     // Check if number of large / small compaction threads has changed, and then
618     // adjust the core pool size of the thread pools, by using the
619     // setCorePoolSize() method. According to the javadocs, it is safe to
620     // change the core pool size on-the-fly. We need to reset the maximum
621     // pool size, as well.
622     int largeThreads = Math.max(1, newConf.getInt(
623             LARGE_COMPACTION_THREADS,
624             LARGE_COMPACTION_THREADS_DEFAULT));
625     if (this.longCompactions.getCorePoolSize() != largeThreads) {
626       LOG.info("Changing the value of " + LARGE_COMPACTION_THREADS +
627               " from " + this.longCompactions.getCorePoolSize() + " to " +
628               largeThreads);
629       if(this.longCompactions.getCorePoolSize() < largeThreads) {
630         this.longCompactions.setMaximumPoolSize(largeThreads);
631         this.longCompactions.setCorePoolSize(largeThreads);
632       } else {
633         this.longCompactions.setCorePoolSize(largeThreads);
634         this.longCompactions.setMaximumPoolSize(largeThreads);
635       }
636     }
637 
638     int smallThreads = newConf.getInt(SMALL_COMPACTION_THREADS,
639             SMALL_COMPACTION_THREADS_DEFAULT);
640     if (this.shortCompactions.getCorePoolSize() != smallThreads) {
641       LOG.info("Changing the value of " + SMALL_COMPACTION_THREADS +
642                 " from " + this.shortCompactions.getCorePoolSize() + " to " +
643                 smallThreads);
644       if(this.shortCompactions.getCorePoolSize() < smallThreads) {
645         this.shortCompactions.setMaximumPoolSize(smallThreads);
646         this.shortCompactions.setCorePoolSize(smallThreads);
647       } else {
648         this.shortCompactions.setCorePoolSize(smallThreads);
649         this.shortCompactions.setMaximumPoolSize(smallThreads);
650       }
651     }
652 
653     int splitThreads = newConf.getInt(SPLIT_THREADS,
654             SPLIT_THREADS_DEFAULT);
655     if (this.splits.getCorePoolSize() != splitThreads) {
656       LOG.info("Changing the value of " + SPLIT_THREADS +
657                 " from " + this.splits.getCorePoolSize() + " to " +
658                 splitThreads);
659       if(this.splits.getCorePoolSize() < splitThreads) {
660         this.splits.setMaximumPoolSize(splitThreads);
661         this.splits.setCorePoolSize(splitThreads);
662       } else {
663         this.splits.setCorePoolSize(splitThreads);
664         this.splits.setMaximumPoolSize(splitThreads);
665       }
666     }
667 
668     int mergeThreads = newConf.getInt(MERGE_THREADS,
669             MERGE_THREADS_DEFAULT);
670     if (this.mergePool.getCorePoolSize() != mergeThreads) {
671       LOG.info("Changing the value of " + MERGE_THREADS +
672                 " from " + this.mergePool.getCorePoolSize() + " to " +
673                 mergeThreads);
674       if(this.mergePool.getCorePoolSize() < mergeThreads) {
675         this.mergePool.setMaximumPoolSize(mergeThreads);
676         this.mergePool.setCorePoolSize(mergeThreads);
677       } else {
678         this.mergePool.setCorePoolSize(mergeThreads);
679         this.mergePool.setMaximumPoolSize(mergeThreads);
680       }
681     }
682 
683     ThroughputController old = this.compactionThroughputController;
684     if (old != null) {
685       old.stop("configuration change");
686     }
687     this.compactionThroughputController =
688         CompactionThroughputControllerFactory.create(server, newConf);
689 
690     // We change this atomically here instead of reloading the config in order that upstream
691     // would be the only one with the flexibility to reload the config.
692     this.conf.reloadConfiguration();
693   }
694 
695   protected int getSmallCompactionThreadNum() {
696     return this.shortCompactions.getCorePoolSize();
697   }
698 
699   protected int getLargeCompactionThreadNum() {
700     return this.longCompactions.getCorePoolSize();
701   }
702 
703   protected int getSplitThreadNum() {
704     return this.splits.getCorePoolSize();
705   }
706 
707   protected int getMergeThreadNum() {
708     return this.mergePool.getCorePoolSize();
709   }
710 
711   /**
712    * {@inheritDoc}
713    */
714   @Override
715   public void registerChildren(ConfigurationManager manager) {
716     // No children to register.
717   }
718 
719   /**
720    * {@inheritDoc}
721    */
722   @Override
723   public void deregisterChildren(ConfigurationManager manager) {
724     // No children to register
725   }
726 
727   @VisibleForTesting
728   public ThroughputController getCompactionThroughputController() {
729     return compactionThroughputController;
730   }
731 
732   @VisibleForTesting
733   /**
734    * Shutdown the long compaction thread pool.
735    * Should only be used in unit test to prevent long compaction thread pool from stealing job
736    * from short compaction queue
737    */
738   void shutdownLongCompactions(){
739     this.longCompactions.shutdown();
740   }
741 }