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}