001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2010-2015 ForgeRock AS. 026 */ 027package org.opends.server.backends.jeb; 028 029import static com.sleepycat.je.EnvironmentConfig.*; 030 031import static org.opends.messages.BackendMessages.*; 032import static org.opends.messages.ConfigMessages.*; 033 034import java.lang.reflect.Method; 035import java.math.BigInteger; 036import java.util.Arrays; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.List; 040import java.util.Map; 041import java.util.SortedSet; 042import java.util.StringTokenizer; 043import java.util.concurrent.TimeUnit; 044import java.util.logging.Level; 045import java.util.logging.Logger; 046 047import org.forgerock.i18n.LocalizableMessage; 048import org.forgerock.i18n.slf4j.LocalizedLogger; 049import org.forgerock.opendj.config.server.ConfigException; 050import org.forgerock.opendj.ldap.ByteString; 051import org.opends.server.admin.BooleanPropertyDefinition; 052import org.opends.server.admin.DurationPropertyDefinition; 053import org.opends.server.admin.PropertyDefinition; 054import org.opends.server.admin.std.meta.JEBackendCfgDefn; 055import org.opends.server.admin.std.server.BackendCfg; 056import org.opends.server.admin.std.server.JEBackendCfg; 057import org.opends.server.config.ConfigConstants; 058import org.opends.server.core.DirectoryServer; 059import org.opends.server.core.MemoryQuota; 060import org.opends.server.types.DN; 061 062import com.sleepycat.je.Durability; 063import com.sleepycat.je.EnvironmentConfig; 064import com.sleepycat.je.dbi.MemoryBudget; 065import org.opends.server.util.Platform; 066 067/** This class maps JE properties to configuration attributes. */ 068public class ConfigurableEnvironment 069{ 070 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 071 072 /** 073 * The name of the attribute which configures the database cache size as a 074 * percentage of Java VM heap size. 075 */ 076 public static final String ATTR_DATABASE_CACHE_PERCENT = 077 ConfigConstants.NAME_PREFIX_CFG + "db-cache-percent"; 078 079 /** 080 * The name of the attribute which configures the database cache size as an 081 * approximate number of bytes. 082 */ 083 public static final String ATTR_DATABASE_CACHE_SIZE = 084 ConfigConstants.NAME_PREFIX_CFG + "db-cache-size"; 085 086 /** 087 * The name of the attribute which configures whether data updated by a 088 * database transaction is forced to disk. 089 */ 090 public static final String ATTR_DATABASE_TXN_NO_SYNC = 091 ConfigConstants.NAME_PREFIX_CFG + "db-txn-no-sync"; 092 093 /** 094 * The name of the attribute which configures whether data updated by a 095 * database transaction is written from the Java VM to the O/S. 096 */ 097 public static final String ATTR_DATABASE_TXN_WRITE_NO_SYNC = 098 ConfigConstants.NAME_PREFIX_CFG + "db-txn-write-no-sync"; 099 100 /** 101 * The name of the attribute which configures whether the database background 102 * cleaner thread runs. 103 */ 104 public static final String ATTR_DATABASE_RUN_CLEANER = 105 ConfigConstants.NAME_PREFIX_CFG + "db-run-cleaner"; 106 107 /** 108 * The name of the attribute which configures the minimum percentage of log 109 * space that must be used in log files. 110 */ 111 public static final String ATTR_CLEANER_MIN_UTILIZATION = 112 ConfigConstants.NAME_PREFIX_CFG + "db-cleaner-min-utilization"; 113 114 /** 115 * The name of the attribute which configures the maximum size of each 116 * individual JE log file, in bytes. 117 */ 118 public static final String ATTR_DATABASE_LOG_FILE_MAX = 119 ConfigConstants.NAME_PREFIX_CFG + "db-log-file-max"; 120 121 /** The name of the attribute which configures the database cache eviction algorithm. */ 122 public static final String ATTR_EVICTOR_LRU_ONLY = 123 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-lru-only"; 124 125 /** 126 * The name of the attribute which configures the number of nodes in one scan 127 * of the database cache evictor. 128 */ 129 public static final String ATTR_EVICTOR_NODES_PER_SCAN = 130 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-nodes-per-scan"; 131 132 /** 133 * The name of the attribute which configures the minimum number of threads 134 * of the database cache evictor pool. 135 */ 136 public static final String ATTR_EVICTOR_CORE_THREADS = 137 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-core-threads"; 138 /** 139 * The name of the attribute which configures the maximum number of threads 140 * of the database cache evictor pool. 141 */ 142 public static final String ATTR_EVICTOR_MAX_THREADS = 143 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-max-threads"; 144 145 /** 146 * The name of the attribute which configures the time excess threads 147 * of the database cache evictor pool are kept alive. 148 */ 149 public static final String ATTR_EVICTOR_KEEP_ALIVE = 150 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-keep-alive"; 151 152 /** 153 * The name of the attribute which configures whether the logging file 154 * handler will be on or off. 155 */ 156 public static final String ATTR_LOGGING_FILE_HANDLER_ON = 157 ConfigConstants.NAME_PREFIX_CFG + "db-logging-file-handler-on"; 158 159 /** The name of the attribute which configures the trace logging message level. */ 160 public static final String ATTR_LOGGING_LEVEL = 161 ConfigConstants.NAME_PREFIX_CFG + "db-logging-level"; 162 163 /** 164 * The name of the attribute which configures how many bytes are written to 165 * the log before the checkpointer runs. 166 */ 167 public static final String ATTR_CHECKPOINTER_BYTES_INTERVAL = 168 ConfigConstants.NAME_PREFIX_CFG + "db-checkpointer-bytes-interval"; 169 170 /** 171 * The name of the attribute which configures the amount of time between 172 * runs of the checkpointer. 173 */ 174 public static final String ATTR_CHECKPOINTER_WAKEUP_INTERVAL = 175 ConfigConstants.NAME_PREFIX_CFG + 176 "db-checkpointer-wakeup-interval"; 177 178 /** The name of the attribute which configures the number of lock tables. */ 179 public static final String ATTR_NUM_LOCK_TABLES = 180 ConfigConstants.NAME_PREFIX_CFG + "db-num-lock-tables"; 181 182 /** 183 * The name of the attribute which configures the number threads 184 * allocated by the cleaner for log file processing. 185 */ 186 public static final String ATTR_NUM_CLEANER_THREADS = 187 ConfigConstants.NAME_PREFIX_CFG + "db-num-cleaner-threads"; 188 189 /** The name of the attribute which configures the size of the file handle cache. */ 190 public static final String ATTR_LOG_FILECACHE_SIZE = 191 ConfigConstants.NAME_PREFIX_CFG + "db-log-filecache-size"; 192 193 /** The name of the attribute which may specify any native JE properties. */ 194 public static final String ATTR_JE_PROPERTY = 195 ConfigConstants.NAME_PREFIX_CFG + "je-property"; 196 197 /** A map of JE property names to the corresponding configuration attribute. */ 198 private static HashMap<String, String> attrMap = new HashMap<>(); 199 200 /** 201 * A map of configuration attribute names to the corresponding configuration object getter method. 202 */ 203 private static Map<String, Method> jebMethodMap = new HashMap<>(); 204 /** A map of configuration attribute names to the corresponding configuration PropertyDefinition. */ 205 private static Map<String, PropertyDefinition<?>> jebDefnMap = new HashMap<>(); 206 207 /** Pulled from resource/admin/ABBREVIATIONS.xsl. db is mose common. */ 208 private static final List<String> ABBREVIATIONS = Arrays.asList(new String[] 209 {"aci", "ip", "ssl", "dn", "rdn", "jmx", "smtp", "http", 210 "https", "ldap", "ldaps", "ldif", "jdbc", "tcp", "tls", 211 "pkcs11", "sasl", "gssapi", "md5", "je", "dse", "fifo", 212 "vlv", "uuid", "md5", "sha1", "sha256", "sha384", "sha512", 213 "tls", "db"}); 214 215 /** E.g. db-cache-percent -> DBCachePercent */ 216 private static String propNametoCamlCase(String hyphenated) 217 { 218 String[] components = hyphenated.split("\\-"); 219 StringBuilder buffer = new StringBuilder(); 220 for (String component: components) { 221 if (ABBREVIATIONS.contains(component)) { 222 buffer.append(component.toUpperCase()); 223 } else { 224 buffer.append(component.substring(0, 1).toUpperCase()).append(component.substring(1)); 225 } 226 } 227 return buffer.toString(); 228 } 229 230 /** 231 * Register a JE property and its corresponding configuration attribute. 232 * 233 * @param propertyName The name of the JE property to be registered. 234 * @param attrName The name of the configuration attribute associated 235 * with the property. 236 * @throws Exception If there is an error in the attribute name. 237 */ 238 private static void registerProp(String propertyName, String attrName) 239 throws Exception 240 { 241 // Strip off NAME_PREFIX_CFG. 242 String baseName = attrName.substring(7); 243 String methodBaseName = propNametoCamlCase(baseName); 244 245 registerJebProp(attrName, methodBaseName); 246 attrMap.put(propertyName, attrName); 247 } 248 249 private static void registerJebProp(String attrName, String methodBaseName) throws Exception 250 { 251 Class<JEBackendCfg> configClass = JEBackendCfg.class; 252 JEBackendCfgDefn defn = JEBackendCfgDefn.getInstance(); 253 Class<? extends JEBackendCfgDefn> defClass = defn.getClass(); 254 255 String propName = "get" + methodBaseName + "PropertyDefinition"; 256 PropertyDefinition<?> propDefn = (PropertyDefinition<?>) defClass.getMethod(propName).invoke(defn); 257 258 String methodPrefix = propDefn instanceof BooleanPropertyDefinition ? "is" : "get"; 259 String methodName = methodPrefix + methodBaseName; 260 261 jebDefnMap.put(attrName, propDefn); 262 jebMethodMap.put(attrName, configClass.getMethod(methodName)); 263 } 264 265 /** 266 * Get the name of the configuration attribute associated with a JE property. 267 * @param jeProperty The name of the JE property. 268 * @return The name of the associated configuration attribute. 269 */ 270 public static String getAttributeForProperty(String jeProperty) 271 { 272 return attrMap.get(jeProperty); 273 } 274 275 /** 276 * Get the value of a JE property that is mapped to a configuration attribute. 277 * @param cfg The configuration containing the property values. 278 * @param attrName The configuration attribute type name. 279 * @return The string value of the JE property. 280 */ 281 private static String getPropertyValue(BackendCfg cfg, String attrName, ByteString backendId) 282 { 283 try 284 { 285 PropertyDefinition<?> propDefn = jebDefnMap.get(attrName); 286 Method method = jebMethodMap.get(attrName); 287 288 if (propDefn instanceof DurationPropertyDefinition) 289 { 290 Long value = (Long)method.invoke(cfg); 291 292 // JE durations are in microseconds so we must convert. 293 DurationPropertyDefinition durationPropDefn = 294 (DurationPropertyDefinition)propDefn; 295 value = 1000*durationPropDefn.getBaseUnit().toMilliSeconds(value); 296 297 return String.valueOf(value); 298 } 299 else 300 { 301 Object value = method.invoke(cfg); 302 303 if (value != null) 304 { 305 return String.valueOf(value); 306 } 307 308 if (attrName.equals(ATTR_NUM_CLEANER_THREADS)) 309 { 310 // Automatically choose based on the number of processors. We will use 311 // similar heuristics to those used to define the default number of 312 // worker threads. 313 value = Platform.computeNumberOfThreads(8, 1.0f); 314 315 logger.debug(INFO_ERGONOMIC_SIZING_OF_JE_CLEANER_THREADS, 316 backendId, (Number) value); 317 } 318 else if (attrName.equals(ATTR_NUM_LOCK_TABLES)) 319 { 320 // Automatically choose based on the number of processors. We'll assume that the user has also allowed 321 // automatic configuration of cleaners and workers. 322 BigInteger tmp = BigInteger.valueOf(Platform.computeNumberOfThreads(1, 2)); 323 value = tmp.nextProbablePrime(); 324 325 logger.debug(INFO_ERGONOMIC_SIZING_OF_JE_LOCK_TABLES, backendId, (Number) value); 326 } 327 328 return String.valueOf(value); 329 } 330 } 331 catch (Exception e) 332 { 333 logger.traceException(e); 334 return ""; 335 } 336 } 337 338 static 339 { 340 // Register the parameters that have JE property names. 341 try 342 { 343 registerProp("je.maxMemoryPercent", ATTR_DATABASE_CACHE_PERCENT); 344 registerProp("je.maxMemory", ATTR_DATABASE_CACHE_SIZE); 345 registerProp("je.cleaner.minUtilization", ATTR_CLEANER_MIN_UTILIZATION); 346 registerProp("je.env.runCleaner", ATTR_DATABASE_RUN_CLEANER); 347 registerProp("je.evictor.lruOnly", ATTR_EVICTOR_LRU_ONLY); 348 registerProp("je.evictor.nodesPerScan", ATTR_EVICTOR_NODES_PER_SCAN); 349 registerProp("je.evictor.coreThreads", ATTR_EVICTOR_CORE_THREADS); 350 registerProp("je.evictor.maxThreads", ATTR_EVICTOR_MAX_THREADS); 351 registerProp("je.evictor.keepAlive", ATTR_EVICTOR_KEEP_ALIVE); 352 registerProp("je.log.fileMax", ATTR_DATABASE_LOG_FILE_MAX); 353 registerProp("je.checkpointer.bytesInterval", 354 ATTR_CHECKPOINTER_BYTES_INTERVAL); 355 registerProp("je.checkpointer.wakeupInterval", 356 ATTR_CHECKPOINTER_WAKEUP_INTERVAL); 357 registerProp("je.lock.nLockTables", ATTR_NUM_LOCK_TABLES); 358 registerProp("je.cleaner.threads", ATTR_NUM_CLEANER_THREADS); 359 registerProp("je.log.fileCacheSize", ATTR_LOG_FILECACHE_SIZE); 360 } 361 catch (Exception e) 362 { 363 logger.traceException(e); 364 } 365 } 366 367 /** 368 * Create a JE environment configuration with default values. 369 * 370 * @return A JE environment config containing default values. 371 */ 372 public static EnvironmentConfig defaultConfig() 373 { 374 EnvironmentConfig envConfig = new EnvironmentConfig(); 375 376 envConfig.setTransactional(true); 377 envConfig.setAllowCreate(true); 378 379 // "je.env.sharedLatches" is "true" by default since JE #12136 (3.3.62?) 380 381 // This parameter was set to false while diagnosing a Berkeley DB JE bug. 382 // Normally cleansed log files are deleted, but if this is set false 383 // they are instead renamed from .jdb to .del. 384 envConfig.setConfigParam(CLEANER_EXPUNGE, "true"); 385 386 // Under heavy write load the check point can fall behind causing 387 // uncontrolled DB growth over time. This parameter makes the out of 388 // the box configuration more robust at the cost of a slight 389 // reduction in maximum write throughput. Experiments have shown 390 // that response time predictability is not impacted negatively. 391 envConfig.setConfigParam(CHECKPOINTER_HIGH_PRIORITY, "true"); 392 393 // If the JVM is reasonably large then we can safely default to 394 // bigger read buffers. This will result in more scalable checkpointer 395 // and cleaner performance. 396 if (Runtime.getRuntime().maxMemory() > 256 * 1024 * 1024) 397 { 398 envConfig.setConfigParam(CLEANER_LOOK_AHEAD_CACHE_SIZE, 399 String.valueOf(2 * 1024 * 1024)); 400 envConfig.setConfigParam(LOG_ITERATOR_READ_SIZE, 401 String.valueOf(2 * 1024 * 1024)); 402 envConfig.setConfigParam(LOG_FAULT_READ_SIZE, String.valueOf(4 * 1024)); 403 } 404 405 // Disable lock timeouts, meaning that no lock wait 406 // timelimit is enforced and a deadlocked operation 407 // will block indefinitely. 408 envConfig.setLockTimeout(0, TimeUnit.MICROSECONDS); 409 410 return envConfig; 411 } 412 413 /** 414 * Parse a configuration associated with a JE environment and create an 415 * environment config from it. 416 * 417 * @param cfg The configuration to be parsed. 418 * @return An environment config instance corresponding to the config entry. 419 * @throws ConfigException If there is an error in the provided configuration 420 * entry. 421 */ 422 public static EnvironmentConfig parseConfigEntry(JEBackendCfg cfg) throws ConfigException 423 { 424 validateDbCacheSize(cfg.getDBCacheSize()); 425 426 EnvironmentConfig envConfig = defaultConfig(); 427 setDurability(envConfig, cfg.isDBTxnNoSync(), cfg.isDBTxnWriteNoSync()); 428 setJEProperties(cfg, envConfig, cfg.dn().rdn().getAttributeValue(0)); 429 setDBLoggingLevel(envConfig, cfg.getDBLoggingLevel(), cfg.dn(), cfg.isDBLoggingFileHandlerOn()); 430 431 // See if there are any native JE properties specified in the config 432 // and if so try to parse, evaluate and set them. 433 return setJEProperties(envConfig, cfg.getJEProperty(), attrMap); 434 } 435 436 private static void validateDbCacheSize(long dbCacheSize) throws ConfigException 437 { 438 if (dbCacheSize != 0) 439 { 440 if (MemoryBudget.getRuntimeMaxMemory() < dbCacheSize) 441 { 442 throw new ConfigException(ERR_BACKEND_CONFIG_CACHE_SIZE_GREATER_THAN_JVM_HEAP.get( 443 dbCacheSize, MemoryBudget.getRuntimeMaxMemory())); 444 } 445 if (dbCacheSize < MemoryBudget.MIN_MAX_MEMORY_SIZE) 446 { 447 throw new ConfigException(ERR_CONFIG_JEB_CACHE_SIZE_TOO_SMALL.get( 448 dbCacheSize, MemoryBudget.MIN_MAX_MEMORY_SIZE)); 449 } 450 MemoryQuota memoryQuota = DirectoryServer.getInstance().getServerContext().getMemoryQuota(); 451 if (!memoryQuota.acquireMemory(dbCacheSize)) 452 { 453 logger.warn(ERR_BACKEND_CONFIG_CACHE_SIZE_GREATER_THAN_JVM_HEAP.get( 454 dbCacheSize, memoryQuota.getMaxMemory())); 455 } 456 } 457 } 458 459 private static void setDurability(EnvironmentConfig envConfig, boolean dbTxnNoSync, boolean dbTxnWriteNoSync) 460 throws ConfigException 461 { 462 if (dbTxnNoSync && dbTxnWriteNoSync) 463 { 464 throw new ConfigException(ERR_CONFIG_JEB_DURABILITY_CONFLICT.get()); 465 } 466 if (dbTxnNoSync) 467 { 468 envConfig.setDurability(Durability.COMMIT_NO_SYNC); 469 } 470 else if (dbTxnWriteNoSync) 471 { 472 envConfig.setDurability(Durability.COMMIT_WRITE_NO_SYNC); 473 } 474 } 475 476 private static void setJEProperties(BackendCfg cfg, EnvironmentConfig envConfig, ByteString backendId) 477 { 478 for (Map.Entry<String, String> mapEntry : attrMap.entrySet()) 479 { 480 String jeProperty = mapEntry.getKey(); 481 String attrName = mapEntry.getValue(); 482 483 String value = getPropertyValue(cfg, attrName, backendId); 484 envConfig.setConfigParam(jeProperty, value); 485 } 486 } 487 488 private static void setDBLoggingLevel(EnvironmentConfig envConfig, String loggingLevel, DN dn, 489 boolean loggingFileHandlerOn) throws ConfigException 490 { 491 Logger parent = Logger.getLogger("com.sleepycat.je"); 492 try 493 { 494 parent.setLevel(Level.parse(loggingLevel)); 495 } 496 catch (Exception e) 497 { 498 throw new ConfigException(ERR_JEB_INVALID_LOGGING_LEVEL.get(loggingLevel, dn)); 499 } 500 501 final Level level = loggingFileHandlerOn ? Level.ALL : Level.OFF; 502 envConfig.setConfigParam(FILE_LOGGING_LEVEL, level.getName()); 503 } 504 505 /** 506 * Parse, validate and set native JE environment properties for 507 * a given environment config. 508 * 509 * @param envConfig The JE environment config for which to set 510 * the properties. 511 * @param jeProperties The JE environment properties to parse, 512 * validate and set. 513 * @param configAttrMap Component supported JE properties to 514 * their configuration attributes map. 515 * @return An environment config instance with given properties 516 * set. 517 * @throws ConfigException If there is an error while parsing, 518 * validating and setting any of the properties provided. 519 */ 520 public static EnvironmentConfig setJEProperties(EnvironmentConfig envConfig, 521 SortedSet<String> jeProperties, HashMap<String, String> configAttrMap) 522 throws ConfigException 523 { 524 if (jeProperties.isEmpty()) { 525 // return default config. 526 return envConfig; 527 } 528 529 // Set to catch duplicate properties. 530 HashSet<String> uniqueJEProperties = new HashSet<>(); 531 532 // Iterate through the config values associated with a JE property. 533 for (String jeEntry : jeProperties) 534 { 535 StringTokenizer st = new StringTokenizer(jeEntry, "="); 536 if (st.countTokens() != 2) 537 { 538 throw new ConfigException(ERR_CONFIG_JE_PROPERTY_INVALID_FORM.get(jeEntry)); 539 } 540 541 String jePropertyName = st.nextToken(); 542 String jePropertyValue = st.nextToken(); 543 // Check if it is a duplicate. 544 if (uniqueJEProperties.contains(jePropertyName)) { 545 throw new ConfigException(ERR_CONFIG_JE_DUPLICATE_PROPERTY.get(jePropertyName)); 546 } 547 548 // Set JE property. 549 try { 550 envConfig.setConfigParam(jePropertyName, jePropertyValue); 551 // If this property shadows an existing config attribute. 552 if (configAttrMap.containsKey(jePropertyName)) { 553 LocalizableMessage message = ERR_CONFIG_JE_PROPERTY_SHADOWS_CONFIG.get( 554 jePropertyName, attrMap.get(jePropertyName)); 555 throw new ConfigException(message); 556 } 557 // Add this property to unique set. 558 uniqueJEProperties.add(jePropertyName); 559 } catch(IllegalArgumentException e) { 560 logger.traceException(e); 561 LocalizableMessage message = ERR_CONFIG_JE_PROPERTY_INVALID.get(jeEntry, e.getMessage()); 562 throw new ConfigException(message, e.getCause()); 563 } 564 } 565 566 return envConfig; 567 } 568}