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}