/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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.elasticsearch.index.engine; import org.apache.lucene.index.*; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.metrics.CounterMetric; import org.elasticsearch.common.metrics.MeanMetric; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.index.merge.MergeStats; import org.elasticsearch.index.merge.OnGoingMerge; import org.elasticsearch.index.shard.MergeSchedulerConfig; import org.elasticsearch.index.shard.ShardId; import java.io.IOException; import java.util.Collections; import java.util.Locale; import java.util.Set; /** * An extension to the {@link ConcurrentMergeScheduler} that provides tracking on merge times, total * and current merges. */ class ElasticsearchConcurrentMergeScheduler extends ConcurrentMergeScheduler { protected final ESLogger logger; private final Settings indexSettings; private final ShardId shardId; protected final MeanMetric totalMerges = new MeanMetric(); protected final CounterMetric totalMergesNumDocs = new CounterMetric(); protected final CounterMetric totalMergesSizeInBytes = new CounterMetric(); protected final CounterMetric currentMerges = new CounterMetric(); protected final CounterMetric currentMergesNumDocs = new CounterMetric(); protected final CounterMetric currentMergesSizeInBytes = new CounterMetric(); protected final CounterMetric totalMergeStoppedTime = new CounterMetric(); protected final CounterMetric totalMergeThrottledTime = new CounterMetric(); private final Set<OnGoingMerge> onGoingMerges = ConcurrentCollections.newConcurrentSet(); private final Set<OnGoingMerge> readOnlyOnGoingMerges = Collections.unmodifiableSet(onGoingMerges); private final MergeSchedulerConfig config; public ElasticsearchConcurrentMergeScheduler(ShardId shardId, Settings indexSettings, MergeSchedulerConfig config) { this.config = config; this.shardId = shardId; this.indexSettings = indexSettings; this.logger = Loggers.getLogger(getClass(), indexSettings, shardId); refreshConfig(); } public Set<OnGoingMerge> onGoingMerges() { return readOnlyOnGoingMerges; } @Override protected void doMerge(IndexWriter writer, MergePolicy.OneMerge merge) throws IOException { int totalNumDocs = merge.totalNumDocs(); long totalSizeInBytes = merge.totalBytesSize(); long timeNS = System.nanoTime(); currentMerges.inc(); currentMergesNumDocs.inc(totalNumDocs); currentMergesSizeInBytes.inc(totalSizeInBytes); OnGoingMerge onGoingMerge = new OnGoingMerge(merge); onGoingMerges.add(onGoingMerge); if (logger.isTraceEnabled()) { logger.trace("merge [{}] starting..., merging [{}] segments, [{}] docs, [{}] size, into [{}] estimated_size", OneMergeHelper.getSegmentName(merge), merge.segments.size(), totalNumDocs, new ByteSizeValue(totalSizeInBytes), new ByteSizeValue(merge.estimatedMergeBytes)); } try { beforeMerge(onGoingMerge); super.doMerge(writer, merge); } finally { long tookMS = TimeValue.nsecToMSec(System.nanoTime() - timeNS); onGoingMerges.remove(onGoingMerge); afterMerge(onGoingMerge); currentMerges.dec(); currentMergesNumDocs.dec(totalNumDocs); currentMergesSizeInBytes.dec(totalSizeInBytes); totalMergesNumDocs.inc(totalNumDocs); totalMergesSizeInBytes.inc(totalSizeInBytes); totalMerges.inc(tookMS); long stoppedMS = TimeValue.nsecToMSec(merge.rateLimiter.getTotalStoppedNS()); long throttledMS = TimeValue.nsecToMSec(merge.rateLimiter.getTotalPausedNS()); totalMergeStoppedTime.inc(stoppedMS); totalMergeThrottledTime.inc(throttledMS); String message = String.format(Locale.ROOT, "merge segment [%s] done: took [%s], [%,.1f MB], [%,d docs], [%s stopped], [%s throttled], [%,.1f MB written], [%,.1f MB/sec throttle]", OneMergeHelper.getSegmentName(merge), TimeValue.timeValueMillis(tookMS), totalSizeInBytes/1024f/1024f, totalNumDocs, TimeValue.timeValueMillis(stoppedMS), TimeValue.timeValueMillis(throttledMS), merge.rateLimiter.getTotalBytesWritten()/1024f/1024f, merge.rateLimiter.getMBPerSec()); if (tookMS > 20000) { // if more than 20 seconds, DEBUG log it logger.debug(message); } else if (logger.isTraceEnabled()) { logger.trace(message); } } } /** * A callback allowing for custom logic before an actual merge starts. */ protected void beforeMerge(OnGoingMerge merge) {} /** * A callback allowing for custom logic before an actual merge starts. */ protected void afterMerge(OnGoingMerge merge) {} @Override public MergeScheduler clone() { // Lucene IW makes a clone internally but since we hold on to this instance // the clone will just be the identity. return this; } @Override protected boolean maybeStall(IndexWriter writer) { // Don't stall here, because we do our own index throttling (in InternalEngine.IndexThrottle) when merges can't keep up return true; } @Override protected MergeThread getMergeThread(IndexWriter writer, MergePolicy.OneMerge merge) throws IOException { MergeThread thread = super.getMergeThread(writer, merge); thread.setName(EsExecutors.threadName(indexSettings, "[" + shardId.index().name() + "][" + shardId.id() + "]: " + thread.getName())); return thread; } MergeStats stats() { final MergeStats mergeStats = new MergeStats(); mergeStats.add(totalMerges.count(), totalMerges.sum(), totalMergesNumDocs.count(), totalMergesSizeInBytes.count(), currentMerges.count(), currentMergesNumDocs.count(), currentMergesSizeInBytes.count(), totalMergeStoppedTime.count(), totalMergeThrottledTime.count(), config.isAutoThrottle() ? getIORateLimitMBPerSec() : Double.POSITIVE_INFINITY); return mergeStats; } void refreshConfig() { if (this.getMaxMergeCount() != config.getMaxMergeCount() || this.getMaxThreadCount() != config.getMaxThreadCount()) { try { this.setMaxMergesAndThreads(config.getMaxMergeCount(), config.getMaxThreadCount()); } catch (IllegalArgumentException ex) { logger.error("Failed to apply merge scheduler settings", ex); } } boolean isEnabled = getIORateLimitMBPerSec() != Double.POSITIVE_INFINITY; if (config.isAutoThrottle() && isEnabled == false) { enableAutoIOThrottle(); } else if (config.isAutoThrottle() == false && isEnabled){ disableAutoIOThrottle(); } } public double getIORateLimitMBPerSec() { return config.isAutoThrottle() ? super.getIORateLimitMBPerSec() : Double.POSITIVE_INFINITY; } }