SOLR-12833: Add configurable timeout to VersionBucket lock.

This commit is contained in:
markrmiller 2018-12-04 10:01:24 -06:00
parent b4e1fe4393
commit b9a966e5f7
4 changed files with 131 additions and 60 deletions

View File

@ -163,6 +163,8 @@ Improvements
* SOLR-12804: Remove static modifier from Overseer queue access. (Mark Miller)
* SOLR-12833: Add configurable timeout to VersionBucket lock. (Jeffery Yuan, Mark Miller)
Other Changes
----------------------

View File

@ -16,12 +16,26 @@
*/
package org.apache.solr.update;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// TODO: make inner?
// TODO: store the highest possible in the index on a commit (but how to not block adds?)
// TODO: could also store highest possible in the transaction log after a commit.
// Or on a new index, just scan "version" for the max?
/** @lucene.internal */
public class VersionBucket {
private int lockTimeoutMs;
public VersionBucket(int lockTimeoutMs) {
this.lockTimeoutMs = lockTimeoutMs;
}
private final Lock lock = new ReentrantLock(true);
private final Condition condition = lock.newCondition();
public long highest;
public void updateHighest(long val) {
@ -29,4 +43,34 @@ public class VersionBucket {
highest = Math.max(highest, Math.abs(val));
}
}
public int getLockTimeoutMs() {
return lockTimeoutMs;
}
public boolean tryLock() {
try {
return lock.tryLock(lockTimeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
public void unlock() {
lock.unlock();
}
public void signalAll() {
condition.signalAll();
}
public void awaitNanos(long nanosTimeout) {
try {
condition.awaitNanos(nanosTimeout);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}

View File

@ -45,8 +45,13 @@ import org.slf4j.LoggerFactory;
import static org.apache.solr.common.params.CommonParams.VERSION_FIELD;
public class VersionInfo {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final String SYS_PROP_BUCKET_VERSION_LOCK_TIMEOUT_MS = "bucketVersionLockTimeoutMs";
/**
* same as default client read timeout: 10 mins
*/
private static final int DEFAULT_VERSION_BUCKET_LOCK_TIMEOUT_MS = 600000;
private final UpdateLog ulog;
private final VersionBucket[] buckets;
@ -54,6 +59,8 @@ public class VersionInfo {
private SchemaField idField;
final ReadWriteLock lock = new ReentrantReadWriteLock(true);
private int versionBucketLockTimeoutMs;
/**
* Gets and returns the {@link org.apache.solr.common.params.CommonParams#VERSION_FIELD} from the specified
* schema, after verifying that it is indexed, stored, and single-valued.
@ -94,9 +101,11 @@ public class VersionInfo {
IndexSchema schema = ulog.uhandler.core.getLatestSchema();
versionField = getAndCheckVersionField(schema);
idField = schema.getUniqueKeyField();
versionBucketLockTimeoutMs = ulog.uhandler.core.getSolrConfig().getInt("updateHandler/versionBucketLockTimeoutMs",
Integer.parseInt(System.getProperty(SYS_PROP_BUCKET_VERSION_LOCK_TIMEOUT_MS, "" + DEFAULT_VERSION_BUCKET_LOCK_TIMEOUT_MS)));
buckets = new VersionBucket[ BitUtil.nextHighestPowerOfTwo(nBuckets) ];
for (int i=0; i<buckets.length; i++) {
buckets[i] = new VersionBucket();
buckets[i] = new VersionBucket(versionBucketLockTimeoutMs);
}
}

View File

@ -1010,8 +1010,7 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
if (vinfo == null) {
if (AtomicUpdateDocumentMerger.isAtomicUpdate(cmd)) {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Atomic document updates are not supported unless <updateLog/> is configured");
} else {
super.processAdd(cmd);
@ -1019,7 +1018,8 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
}
}
// This is only the hash for the bucket, and must be based only on the uniqueKey (i.e. do not use a pluggable hash here)
// This is only the hash for the bucket, and must be based only on the uniqueKey (i.e. do not use a pluggable hash
// here)
int bucketHash = bucketHash(idBytes);
// at this point, there is an update we need to try and apply.
@ -1059,9 +1059,10 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
}
vinfo.lockForUpdate();
if (bucket.tryLock()) {
try {
synchronized (bucket) {
bucket.notifyAll(); //just in case anyone is waiting let them know that we have a new update
bucket.signalAll();
// just in case anyone is waiting let them know that we have a new update
// we obtain the version when synchronized and then do the add so we can ensure that
// if version1 < version2 then version1 is actually added before version2.
@ -1100,15 +1101,16 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
if (versionOnUpdate != 0) {
Long lastVersion = vinfo.lookupVersion(cmd.getIndexedId());
long foundVersion = lastVersion == null ? -1 : lastVersion;
if ( versionOnUpdate == foundVersion || (versionOnUpdate < 0 && foundVersion < 0) || (versionOnUpdate==1 && foundVersion > 0) ) {
if (versionOnUpdate == foundVersion || (versionOnUpdate < 0 && foundVersion < 0)
|| (versionOnUpdate == 1 && foundVersion > 0)) {
// we're ok if versions match, or if both are negative (all missing docs are equal), or if cmd
// specified it must exist (versionOnUpdate==1) and it does.
} else {
throw new SolrException(ErrorCode.CONFLICT, "version conflict for " + cmd.getPrintableId() + " expected=" + versionOnUpdate + " actual=" + foundVersion);
throw new SolrException(ErrorCode.CONFLICT, "version conflict for " + cmd.getPrintableId()
+ " expected=" + versionOnUpdate + " actual=" + foundVersion);
}
}
long version = vinfo.getNewClock();
cmd.setVersion(version);
cmd.getSolrInputDocument().setField(CommonParams.VERSION_FIELD, version);
@ -1142,11 +1144,14 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
return true;
} else {
assert fetchedFromLeader instanceof AddUpdateCommand;
// Newer document was fetched from the leader. Apply that document instead of this current in-place update.
log.info("In-place update of {} failed to find valid lastVersion to apply to, forced to fetch full doc from leader: {}",
// Newer document was fetched from the leader. Apply that document instead of this current in-place
// update.
log.info(
"In-place update of {} failed to find valid lastVersion to apply to, forced to fetch full doc from leader: {}",
idBytes.utf8ToString(), fetchedFromLeader);
// Make this update to become a non-inplace update containing the full document obtained from the leader
// Make this update to become a non-inplace update containing the full document obtained from the
// leader
cmd.solrDoc = ((AddUpdateCommand) fetchedFromLeader).solrDoc;
cmd.prevVersion = -1;
cmd.setVersion((long) cmd.solrDoc.getFieldValue(CommonParams.VERSION_FIELD));
@ -1202,12 +1207,18 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
if (willDistrib && cloneRequiredOnLeader) {
cmd.solrDoc = clonedDoc;
}
} // end synchronized (bucket)
} finally {
bucket.unlock();
vinfo.unlockForUpdate();
}
return false;
} else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unable to get version bucket lock in " + bucket.getLockTimeoutMs() + " ms");
}
}
@VisibleForTesting
@ -1236,32 +1247,32 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
TimeOut waitTimeout = new TimeOut(5, TimeUnit.SECONDS, TimeSource.NANO_TIME);
vinfo.lockForUpdate();
if (bucket.tryLock()) {
try {
synchronized (bucket) {
Long lookedUpVersion = vinfo.lookupVersion(cmd.getIndexedId());
lastFoundVersion = lookedUpVersion == null ? 0L : lookedUpVersion;
if (Math.abs(lastFoundVersion) < cmd.prevVersion) {
log.debug("Re-ordered inplace update. version={}, prevVersion={}, lastVersion={}, replayOrPeerSync={}, id={}",
(cmd.getVersion() == 0 ? versionOnUpdate : cmd.getVersion()), cmd.prevVersion, lastFoundVersion, isReplayOrPeersync, cmd.getPrintableId());
(cmd.getVersion() == 0 ? versionOnUpdate : cmd.getVersion()), cmd.prevVersion, lastFoundVersion,
isReplayOrPeersync, cmd.getPrintableId());
}
while (Math.abs(lastFoundVersion) < cmd.prevVersion && !waitTimeout.hasTimedOut()) {
try {
long timeLeft = waitTimeout.timeLeft(TimeUnit.MILLISECONDS);
if (timeLeft > 0) { // wait(0) waits forever until notified, but we don't want that.
bucket.wait(timeLeft);
}
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
bucket.awaitNanos(waitTimeout.timeLeft(TimeUnit.NANOSECONDS));
lookedUpVersion = vinfo.lookupVersion(cmd.getIndexedId());
lastFoundVersion = lookedUpVersion == null ? 0L : lookedUpVersion;
}
}
} finally {
bucket.unlock();
vinfo.unlockForUpdate();
}
} else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unable to get version bucket lock in " + bucket.getLockTimeoutMs() + " ms");
}
if (Math.abs(lastFoundVersion) > cmd.prevVersion) {
// This must've been the case due to a higher version full update succeeding concurrently, while we were waiting or
@ -1793,7 +1804,8 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
return false;
}
// This is only the hash for the bucket, and must be based only on the uniqueKey (i.e. do not use a pluggable hash here)
// This is only the hash for the bucket, and must be based only on the uniqueKey (i.e. do not use a pluggable hash
// here)
int bucketHash = bucketHash(idBytes);
// at this point, there is an update we need to try and apply.
@ -1819,9 +1831,8 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
VersionBucket bucket = vinfo.bucket(bucketHash);
vinfo.lockForUpdate();
if (bucket.tryLock()) {
try {
synchronized (bucket) {
if (versionsStored) {
long bucketVersion = bucket.highest;
@ -1847,11 +1858,13 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
if (signedVersionOnUpdate != 0) {
Long lastVersion = vinfo.lookupVersion(cmd.getIndexedId());
long foundVersion = lastVersion == null ? -1 : lastVersion;
if ( (signedVersionOnUpdate == foundVersion) || (signedVersionOnUpdate < 0 && foundVersion < 0) || (signedVersionOnUpdate == 1 && foundVersion > 0) ) {
if ((signedVersionOnUpdate == foundVersion) || (signedVersionOnUpdate < 0 && foundVersion < 0)
|| (signedVersionOnUpdate == 1 && foundVersion > 0)) {
// we're ok if versions match, or if both are negative (all missing docs are equal), or if cmd
// specified it must exist (versionOnUpdate==1) and it does.
} else {
throw new SolrException(ErrorCode.CONFLICT, "version conflict for " + cmd.getId() + " expected=" + signedVersionOnUpdate + " actual=" + foundVersion);
throw new SolrException(ErrorCode.CONFLICT, "version conflict for " + cmd.getId() + " expected="
+ signedVersionOnUpdate + " actual=" + foundVersion);
}
}
@ -1892,11 +1905,14 @@ public class DistributedUpdateProcessor extends UpdateRequestProcessor {
doLocalDelete(cmd);
return false;
} // end synchronized (bucket)
} finally {
bucket.unlock();
vinfo.unlockForUpdate();
}
} else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unable to get version bucket lock in " + bucket.getLockTimeoutMs() + " ms");
}
}
@Override