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.io.util;
019
020import java.lang.management.ManagementFactory;
021import java.lang.management.MemoryType;
022import java.lang.management.MemoryUsage;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.conf.StorageUnit;
025import org.apache.hadoop.hbase.HConstants;
026import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
027import org.apache.hadoop.hbase.util.Pair;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * Util class to calculate memory size for memstore(on heap, off heap), block cache(L1, L2) of RS.
034 */
035@InterfaceAudience.Private
036public class MemorySizeUtil {
037
038  public static final String MEMSTORE_SIZE_KEY = "hbase.regionserver.global.memstore.size";
039  public static final String MEMSTORE_SIZE_OLD_KEY =
040    "hbase.regionserver.global.memstore.upperLimit";
041  public static final String MEMSTORE_SIZE_LOWER_LIMIT_KEY =
042    "hbase.regionserver.global.memstore.size.lower.limit";
043  public static final String MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY =
044    "hbase.regionserver.global.memstore.lowerLimit";
045  // Max global off heap memory that can be used for all memstores
046  // This should be an absolute value in MBs and not percent.
047  public static final String OFFHEAP_MEMSTORE_SIZE_KEY =
048    "hbase.regionserver.offheap.global.memstore.size";
049
050  public static final float DEFAULT_MEMSTORE_SIZE = 0.4f;
051  // Default lower water mark limit is 95% size of memstore size.
052  public static final float DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT = 0.95f;
053
054  /**
055   * Configuration key for the absolute amount of heap memory that must remain free for a
056   * RegionServer to start
057   */
058  public static final String HBASE_REGION_SERVER_FREE_HEAP_MIN_MEMORY_SIZE_KEY =
059    "hbase.regionserver.free.heap.min.memory.size";
060
061  private static final Logger LOG = LoggerFactory.getLogger(MemorySizeUtil.class);
062
063  private static final String JVM_HEAP_EXCEPTION = "Got an exception while attempting to read "
064    + "information about the JVM heap. Please submit this log information in a bug report and "
065    + "include your JVM settings, specifically the GC in use and any -XX options. Consider "
066    + "restarting the service.";
067
068  /**
069   * Return JVM memory statistics while properly handling runtime exceptions from the JVM.
070   * @return a memory usage object, null if there was a runtime exception. (n.b. you could also get
071   *         -1 values back from the JVM)
072   * @see MemoryUsage
073   */
074  public static MemoryUsage safeGetHeapMemoryUsage() {
075    MemoryUsage usage = null;
076    try {
077      usage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
078    } catch (RuntimeException exception) {
079      LOG.warn(JVM_HEAP_EXCEPTION, exception);
080    }
081    return usage;
082  }
083
084  /**
085   * Validates that heap allocations for MemStore and block cache do not exceed the allowed limit,
086   * ensuring enough free heap remains for other RegionServer tasks.
087   * @param conf the configuration to validate
088   * @throws RuntimeException if the combined allocation exceeds the threshold
089   */
090  public static void validateRegionServerHeapMemoryAllocation(Configuration conf) {
091    if (conf.get(MEMSTORE_SIZE_OLD_KEY) != null) {
092      LOG.warn(MEMSTORE_SIZE_OLD_KEY + " is deprecated by " + MEMSTORE_SIZE_KEY);
093    }
094    float memStoreFraction = getGlobalMemStoreHeapPercent(conf, false);
095    float blockCacheFraction = getBlockCacheHeapPercent(conf);
096    float minFreeHeapFraction = getRegionServerMinFreeHeapFraction(conf);
097
098    int memStorePercent = (int) (memStoreFraction * 100);
099    int blockCachePercent = (int) (blockCacheFraction * 100);
100    int minFreeHeapPercent = (int) (minFreeHeapFraction * 100);
101    int usedPercent = memStorePercent + blockCachePercent;
102    int maxAllowedUsed = 100 - minFreeHeapPercent;
103
104    if (usedPercent > maxAllowedUsed) {
105      throw new RuntimeException(String.format(
106        "RegionServer heap memory allocation is invalid: total memory usage exceeds 100%% "
107          + "(memStore + blockCache + requiredFreeHeap). "
108          + "Check the following configuration values:%n" + "  - %s = %.2f%n" + "  - %s = %s%n"
109          + "  - %s = %s%n" + "  - %s = %s",
110        MEMSTORE_SIZE_KEY, memStoreFraction, HConstants.HFILE_BLOCK_CACHE_MEMORY_SIZE_KEY,
111        conf.get(HConstants.HFILE_BLOCK_CACHE_MEMORY_SIZE_KEY),
112        HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, conf.get(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY),
113        HBASE_REGION_SERVER_FREE_HEAP_MIN_MEMORY_SIZE_KEY,
114        conf.get(HBASE_REGION_SERVER_FREE_HEAP_MIN_MEMORY_SIZE_KEY)));
115    }
116  }
117
118  /**
119   * Retrieve an explicit minimum required free heap size in bytes in the configuration.
120   * @param conf used to read configs
121   * @return the minimum required free heap size in bytes, or a negative value if not configured.
122   * @throws IllegalArgumentException if HBASE_REGION_SERVER_FREE_HEAP_MIN_MEMORY_SIZE_KEY format is
123   *                                  invalid
124   */
125  private static long getRegionServerMinFreeHeapInBytes(Configuration conf) {
126    final String key = HBASE_REGION_SERVER_FREE_HEAP_MIN_MEMORY_SIZE_KEY;
127    try {
128      return Long.parseLong(conf.get(key));
129    } catch (NumberFormatException e) {
130      return (long) conf.getStorageSize(key, -1, StorageUnit.BYTES);
131    }
132  }
133
134  /**
135   * Returns the minimum required free heap as a fraction of total heap.
136   */
137  public static float getRegionServerMinFreeHeapFraction(final Configuration conf) {
138    final MemoryUsage usage = safeGetHeapMemoryUsage();
139    if (usage == null) {
140      return 0;
141    }
142
143    long minFreeHeapInBytes = getRegionServerMinFreeHeapInBytes(conf);
144    if (minFreeHeapInBytes >= 0) {
145      return (float) minFreeHeapInBytes / usage.getMax();
146    }
147
148    return HConstants.HBASE_CLUSTER_MINIMUM_MEMORY_THRESHOLD;
149  }
150
151  /**
152   * Retrieve global memstore configured size as percentage of total heap.
153   */
154  public static float getGlobalMemStoreHeapPercent(final Configuration c,
155    final boolean logInvalid) {
156    float limit =
157      c.getFloat(MEMSTORE_SIZE_KEY, c.getFloat(MEMSTORE_SIZE_OLD_KEY, DEFAULT_MEMSTORE_SIZE));
158    if (limit > 0.8f || limit <= 0.0f) {
159      if (logInvalid) {
160        LOG.warn("Setting global memstore limit to default of " + DEFAULT_MEMSTORE_SIZE
161          + " because supplied value outside allowed range of (0 -> 0.8]");
162      }
163      limit = DEFAULT_MEMSTORE_SIZE;
164    }
165    return limit;
166  }
167
168  /**
169   * Retrieve configured size for global memstore lower water mark as fraction of global memstore
170   * size.
171   */
172  public static float getGlobalMemStoreHeapLowerMark(final Configuration conf,
173    boolean honorOldConfig) {
174    String lowMarkPercentStr = conf.get(MEMSTORE_SIZE_LOWER_LIMIT_KEY);
175    if (lowMarkPercentStr != null) {
176      float lowMarkPercent = Float.parseFloat(lowMarkPercentStr);
177      if (lowMarkPercent > 1.0f) {
178        LOG.error("Bad configuration value for " + MEMSTORE_SIZE_LOWER_LIMIT_KEY + ": "
179          + lowMarkPercent + ". Using 1.0f instead.");
180        lowMarkPercent = 1.0f;
181      }
182      return lowMarkPercent;
183    }
184    if (!honorOldConfig) return DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT;
185    String lowerWaterMarkOldValStr = conf.get(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY);
186    if (lowerWaterMarkOldValStr != null) {
187      LOG.warn(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " is deprecated. Instead use "
188        + MEMSTORE_SIZE_LOWER_LIMIT_KEY);
189      float lowerWaterMarkOldVal = Float.parseFloat(lowerWaterMarkOldValStr);
190      float upperMarkPercent = getGlobalMemStoreHeapPercent(conf, false);
191      if (lowerWaterMarkOldVal > upperMarkPercent) {
192        lowerWaterMarkOldVal = upperMarkPercent;
193        LOG.error("Value of " + MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " (" + lowerWaterMarkOldVal
194          + ") is greater than global memstore limit (" + upperMarkPercent + ") set by "
195          + MEMSTORE_SIZE_KEY + "/" + MEMSTORE_SIZE_OLD_KEY + ". Setting memstore lower limit "
196          + "to " + upperMarkPercent);
197      }
198      return lowerWaterMarkOldVal / upperMarkPercent;
199    }
200    return DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT;
201  }
202
203  /** Returns Pair of global memstore size and memory type(ie. on heap or off heap). */
204  public static Pair<Long, MemoryType> getGlobalMemStoreSize(Configuration conf) {
205    long offheapMSGlobal = conf.getLong(OFFHEAP_MEMSTORE_SIZE_KEY, 0);// Size in MBs
206    if (offheapMSGlobal > 0) {
207      // Off heap memstore size has not relevance when MSLAB is turned OFF. We will go with making
208      // this entire size split into Chunks and pooling them in MemstoreLABPoool. We dont want to
209      // create so many on demand off heap chunks. In fact when this off heap size is configured, we
210      // will go with 100% of this size as the pool size
211      if (MemStoreLAB.isEnabled(conf)) {
212        // We are in offheap Memstore use
213        long globalMemStoreLimit = (long) (offheapMSGlobal * 1024 * 1024); // Size in bytes
214        return new Pair<>(globalMemStoreLimit, MemoryType.NON_HEAP);
215      } else {
216        // Off heap max memstore size is configured with turning off MSLAB. It makes no sense. Do a
217        // warn log and go with on heap memstore percentage. By default it will be 40% of Xmx
218        LOG.warn("There is no relevance of configuring '" + OFFHEAP_MEMSTORE_SIZE_KEY + "' when '"
219          + MemStoreLAB.USEMSLAB_KEY + "' is turned off."
220          + " Going with on heap global memstore size ('" + MEMSTORE_SIZE_KEY + "')");
221      }
222    }
223    return new Pair<>(getOnheapGlobalMemStoreSize(conf), MemoryType.HEAP);
224  }
225
226  /**
227   * Returns the onheap global memstore limit based on the config
228   * 'hbase.regionserver.global.memstore.size'.
229   * @return the onheap global memstore limt
230   */
231  public static long getOnheapGlobalMemStoreSize(Configuration conf) {
232    long max = -1L;
233    final MemoryUsage usage = safeGetHeapMemoryUsage();
234    if (usage != null) {
235      max = usage.getMax();
236    }
237    float globalMemStorePercent = getGlobalMemStoreHeapPercent(conf, true);
238    return ((long) (max * globalMemStorePercent));
239  }
240
241  /**
242   * Retrieve configured size for on heap block cache as percentage of total heap.
243   */
244  public static float getBlockCacheHeapPercent(final Configuration conf) {
245    // Check if an explicit block cache size is configured.
246    long l1CacheSizeInBytes = getBlockCacheSizeInBytes(conf);
247    if (l1CacheSizeInBytes > 0) {
248      final MemoryUsage usage = safeGetHeapMemoryUsage();
249      return usage == null ? 0 : (float) l1CacheSizeInBytes / usage.getMax();
250    }
251
252    return conf.getFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY,
253      HConstants.HFILE_BLOCK_CACHE_SIZE_DEFAULT);
254  }
255
256  /**
257   * Retrieve an explicit block cache size in bytes in the configuration.
258   * @param conf used to read cache configs
259   * @return the number of bytes to use for LRU, negative if disabled.
260   * @throws IllegalArgumentException if HFILE_BLOCK_CACHE_MEMORY_SIZE_KEY format is invalid
261   */
262  public static long getBlockCacheSizeInBytes(Configuration conf) {
263    final String key = HConstants.HFILE_BLOCK_CACHE_MEMORY_SIZE_KEY;
264    try {
265      return Long.parseLong(conf.get(key));
266    } catch (NumberFormatException e) {
267      return (long) conf.getStorageSize(key, -1, StorageUnit.BYTES);
268    }
269  }
270
271  /**
272   * @param conf used to read cache configs
273   * @return the number of bytes to use for LRU, negative if disabled.
274   * @throws IllegalArgumentException if HFILE_BLOCK_CACHE_SIZE_KEY is > 1.0
275   */
276  public static long getOnHeapCacheSize(final Configuration conf) {
277    final float cachePercentage = getBlockCacheHeapPercent(conf);
278    if (cachePercentage <= 0.0001f) {
279      return -1;
280    }
281    if (cachePercentage > 1.0) {
282      throw new IllegalArgumentException(
283        HConstants.HFILE_BLOCK_CACHE_SIZE_KEY + " must be between 0.0 and 1.0, and not > 1.0");
284    }
285
286    final MemoryUsage usage = safeGetHeapMemoryUsage();
287    if (usage == null) {
288      return -1;
289    }
290    final long heapMax = usage.getMax();
291    float onHeapCacheFixedSize =
292      (float) conf.getLong(HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_KEY,
293        HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_DEFAULT) / heapMax;
294    // Calculate the amount of heap to give the heap.
295    if (onHeapCacheFixedSize > 0 && onHeapCacheFixedSize < cachePercentage) {
296      return (long) (heapMax * onHeapCacheFixedSize);
297    } else {
298      final long cacheSizeInBytes = getBlockCacheSizeInBytes(conf);
299      return cacheSizeInBytes > 0 ? cacheSizeInBytes : (long) (heapMax * cachePercentage);
300    }
301  }
302
303  /**
304   * @param conf used to read config for bucket cache size.
305   * @return the number of bytes to use for bucket cache, negative if disabled.
306   */
307  public static long getBucketCacheSize(final Configuration conf) {
308    // Size configured in MBs
309    float bucketCacheSize = conf.getFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 0F);
310    if (bucketCacheSize < 1) {
311      throw new IllegalArgumentException("Bucket Cache should be minimum 1 MB in size."
312        + "Configure 'hbase.bucketcache.size' with > 1 value");
313    }
314    return (long) (bucketCacheSize * 1024 * 1024);
315  }
316}