@Override
  public TunerResult tune(TunerContext context) {
    float curMemstoreSize = context.getCurMemStoreSize();
    float curBlockCacheSize = context.getCurBlockCacheSize();
    addToRollingStats(context);

    if (ignoreInitialPeriods < numPeriodsToIgnore) {
      // Ignoring the first few tuner periods
      ignoreInitialPeriods++;
      rollingStatsForTunerSteps.insertDataValue(0);
      LOG.info("Ignoring initial tuning periods: {} so far, {} to ignore", ignoreInitialPeriods,
        numPeriodsToIgnore);
      return NO_OP_TUNER_RESULT;
    }
    StepDirection newTuneDirection = getTuneDirection(context);

    long blockedFlushCount = context.getBlockedFlushCount();
    long unblockedFlushCount = context.getUnblockedFlushCount();
    long totalOnheapFlushCount = blockedFlushCount + unblockedFlushCount;
    boolean offheapMemstore = context.isOffheapMemStore();
    float newMemstoreSize;
    float newBlockCacheSize;

    // Adjusting step size for tuning to get to steady state or restart from steady state.
    // Even if the step size was 4% and 32 GB memory size, we will be shifting 1 GB back and forth
    // per tuner operation and it can affect the performance of cluster so we keep on decreasing
    // step size until everything settles.
    if (
      prevTuneDirection == StepDirection.NEUTRAL && newTuneDirection != StepDirection.NEUTRAL
        && rollingStatsForTunerSteps.getDeviation() < TUNER_STEP_EPS
    ) {
      // Restarting the tuning from steady state and setting step size to maximum.
      // The deviation cannot be that low if last period was neutral and some recent periods were
      // not neutral.
      step = maximumStepSize;
    } else if (
      (newTuneDirection == StepDirection.INCREASE_MEMSTORE_SIZE && decayingTunerStepSizeSum < 0)
        || (newTuneDirection == StepDirection.INCREASE_BLOCK_CACHE_SIZE
          && decayingTunerStepSizeSum > 0)
    ) {
      // Current step is opposite of past tuner actions so decrease the step size to reach steady
      // state.
      if (!offheapMemstore && step != minimumStepSize) {
        // we leave the step to be at minimumStepSize for offheap memstore
        step = step / 2.00f;
      }
    }
    if (step < minimumStepSize) {
      // If step size is too small then we do nothing.
      LOG.debug("Tuner step size is too low; we will not perform any tuning this time.");
      step = 0.0f;
      newTuneDirection = StepDirection.NEUTRAL;
    }
    // There are no flushes due to onheap pressure and
    // we have an offheap memstore and we are in need of more block_cache size.
    if (
      totalOnheapFlushCount == 0 && offheapMemstore
        && newTuneDirection == StepDirection.INCREASE_BLOCK_CACHE_SIZE
    ) {
      // we are sure that there are flushes only due to offheap pressure
      // So don't do the memstore decrease equal to the step size. Instead do minimum stepSize
      // decrease. But even if we have some flushes due to heap then it is better we tune
      // the existing way.
      step = minimumStepSize;
    }
    // Increase / decrease the memstore / block cache sizes depending on new tuner step.
    // We don't want to exert immediate pressure on memstore. So, we decrease its size gracefully;
    // we set a minimum bar in the middle of the total memstore size and the lower limit.
    float minMemstoreSize = ((globalMemStoreLimitLowMarkPercent + 1) * curMemstoreSize) / 2.00f;

    switch (newTuneDirection) {
      case INCREASE_BLOCK_CACHE_SIZE:
        if (curMemstoreSize - step < minMemstoreSize) {
          step = curMemstoreSize - minMemstoreSize;
        }
        newMemstoreSize = curMemstoreSize - step;
        newBlockCacheSize = curBlockCacheSize + step;
        rollingStatsForTunerSteps.insertDataValue(-(int) (step * 100000));
        decayingTunerStepSizeSum = (decayingTunerStepSizeSum - step) / 2.00f;
        break;
      case INCREASE_MEMSTORE_SIZE:
        newBlockCacheSize = curBlockCacheSize - step;
        newMemstoreSize = curMemstoreSize + step;
        rollingStatsForTunerSteps.insertDataValue((int) (step * 100000));
        decayingTunerStepSizeSum = (decayingTunerStepSizeSum + step) / 2.00f;
        break;
      default:
        prevTuneDirection = StepDirection.NEUTRAL;
        rollingStatsForTunerSteps.insertDataValue(0);
        decayingTunerStepSizeSum = (decayingTunerStepSizeSum) / 2.00f;
        return NO_OP_TUNER_RESULT;
    }
    // Check we are within max/min bounds.
    if (newMemstoreSize > globalMemStorePercentMaxRange) {
      newMemstoreSize = globalMemStorePercentMaxRange;
    } else if (newMemstoreSize < globalMemStorePercentMinRange) {
      newMemstoreSize = globalMemStorePercentMinRange;
    }
    if (newBlockCacheSize > blockCachePercentMaxRange) {
      newBlockCacheSize = blockCachePercentMaxRange;
    } else if (newBlockCacheSize < blockCachePercentMinRange) {
      newBlockCacheSize = blockCachePercentMinRange;
    }
    TUNER_RESULT.setBlockCacheSize(newBlockCacheSize);
    TUNER_RESULT.setMemStoreSize(newMemstoreSize);
    prevTuneDirection = newTuneDirection;
    return TUNER_RESULT;
  }
