fold in feedback

This commit is contained in:
Michael McCandless 2016-01-05 09:53:13 -05:00 committed by mikemccand
parent 485f4171bb
commit 99d6ec53fa
6 changed files with 105 additions and 83 deletions

View File

@ -375,7 +375,7 @@ public abstract class Engine implements Closeable {
} }
/** How much heap is used that would be freed by a refresh. Note that this may throw {@link AlreadyClosedException}. */ /** How much heap is used that would be freed by a refresh. Note that this may throw {@link AlreadyClosedException}. */
abstract public long indexBufferRAMBytesUsed(); abstract public long getIndexBufferRAMBytesUsed();
protected Segment[] getSegmentInfo(SegmentInfos lastCommittedSegmentInfos, boolean verbose) { protected Segment[] getSegmentInfo(SegmentInfos lastCommittedSegmentInfos, boolean verbose) {
ensureOpen(); ensureOpen();

View File

@ -522,11 +522,11 @@ public class InternalEngine extends Engine {
// TODO: it's not great that we secretly tie searcher visibility to "freeing up heap" here... really we should keep two // TODO: it's not great that we secretly tie searcher visibility to "freeing up heap" here... really we should keep two
// searcher managers, one for searching which is only refreshed by the schedule the user requested (refresh_interval, or invoking // searcher managers, one for searching which is only refreshed by the schedule the user requested (refresh_interval, or invoking
// refresh API), and another for version map interactions: // refresh API), and another for version map interactions. See #15768.
long versionMapBytes = versionMap.ramBytesUsedForRefresh(); final long versionMapBytes = versionMap.ramBytesUsedForRefresh();
long indexingBufferBytes = indexWriter.ramBytesUsed(); final long indexingBufferBytes = indexWriter.ramBytesUsed();
boolean useRefresh = versionMapRefreshPending.get() || (indexingBufferBytes/4 < versionMapBytes); final boolean useRefresh = versionMapRefreshPending.get() || (indexingBufferBytes/4 < versionMapBytes);
if (useRefresh) { if (useRefresh) {
// The version map is using > 25% of the indexing buffer, so we do a refresh so the version map also clears // The version map is using > 25% of the indexing buffer, so we do a refresh so the version map also clears
logger.debug("use refresh to write indexing buffer (heap size=[{}]), to also clear version map (heap size=[{}])", logger.debug("use refresh to write indexing buffer (heap size=[{}]), to also clear version map (heap size=[{}])",
@ -823,7 +823,7 @@ public class InternalEngine extends Engine {
} }
@Override @Override
public long indexBufferRAMBytesUsed() { public long getIndexBufferRAMBytesUsed() {
return indexWriter.ramBytesUsed() + versionMap.ramBytesUsedForRefresh(); return indexWriter.ramBytesUsed() + versionMap.ramBytesUsedForRefresh();
} }
@ -1050,7 +1050,7 @@ public class InternalEngine extends Engine {
@Override @Override
public void activateThrottling() { public void activateThrottling() {
int count = throttleRequestCount.incrementAndGet(); int count = throttleRequestCount.incrementAndGet();
assert count >= 1; assert count >= 1: "invalid post-increment throttleRequestCount=" + count;
if (count == 1) { if (count == 1) {
throttle.activate(); throttle.activate();
} }
@ -1059,7 +1059,7 @@ public class InternalEngine extends Engine {
@Override @Override
public void deactivateThrottling() { public void deactivateThrottling() {
int count = throttleRequestCount.decrementAndGet(); int count = throttleRequestCount.decrementAndGet();
assert count >= 0; assert count >= 0: "invalid post-decrement throttleRequestCount=" + count;
if (count == 0) { if (count == 0) {
throttle.deactivate(); throttle.deactivate();
} }

View File

@ -227,7 +227,7 @@ public class ShadowEngine extends Engine {
} }
@Override @Override
public long indexBufferRAMBytesUsed() { public long getIndexBufferRAMBytesUsed() {
// No IndexWriter nor version map // No IndexWriter nor version map
throw new UnsupportedOperationException("ShadowEngine has no IndexWriter"); throw new UnsupportedOperationException("ShadowEngine has no IndexWriter");
} }

View File

@ -19,6 +19,18 @@
package org.elasticsearch.index.shard; package org.elasticsearch.index.shard;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.ClosedByInterruptException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.lucene.index.*; import org.apache.lucene.index.*;
import org.apache.lucene.search.QueryCachingPolicy; import org.apache.lucene.search.QueryCachingPolicy;
import org.apache.lucene.search.UsageTrackingQueryCachingPolicy; import org.apache.lucene.search.UsageTrackingQueryCachingPolicy;
@ -81,8 +93,8 @@ import org.elasticsearch.index.search.stats.SearchStats;
import org.elasticsearch.index.search.stats.ShardSearchStats; import org.elasticsearch.index.search.stats.ShardSearchStats;
import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.index.similarity.SimilarityService;
import org.elasticsearch.index.snapshots.IndexShardRepository; import org.elasticsearch.index.snapshots.IndexShardRepository;
import org.elasticsearch.index.store.Store;
import org.elasticsearch.index.store.Store.MetadataSnapshot; import org.elasticsearch.index.store.Store.MetadataSnapshot;
import org.elasticsearch.index.store.Store;
import org.elasticsearch.index.store.StoreFileMetaData; import org.elasticsearch.index.store.StoreFileMetaData;
import org.elasticsearch.index.store.StoreStats; import org.elasticsearch.index.store.StoreStats;
import org.elasticsearch.index.suggest.stats.ShardSuggestMetric; import org.elasticsearch.index.suggest.stats.ShardSuggestMetric;
@ -104,17 +116,6 @@ import org.elasticsearch.search.suggest.completion.CompletionFieldStats;
import org.elasticsearch.search.suggest.completion.CompletionStats; import org.elasticsearch.search.suggest.completion.CompletionStats;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.ClosedByInterruptException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class IndexShard extends AbstractIndexShardComponent { public class IndexShard extends AbstractIndexShardComponent {
@ -148,6 +149,9 @@ public class IndexShard extends AbstractIndexShardComponent {
private final IndexSettings idxSettings; private final IndexSettings idxSettings;
private final NodeServicesProvider provider; private final NodeServicesProvider provider;
/** How many bytes we are currently moving to disk, via either IndexWriter.flush or refresh */
private final AtomicLong writingBytes = new AtomicLong();
private TimeValue refreshInterval; private TimeValue refreshInterval;
private volatile ScheduledFuture<?> refreshScheduledFuture; private volatile ScheduledFuture<?> refreshScheduledFuture;
@ -542,15 +546,17 @@ public class IndexShard extends AbstractIndexShardComponent {
public void refresh(String source) { public void refresh(String source) {
verifyNotClosed(); verifyNotClosed();
if (canIndex()) { if (canIndex()) {
long ramBytesUsed = getEngine().indexBufferRAMBytesUsed(); long bytes = getEngine().getIndexBufferRAMBytesUsed();
indexingMemoryController.addWritingBytes(this, ramBytesUsed); writingBytes.addAndGet(bytes);
try { try {
logger.debug("refresh with source [{}] indexBufferRAMBytesUsed [{}]", source, new ByteSizeValue(ramBytesUsed)); logger.debug("refresh with source [{}] indexBufferRAMBytesUsed [{}]", source, new ByteSizeValue(bytes));
long time = System.nanoTime(); long time = System.nanoTime();
getEngine().refresh(source); getEngine().refresh(source);
refreshMetric.inc(System.nanoTime() - time); refreshMetric.inc(System.nanoTime() - time);
} finally { } finally {
indexingMemoryController.removeWritingBytes(this, ramBytesUsed); logger.debug("remove [{}] writing bytes for shard [{}]", new ByteSizeValue(bytes), shardId());
// nocommit but we don't promptly stop index throttling anymore?
writingBytes.addAndGet(-bytes);
} }
} else { } else {
logger.debug("refresh with source [{}]", source); logger.debug("refresh with source [{}]", source);
@ -560,6 +566,11 @@ public class IndexShard extends AbstractIndexShardComponent {
} }
} }
/** Returns how many bytes we are currently moving from heap to disk */
public long getWritingBytes() {
return writingBytes.get();
}
public RefreshStats refreshStats() { public RefreshStats refreshStats() {
return new RefreshStats(refreshMetric.count(), TimeUnit.NANOSECONDS.toMillis(refreshMetric.sum())); return new RefreshStats(refreshMetric.count(), TimeUnit.NANOSECONDS.toMillis(refreshMetric.sum()));
} }
@ -1008,7 +1019,7 @@ public class IndexShard extends AbstractIndexShardComponent {
return 0; return 0;
} }
try { try {
return engine.indexBufferRAMBytesUsed(); return engine.getIndexBufferRAMBytesUsed();
} catch (AlreadyClosedException ex) { } catch (AlreadyClosedException ex) {
return 0; return 0;
} }
@ -1262,15 +1273,18 @@ public class IndexShard extends AbstractIndexShardComponent {
public void run() { public void run() {
try { try {
Engine engine = getEngine(); Engine engine = getEngine();
long bytes = engine.indexBufferRAMBytesUsed(); long bytes = engine.getIndexBufferRAMBytesUsed();
// NOTE: this can be an overestimate by up to 20%, if engine uses IW.flush not refresh, because version map // NOTE: this can be an overestimate by up to 20%, if engine uses IW.flush not refresh, because version map
// memory is low enough, but this is fine because after the writes finish, IMC will poll again and see that // memory is low enough, but this is fine because after the writes finish, IMC will poll again and see that
// there's still up to the 20% being used and continue writing if necessary: // there's still up to the 20% being used and continue writing if necessary:
indexingMemoryController.addWritingBytes(IndexShard.this, bytes); writingBytes.addAndGet(bytes);
logger.debug("add [{}] writing bytes for shard [{}]", new ByteSizeValue(bytes), shardId());
try { try {
getEngine().writeIndexingBuffer(); getEngine().writeIndexingBuffer();
} finally { } finally {
indexingMemoryController.removeWritingBytes(IndexShard.this, bytes); logger.debug("remove [{}] writing bytes for shard [{}]", new ByteSizeValue(bytes), shardId());
// nocommit but we don't promptly stop index throttling anymore?
writingBytes.addAndGet(-bytes);
} }
} catch (Exception e) { } catch (Exception e) {
handleRefreshException(e); handleRefreshException(e);

View File

@ -23,6 +23,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
@ -81,9 +82,6 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
private final ShardsIndicesStatusChecker statusChecker; private final ShardsIndicesStatusChecker statusChecker;
/** Maps each shard to how many bytes it is currently, asynchronously, writing to disk */
private final Map<IndexShard,Long> writingBytes = new ConcurrentHashMap<>();
@Inject @Inject
public IndexingMemoryController(Settings settings, ThreadPool threadPool, IndicesService indicesService) { public IndexingMemoryController(Settings settings, ThreadPool threadPool, IndicesService indicesService) {
this(settings, threadPool, indicesService, JvmInfo.jvmInfo().getMem().getHeapMax().bytes()); this(settings, threadPool, indicesService, JvmInfo.jvmInfo().getMem().getHeapMax().bytes());
@ -126,21 +124,6 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
SHARD_MEMORY_INTERVAL_TIME_SETTING, this.interval); SHARD_MEMORY_INTERVAL_TIME_SETTING, this.interval);
} }
/** Shard calls this when it starts writing its indexing buffer to disk to notify us */
public void addWritingBytes(IndexShard shard, long numBytes) {
writingBytes.put(shard, numBytes);
logger.debug("add [{}] writing bytes for shard [{}]", new ByteSizeValue(numBytes), shard.shardId());
}
/** Shard calls when it's done writing these bytes to disk */
public void removeWritingBytes(IndexShard shard, long numBytes) {
writingBytes.remove(shard);
logger.debug("clear [{}] writing bytes for shard [{}]", new ByteSizeValue(numBytes), shard.shardId());
// Since some bytes just freed up, now we check again to give throttling a chance to stop:
forceCheck();
}
@Override @Override
protected void doStart() { protected void doStart() {
// it's fine to run it on the scheduler thread, no busy work // it's fine to run it on the scheduler thread, no busy work
@ -184,12 +167,17 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
return shard.getIndexBufferRAMBytesUsed(); return shard.getIndexBufferRAMBytesUsed();
} }
/** returns how many bytes this shard is currently writing to disk */
protected long getShardWritingBytes(IndexShard shard) {
return shard.getWritingBytes();
}
/** ask this shard to refresh, in the background, to free up heap */ /** ask this shard to refresh, in the background, to free up heap */
protected void writeIndexingBufferAsync(IndexShard shard) { protected void writeIndexingBufferAsync(IndexShard shard) {
shard.writeIndexingBufferAsync(); shard.writeIndexingBufferAsync();
} }
/** used by tests to check if any shards active status changed, now. */ /** force checker to run now */
public void forceCheck() { public void forceCheck() {
statusChecker.run(); statusChecker.run();
} }
@ -225,25 +213,42 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
} }
} }
class ShardsIndicesStatusChecker implements Runnable { /** not static because we need access to many fields/methods from our containing class (IMC): */
final class ShardsIndicesStatusChecker implements Runnable {
long bytesWrittenSinceCheck; final AtomicLong bytesWrittenSinceCheck = new AtomicLong();
final ReentrantLock runLock = new ReentrantLock();
/** Shard calls this on each indexing/delete op */ /** Shard calls this on each indexing/delete op */
public synchronized void bytesWritten(int bytes) { public void bytesWritten(int bytes) {
bytesWrittenSinceCheck += bytes; long totalBytes = bytesWrittenSinceCheck.addAndGet(bytes);
if (bytesWrittenSinceCheck > indexingBuffer.bytes()/30) { if (totalBytes > indexingBuffer.bytes()/30) {
if (runLock.tryLock()) {
try {
bytesWrittenSinceCheck.addAndGet(-totalBytes);
// NOTE: this is only an approximate check, because bytes written is to the translog, vs indexing memory buffer which is // NOTE: this is only an approximate check, because bytes written is to the translog, vs indexing memory buffer which is
// typically smaller but can be larger in extreme cases (many unique terms). This logic is here only as a safety against // typically smaller but can be larger in extreme cases (many unique terms). This logic is here only as a safety against
// thread starvation or too infrequent checking, to ensure we are still checking periodically, in proportion to bytes // thread starvation or too infrequent checking, to ensure we are still checking periodically, in proportion to bytes
// processed by indexing: // processed by indexing:
run(); run();
} finally {
runLock.unlock();
}
}
} }
} }
@Override @Override
public synchronized void run() { public void run() {
runLock.lock();
try {
runUnlocked();
} finally {
runLock.unlock();
}
}
private void runUnlocked() {
// NOTE: even if we hit an errant exc here, our ThreadPool.scheduledWithFixedDelay will log the exception and re-invoke us // NOTE: even if we hit an errant exc here, our ThreadPool.scheduledWithFixedDelay will log the exception and re-invoke us
// again, on schedule // again, on schedule
@ -257,12 +262,11 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
checkIdle(shard, inactiveTime.nanos()); checkIdle(shard, inactiveTime.nanos());
// How many bytes this shard is currently (async'd) moving from heap to disk: // How many bytes this shard is currently (async'd) moving from heap to disk:
Long shardWritingBytes = writingBytes.get(shard); long shardWritingBytes = getShardWritingBytes(shard);
// How many heap bytes this shard is currently using // How many heap bytes this shard is currently using
long shardBytesUsed = getIndexBufferRAMBytesUsed(shard); long shardBytesUsed = getIndexBufferRAMBytesUsed(shard);
if (shardWritingBytes != null) {
shardBytesUsed -= shardWritingBytes; shardBytesUsed -= shardWritingBytes;
totalBytesWriting += shardWritingBytes; totalBytesWriting += shardWritingBytes;
@ -271,7 +275,6 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
if (shardBytesUsed < 0) { if (shardBytesUsed < 0) {
continue; continue;
} }
}
totalBytesUsed += shardBytesUsed; totalBytesUsed += shardBytesUsed;
} }
@ -293,12 +296,11 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
for (IndexShard shard : availableShards()) { for (IndexShard shard : availableShards()) {
// How many bytes this shard is currently (async'd) moving from heap to disk: // How many bytes this shard is currently (async'd) moving from heap to disk:
Long shardWritingBytes = writingBytes.get(shard); long shardWritingBytes = getShardWritingBytes(shard);
// How many heap bytes this shard is currently using // How many heap bytes this shard is currently using
long shardBytesUsed = getIndexBufferRAMBytesUsed(shard); long shardBytesUsed = getIndexBufferRAMBytesUsed(shard);
if (shardWritingBytes != null) {
// Only count up bytes not already being refreshed: // Only count up bytes not already being refreshed:
shardBytesUsed -= shardWritingBytes; shardBytesUsed -= shardWritingBytes;
@ -307,11 +309,10 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
if (shardBytesUsed < 0) { if (shardBytesUsed < 0) {
continue; continue;
} }
}
if (shardBytesUsed > 0) { if (shardBytesUsed > 0) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
if (shardWritingBytes != null) { if (shardWritingBytes != 0) {
logger.trace("shard [{}] is using [{}] heap, writing [{}] heap", shard.shardId(), shardBytesUsed, shardWritingBytes); logger.trace("shard [{}] is using [{}] heap, writing [{}] heap", shard.shardId(), shardBytesUsed, shardWritingBytes);
} else { } else {
logger.trace("shard [{}] is using [{}] heap, not writing any bytes", shard.shardId(), shardBytesUsed); logger.trace("shard [{}] is using [{}] heap, not writing any bytes", shard.shardId(), shardBytesUsed);
@ -341,8 +342,6 @@ public class IndexingMemoryController extends AbstractLifecycleComponent<Indexin
} }
throttled.clear(); throttled.clear();
} }
bytesWrittenSinceCheck = 0;
} }
} }

View File

@ -73,6 +73,16 @@ public class IndexingMemoryControllerTests extends ESSingleNodeTestCase {
return indexBufferRAMBytesUsed.get(shard) + writingBytes.get(shard); return indexBufferRAMBytesUsed.get(shard) + writingBytes.get(shard);
} }
@Override
protected long getShardWritingBytes(IndexShard shard) {
Long bytes = writingBytes.get(shard);
if (bytes == null) {
return 0;
} else {
return bytes;
}
}
@Override @Override
protected void checkIdle(IndexShard shard, long inactiveTimeNS) { protected void checkIdle(IndexShard shard, long inactiveTimeNS) {
} }
@ -81,7 +91,6 @@ public class IndexingMemoryControllerTests extends ESSingleNodeTestCase {
public void writeIndexingBufferAsync(IndexShard shard) { public void writeIndexingBufferAsync(IndexShard shard) {
long bytes = indexBufferRAMBytesUsed.put(shard, 0L); long bytes = indexBufferRAMBytesUsed.put(shard, 0L);
writingBytes.put(shard, writingBytes.get(shard) + bytes); writingBytes.put(shard, writingBytes.get(shard) + bytes);
addWritingBytes(shard, bytes);
indexBufferRAMBytesUsed.put(shard, 0L); indexBufferRAMBytesUsed.put(shard, 0L);
} }
@ -96,8 +105,7 @@ public class IndexingMemoryControllerTests extends ESSingleNodeTestCase {
} }
public void doneWriting(IndexShard shard) { public void doneWriting(IndexShard shard) {
long bytes = writingBytes.put(shard, 0L); writingBytes.put(shard, 0L);
removeWritingBytes(shard, bytes);
} }
public void assertBuffer(IndexShard shard, int expectedMB) { public void assertBuffer(IndexShard shard, int expectedMB) {
@ -281,6 +289,7 @@ public class IndexingMemoryControllerTests extends ESSingleNodeTestCase {
// Both shards finally finish writing, and throttling should stop: // Both shards finally finish writing, and throttling should stop:
controller.doneWriting(shard0); controller.doneWriting(shard0);
controller.doneWriting(shard1); controller.doneWriting(shard1);
controller.forceCheck();
controller.assertNotThrottled(shard0); controller.assertNotThrottled(shard0);
controller.assertNotThrottled(shard1); controller.assertNotThrottled(shard1);
} }