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