blob: 259e55a219d548ca317f3a22edaa00b7385fd897 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.jackrabbit.oak.segment;
import static org.apache.jackrabbit.guava.common.base.Strings.isNullOrEmpty;
import static org.apache.jackrabbit.oak.commons.IOUtils.closeQuietly;
import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_STRING_CACHE_MB;
import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_TEMPLATE_CACHE_MB;
import static org.apache.jackrabbit.oak.segment.SegmentCache.DEFAULT_SEGMENT_CACHE_MB;
import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_NODE_CACHE_SIZE_OSGi;
import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_STRING_CACHE_SIZE_OSGi;
import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_TEMPLATE_CACHE_SIZE_OSGi;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.DISABLE_ESTIMATION_DEFAULT;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.FORCE_TIMEOUT_DEFAULT;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GC_PROGRESS_LOG_DEFAULT;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.MEMORY_THRESHOLD_DEFAULT;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.PAUSE_DEFAULT;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.RETAINED_GENERATIONS_DEFAULT;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.RETRY_COUNT_DEFAULT;
import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.SIZE_DELTA_ESTIMATION_DEFAULT;
import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.DEFAULT_MAX_FILE_SIZE;
import static org.apache.jackrabbit.oak.spi.blob.osgi.SplitBlobStoreService.ONLY_STANDALONE_TARGET;
import org.apache.jackrabbit.guava.common.io.Closer;
import org.apache.jackrabbit.oak.osgi.OsgiUtil;
import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentNodeStorePersistence;
import org.apache.jackrabbit.oak.segment.spi.persistence.persistentcache.PersistentCache;
import org.apache.jackrabbit.oak.spi.blob.BlobStore;
import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
import org.apache.jackrabbit.oak.stats.StatisticsProvider;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
/**
* An OSGi wrapper for the segment node store.
*/
@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
@Designate(ocd = SegmentNodeStoreService.Configuration.class)
public class SegmentNodeStoreService {
private static final Logger log = LoggerFactory.getLogger(SegmentNodeStoreService.class);
// TODO(frm) This is only exposed to tests. Should it be removed?
public static final String CUSTOM_BLOB_STORE = "customBlobStore";
// TODO(frm) This is only exposed to tests. Should it be removed?
public static final String REPOSITORY_HOME_DIRECTORY = "repository.home";
private static final long DEFAULT_BLOB_SNAPSHOT_INTERVAL = 12 * 60 * 60;
private static final long DEFAULT_BLOB_GC_MAX_AGE = 24 * 60 * 60;
@ObjectClassDefinition(
name = "Oak Segment Tar NodeStore service",
description = "Apache Jackrabbit Oak NodeStore implementation based on " +
"the segment model. For configuration refer to http://jackrabbit.apache.org/oak/docs/osgi_config.html#SegmentNodeStore. " +
"Note that for system stability purpose it is advisable to not " +
"change these settings at runtime. Instead the config change should " +
"be done via file system based config file and this view should ONLY " +
"be used to determine which options are supported."
)
@interface Configuration {
@AttributeDefinition(
name = "Repository Home Directory",
description = "Path on the file system where repository data will be stored. "
+ "Defaults to the value of the framework property 'repository.home' or to 'repository' "
+ "if that is neither specified."
)
String repository_home() default "";
@AttributeDefinition(
name = "Mode",
description = "TarMK mode (64 for memory mapped file access, 32 for normal file access). " +
"Default value is taken from the 'sun.arch.data.model' system property."
)
String tarmk_mode() default "";
@AttributeDefinition(
name = "Maximum tar file size (MB)",
description = "The maximum size of the tar files in megabytes. " +
"Default value is '" + DEFAULT_MAX_FILE_SIZE + "'."
)
int tarmk_size() default DEFAULT_MAX_FILE_SIZE;
@AttributeDefinition(
name = "Segment cache size (MB)",
description = "Cache size for storing most recently used segments in megabytes. " +
"Default value is '" + DEFAULT_SEGMENT_CACHE_MB + "'."
)
int segmentCache_size() default DEFAULT_SEGMENT_CACHE_MB;
@AttributeDefinition(
name = "String cache size (MB)",
description = "Cache size for storing most recently used strings in megabytes. " +
"Default value is '" + DEFAULT_STRING_CACHE_MB + "'."
)
int stringCache_size() default DEFAULT_STRING_CACHE_MB;
@AttributeDefinition(
name = "Template cache size (MB)",
description = "Cache size for storing most recently used templates in megabytes. " +
"Default value is '" + DEFAULT_TEMPLATE_CACHE_MB + "'."
)
int templateCache_size() default DEFAULT_TEMPLATE_CACHE_MB;
@AttributeDefinition(
name = "String deduplication cache size (#items)",
description = "Maximum number of strings to keep in the deduplication cache. " +
"Default value is '" + DEFAULT_STRING_CACHE_SIZE_OSGi + "'."
)
int stringDeduplicationCache_size() default DEFAULT_STRING_CACHE_SIZE_OSGi;
@AttributeDefinition(
name = "Template deduplication cache size (#items)",
description = "Maximum number of templates to keep in the deduplication cache. " +
"Default value is '" + DEFAULT_TEMPLATE_CACHE_SIZE_OSGi + "'."
)
int templateDeduplicationCache_size() default DEFAULT_TEMPLATE_CACHE_SIZE_OSGi;
@AttributeDefinition(
name = "Node deduplication cache size (#items)",
description = "Maximum number of node to keep in the deduplication cache. If the supplied " +
"value is not a power of 2, it will be rounded up to the next power of 2. " +
"Default value is '" + DEFAULT_NODE_CACHE_SIZE_OSGi + "'."
)
int nodeDeduplicationCache_size() default DEFAULT_NODE_CACHE_SIZE_OSGi;
@AttributeDefinition(
name = "Pause compaction",
description = "When set to true the compaction phase is skipped during garbage collection. " +
"Default value is '" + PAUSE_DEFAULT + "'."
)
boolean pauseCompaction() default PAUSE_DEFAULT;
@AttributeDefinition(
name = "Compaction retries",
description = "Number of tries to compact concurrent commits on top of already " +
"compacted commits. " +
"Default value is '" + RETRY_COUNT_DEFAULT + "'."
)
int compaction_retryCount() default RETRY_COUNT_DEFAULT;
@AttributeDefinition(
name = "Force compaction timeout",
description = "Number of seconds to attempt to force compact concurrent commits on top " +
"of already compacted commits after the maximum number of retries has been " +
"reached. Forced compaction tries to acquire an exclusive write lock on the " +
"node store, blocking concurrent write access as long as the lock is held. " +
"Default value is '" + FORCE_TIMEOUT_DEFAULT + "'."
)
int compaction_force_timeout() default FORCE_TIMEOUT_DEFAULT;
@AttributeDefinition(
name = "Garbage collection repository size threshold",
description = "Garbage collection will be skipped unless the repository grew at least by " +
"the number of bytes specified. " +
"Default value is '" + SIZE_DELTA_ESTIMATION_DEFAULT + "'."
)
long compaction_sizeDeltaEstimation() default SIZE_DELTA_ESTIMATION_DEFAULT;
@AttributeDefinition(
name = "Disable estimation phase",
description = "Disables the estimation phase allowing garbage collection to run unconditionally. " +
"Default value is '" + DISABLE_ESTIMATION_DEFAULT + "'."
)
boolean compaction_disableEstimation() default DISABLE_ESTIMATION_DEFAULT;
@AttributeDefinition(
name = "Compaction retained generations",
description = "Number of segment generations to retain during garbage collection. " +
"The number of generations defaults to " + RETAINED_GENERATIONS_DEFAULT + " and " +
"can't be changed. This configuration option is considered deprecated " +
"and will be removed in the future."
)
int compaction_retainedGenerations() default RETAINED_GENERATIONS_DEFAULT;
@AttributeDefinition(
name = "Compaction memory threshold",
description = "Threshold of available heap memory in percent of total heap memory below " +
"which the compaction phase is canceled. 0 disables heap memory monitoring. " +
"Default value is '" + MEMORY_THRESHOLD_DEFAULT + "'."
)
int compaction_memoryThreshold() default MEMORY_THRESHOLD_DEFAULT;
@AttributeDefinition(
name = "Compaction progress log",
description = "The number of nodes compacted after which a status message is logged. " +
"-1 disables progress logging. " +
"Default value is '" + GC_PROGRESS_LOG_DEFAULT + "'."
)
long compaction_progressLog() default GC_PROGRESS_LOG_DEFAULT;
@AttributeDefinition(
name = "Standby mode",
description = "Flag indicating this component will not register as a NodeStore but as a " +
"NodeStoreProvider instead. " +
"Default value is 'false'."
)
boolean standby() default false;
@AttributeDefinition(
name = "Custom blob store",
description = "Boolean value indicating that a custom BlobStore is used for storing " +
"large binary values."
)
boolean customBlobStore() default false;
@AttributeDefinition(
name = "Custom segment store",
description = "Boolean value indicating that a custom (non-tar) segment store is used"
)
boolean customSegmentStore() default false;
@AttributeDefinition(
name = "Split persistence",
description = "Boolean value indicating that the writes should be done locally when using the custom segment store"
)
boolean splitPersistence() default false;
@AttributeDefinition(
name = "Cache persistence",
description = "Boolean value indicating that the persisted cache should be used for the custom segment store"
)
boolean cachePersistence() default false;
@AttributeDefinition(
name = "Backup directory",
description = "Directory (relative to current working directory) for storing repository backups. " +
"Defaults to 'repository.home/segmentstore-backup'."
)
String repository_backup_dir() default "";
@AttributeDefinition(
name = "Blob gc max age (in secs)",
description = "The blob garbage collection logic will only consider those blobs which " +
"are not accessed recently (currentTime - lastModifiedTime > blobGcMaxAgeInSecs). " +
"For example with the default setting only those blobs which have been created " +
"at least 24 hours ago will be considered for garbage collection. " +
"Default value is '" + DEFAULT_BLOB_GC_MAX_AGE + "'."
)
long blobGcMaxAgeInSecs() default DEFAULT_BLOB_GC_MAX_AGE;
@AttributeDefinition(
name = "Blob tracking snapshot interval",
description = "Interval in seconds in which snapshots of locally tracked blob ids are " +
"taken and synchronized with the blob store. This should be configured to be " +
"less than the frequency of blob garbage collection so that deletions during blob " +
"garbage collection can be accounted for in the next garbage collection execution. " +
"Default value is '" + DEFAULT_BLOB_SNAPSHOT_INTERVAL + "'."
)
long blobTrackSnapshotIntervalInSecs() default DEFAULT_BLOB_SNAPSHOT_INTERVAL;
}
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.STATIC,
policyOption = ReferencePolicyOption.GREEDY,
target = ONLY_STANDALONE_TARGET
)
private volatile BlobStore blobStore;
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.STATIC,
policyOption = ReferencePolicyOption.GREEDY
)
private volatile SegmentNodeStorePersistence segmentStore;
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.STATIC,
policyOption = ReferencePolicyOption.GREEDY
)
private volatile PersistentCache persistentCache;
@Reference
private StatisticsProvider statisticsProvider = StatisticsProvider.NOOP;
private final Closer closer = Closer.create();
@Activate
public void activate(ComponentContext context, Configuration configuration) throws IOException {
OsgiWhiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext());
registerSegmentStore(context, configuration, blobStore, segmentStore, persistentCache, statisticsProvider, closer, whiteboard, log);
}
private static SegmentNodeStore registerSegmentStore(
ComponentContext context,
Configuration configuration,
BlobStore blobStore,
SegmentNodeStorePersistence segmentStore,
PersistentCache persistentCache,
StatisticsProvider statisticsProvider,
Closer closer,
Whiteboard whiteboard,
Logger logger
) throws IOException {
return SegmentNodeStoreRegistrar.registerSegmentNodeStore(new SegmentNodeStoreRegistrar.Configuration() {
int roundToNextPowerOfTwo(int size) {
return 1 << (32 - Integer.numberOfLeadingZeros(Math.max(0, size - 1)));
}
String getMode() {
String mode = configuration.tarmk_mode();
if (isNullOrEmpty(mode)) {
return System.getProperty("tarmk.mode", System.getProperty("sun.arch.data.model", "32"));
}
return mode;
}
int getCacheSize(String name, int otherwise) {
Integer size = Integer.getInteger(name);
if (size != null) {
return size;
}
return otherwise;
}
@Override
public boolean isPrimarySegmentStore() {
return true;
}
@Override
public boolean isSecondarySegmentStore() {
return false;
}
@Override
public boolean isStandbyInstance() {
return configuration.standby();
}
@Override
public String getRole() {
return null;
}
@Override
public int getRetainedGenerations() {
return configuration.compaction_retainedGenerations();
}
@Override
public int getDefaultRetainedGenerations() {
return RETAINED_GENERATIONS_DEFAULT;
}
@Override
public boolean getPauseCompaction() {
return configuration.pauseCompaction();
}
@Override
public int getRetryCount() {
return configuration.compaction_retryCount();
}
@Override
public int getForceCompactionTimeout() {
return configuration.compaction_force_timeout();
}
@Override
public long getSizeDeltaEstimation() {
return configuration.compaction_sizeDeltaEstimation();
}
@Override
public int getMemoryThreshold() {
return configuration.compaction_memoryThreshold();
}
@Override
public boolean getDisableEstimation() {
return configuration.compaction_disableEstimation();
}
@Override
public long getGCProcessLog() {
return configuration.compaction_progressLog();
}
@Override
public File getSegmentDirectory() {
return new File(getRepositoryHome(), "segmentstore");
}
@Override
public File getSplitPersistenceDirectory() {
return new File(getRepositoryHome(), "segmentstore-split");
}
@Override
public int getSegmentCacheSize() {
Integer size = Integer.getInteger("segmentCache.size");
if (size != null) {
return size;
}
return configuration.segmentCache_size();
}
@Override
public int getStringCacheSize() {
return getCacheSize("stringCache.size", configuration.stringCache_size());
}
@Override
public int getTemplateCacheSize() {
Integer size = Integer.getInteger("templateCache.size");
if (size != null) {
return size;
}
return configuration.templateCache_size();
}
@Override
public int getStringDeduplicationCacheSize() {
Integer size = Integer.getInteger("stringDeduplicationCache.size");
if (size != null) {
return size;
}
return configuration.stringDeduplicationCache_size();
}
@Override
public int getTemplateDeduplicationCacheSize() {
Integer size = Integer.getInteger("templateDeduplicationCache.size");
if (size != null) {
return size;
}
return configuration.templateDeduplicationCache_size();
}
@Override
public int getNodeDeduplicationCacheSize() {
Integer size = Integer.getInteger("nodeDeduplicationCache.size");
if (size != null) {
return roundToNextPowerOfTwo(size);
}
return roundToNextPowerOfTwo(configuration.nodeDeduplicationCache_size());
}
@Override
public int getMaxFileSize() {
return configuration.tarmk_size();
}
@Override
public boolean getMemoryMapping() {
return getMode().equals("64");
}
@Override
public boolean hasCustomBlobStore() {
return configuration.customBlobStore();
}
@Override
public boolean hasCustomSegmentStore() {
return configuration.customSegmentStore();
}
@Override
public boolean hasSplitPersistence() {
return configuration.splitPersistence();
}
@Override
public boolean hasCachePersistence() {
return configuration.cachePersistence();
}
@Override
public boolean registerDescriptors() {
return true;
}
@Override
public boolean dispatchChanges() {
return !isStandbyInstance();
}
@Override
public String getRepositoryHome() {
String repositoryHome = OsgiUtil.lookupConfigurationThenFramework(context, "repository.home");
if (isNullOrEmpty(repositoryHome)) {
return "repository";
}
return repositoryHome;
}
@Override
public long getBlobSnapshotInterval() {
return configuration.blobTrackSnapshotIntervalInSecs();
}
@Override
public long getBlobGcMaxAge() {
return configuration.blobGcMaxAgeInSecs();
}
@Override
public File getBackupDirectory() {
String backupDirectory = configuration.repository_backup_dir();
if (isNullOrEmpty(backupDirectory)) {
return new File(getRepositoryHome(), "segmentstore-backup");
}
return new File(backupDirectory);
}
@Override
public Whiteboard getWhiteboard() {
return whiteboard;
}
@Override
public Closer getCloser() {
return closer;
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public StatisticsProvider getStatisticsProvider() {
return statisticsProvider;
}
@Override
public BlobStore getBlobStore() {
return blobStore;
}
@Override
public SegmentNodeStorePersistence getSegmentNodeStorePersistence() {
return segmentStore;
}
@Override
public PersistentCache getPersistentCache() {
return persistentCache;
}
@Override
public BundleContext getBundleContext() {
return context.getBundleContext();
}
});
}
@Deactivate
public void deactivate() {
closeQuietly(closer);
}
}