The default implementation for the HeapMemoryTuner. This will do statistical checks on number of
evictions, cache misses and flushes to decide whether there should be changes in the heap size of
memstore/block cache. During each tuner operation tuner takes a step which can either be
INCREASE_BLOCK_CACHE_SIZE (increase block cache size), INCREASE_MEMSTORE_SIZE (increase memstore
size) and by default it is NEUTRAL (no change). We say block cache is sufficient when there is no
block cache eviction at all or major amount of memory allocated to block cache is empty,
similarly we say memory allocated for memstore is sufficient when there is no memstore flushes
because of heap pressure or major amount of memory allocated to memstore is empty. If both are
sufficient we do nothing, if exactly one of them is found to be sufficient we decrease its size
by <i>step</i> and increase the other by same amount. If none of them is sufficient we do
statistical analysis on number of cache misses and flushes to determine tuner direction. Based on
these statistics we decide the tuner direction. If we are not confident about which step
direction to take we do nothing and wait for next iteration. On expectation we will be tuning for
at least 10% tuner calls. The number of past periods to consider for statistics calculation can
be specified in config by <i>hbase.regionserver.heapmemory.autotuner.lookup.periods</i>. Also
these many initial calls to tuner will be ignored (cache is warming up and we leave the system to
reach steady state). After the tuner takes a step, in next call we insure that last call was
indeed helpful and did not do us any harm. If not then we revert the previous step. The step size
is dynamic and it changes based on current and past few tuning directions and their step sizes.
We maintain a parameter <i>decayingAvgTunerStepSize</i> which is sum of past tuner steps with
sign(positive for increase in memstore and negative for increase in block cache). But rather than
simple sum it is calculated by giving more priority to the recent tuning steps. When last few
tuner steps were NETURAL then we assume we are restarting the tuning process and step size is
updated to maximum allowed size which can be specified in config by
<i>hbase.regionserver.heapmemory.autotuner.step.max</i>. If in a particular tuning operation the
step direction is opposite to what indicated by <i>decayingTunerStepSizeSum</i> we decrease the
step size by half. Step size does not change in other tuning operations. When step size gets
below a certain threshold then the following tuner operations are considered to be neutral. The
minimum step size can be specified in config by
<i>hbase.regionserver.heapmemory.autotuner.step.min</i>.
hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/DefaultHeapMemoryTuner.java