use of voldemort.store.InsufficientOperationalNodesException in project voldemort by voldemort.
the class PerformParallelRequests method execute.
public void execute(final Pipeline pipeline) {
List<Node> nodes = pipelineData.getNodes();
int attempts = Math.min(preferred, nodes.size());
final Map<Integer, Response<ByteArray, Object>> responses = new ConcurrentHashMap<Integer, Response<ByteArray, Object>>();
final CountDownLatch latch = new CountDownLatch(attempts);
if (logger.isTraceEnabled())
logger.trace("Attempting " + attempts + " " + pipeline.getOperation().getSimpleName() + " operations in parallel for key " + key);
final AtomicBoolean isResponseProcessed = new AtomicBoolean(false);
for (int i = 0; i < attempts; i++) {
final Node node = nodes.get(i);
pipelineData.incrementNodeIndex();
final long startMs = logger.isDebugEnabled() ? System.currentTimeMillis() : -1;
NonblockingStoreCallback callback = new NonblockingStoreCallback() {
public void requestComplete(Object result, long requestTime) {
if (logger.isTraceEnabled())
logger.trace(pipeline.getOperation().getSimpleName() + " response received (" + requestTime + " ms.) from node " + node.getId() + "for key " + key);
Response<ByteArray, Object> response = new Response<ByteArray, Object>(node, key, result, requestTime);
if (logger.isDebugEnabled())
logger.debug("Finished " + pipeline.getOperation().getSimpleName() + " for key " + ByteUtils.toHexString(key.get()) + " (keyRef: " + System.identityHashCode(key) + "); started at " + startMs + " took " + requestTime + " ms on node " + node.getId() + "(" + node.getHost() + ")");
responses.put(node.getId(), response);
latch.countDown();
// This reduces the window where an exception is lost
if (isResponseProcessed.get() && response.getValue() instanceof Exception) {
if (response.getValue() instanceof InvalidMetadataException) {
pipelineData.reportException((InvalidMetadataException) response.getValue());
logger.warn("Received invalid metadata problem after a successful " + pipeline.getOperation().getSimpleName() + " call on node " + node.getId() + ", store '" + pipelineData.getStoreName() + "' for key " + key);
} else {
handleResponseError(response, pipeline, failureDetector);
}
}
}
};
if (logger.isTraceEnabled())
logger.trace("Submitting " + pipeline.getOperation().getSimpleName() + " request on node " + node.getId() + " for key " + key);
NonblockingStore store = nonblockingStores.get(node.getId());
if (pipeline.getOperation() == Operation.GET)
store.submitGetRequest(key, transforms, callback, timeoutMs);
else if (pipeline.getOperation() == Operation.GET_VERSIONS)
store.submitGetVersionsRequest(key, callback, timeoutMs);
else
throw new IllegalStateException(getClass().getName() + " does not support pipeline operation " + pipeline.getOperation());
}
try {
latch.await(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (logger.isEnabledFor(Level.WARN))
logger.warn(e, e);
}
for (Response<ByteArray, Object> response : responses.values()) {
if (response.getValue() instanceof Exception) {
if (handleResponseError(response, pipeline, failureDetector))
return;
} else {
pipelineData.incrementSuccesses();
Response<ByteArray, V> rCast = Utils.uncheckedCast(response);
pipelineData.getResponses().add(rCast);
failureDetector.recordSuccess(response.getNode(), response.getRequestTime());
pipelineData.getZoneResponses().add(response.getNode().getZoneId());
}
}
isResponseProcessed.set(true);
if (logger.isDebugEnabled())
logger.debug("GET for key " + ByteUtils.toHexString(key.get()) + " (keyRef: " + System.identityHashCode(key) + "); successes: " + pipelineData.getSuccesses() + " preferred: " + preferred + " required: " + required);
if (pipelineData.getSuccesses() < required) {
if (insufficientSuccessesEvent != null) {
pipeline.addEvent(insufficientSuccessesEvent);
} else {
pipelineData.setFatalError(new InsufficientOperationalNodesException(required + " " + pipeline.getOperation().getSimpleName() + "s required, but only " + pipelineData.getSuccesses() + " succeeded", pipelineData.getReplicationSet(), pipelineData.getNodes(), pipelineData.getFailedNodes(), pipelineData.getFailures()));
pipeline.abort();
}
} else {
if (pipelineData.getZonesRequired() != null) {
int zonesSatisfied = pipelineData.getZoneResponses().size();
if (zonesSatisfied >= (pipelineData.getZonesRequired() + 1)) {
pipeline.addEvent(completeEvent);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Operation " + pipeline.getOperation().getSimpleName() + "failed due to insufficient zone responses, required " + pipelineData.getZonesRequired() + " obtained " + zonesSatisfied + " " + pipelineData.getZoneResponses() + " for key " + key);
}
if (this.insufficientZonesEvent != null) {
pipeline.addEvent(this.insufficientZonesEvent);
} else {
pipelineData.setFatalError(new InsufficientZoneResponsesException((pipelineData.getZonesRequired() + 1) + " " + pipeline.getOperation().getSimpleName() + "s required zone, but only " + zonesSatisfied + " succeeded"));
}
}
} else {
pipeline.addEvent(completeEvent);
}
}
}
use of voldemort.store.InsufficientOperationalNodesException in project voldemort by voldemort.
the class PerformParallelDeleteRequests method executeInternal.
private void executeInternal(final Pipeline pipeline) {
List<Node> nodes = pipelineData.getNodes();
final Map<Integer, Response<ByteArray, Object>> responses = new ConcurrentHashMap<Integer, Response<ByteArray, Object>>();
int attempts = nodes.size();
int blocks = Math.min(preferred, attempts);
final CountDownLatch attemptsLatch = new CountDownLatch(attempts);
final CountDownLatch blocksLatch = new CountDownLatch(blocks);
if (logger.isTraceEnabled())
logger.trace("Attempting " + attempts + " " + pipeline.getOperation().getSimpleName() + " operations in parallel");
long beginTime = System.nanoTime();
for (int i = 0; i < attempts; i++) {
final Node node = nodes.get(i);
pipelineData.incrementNodeIndex();
NonblockingStoreCallback callback = new NonblockingStoreCallback() {
public void requestComplete(Object result, long requestTime) {
if (logger.isTraceEnabled())
logger.trace(pipeline.getOperation().getSimpleName() + " response received (" + requestTime + " ms.) from node " + node.getId());
Response<ByteArray, Object> response = new Response<ByteArray, Object>(node, key, result, requestTime);
if (logger.isTraceEnabled()) {
logger.trace(attemptsLatch.getCount() + " attempts remaining. Will block " + " for " + blocksLatch.getCount() + " more ");
}
responses.put(node.getId(), response);
if (response.getValue() instanceof Exception && isOperationCompleted.get()) {
handleException(response, pipeline);
}
attemptsLatch.countDown();
blocksLatch.countDown();
}
};
if (logger.isTraceEnabled())
logger.info("Submitting " + pipeline.getOperation().getSimpleName() + " request on node " + node.getId());
NonblockingStore store = nonblockingStores.get(node.getId());
store.submitDeleteRequest(key, version, callback, timeoutMs);
}
try {
long ellapsedNs = System.nanoTime() - beginTime;
long remainingNs = (timeoutMs * Time.NS_PER_MS) - ellapsedNs;
if (remainingNs > 0) {
blocksLatch.await(remainingNs, TimeUnit.NANOSECONDS);
}
} catch (InterruptedException e) {
if (logger.isEnabledFor(Level.WARN))
logger.warn(e, e);
}
if (processResponses(responses, pipeline))
return;
// wait for more responses in case we did not have enough successful
// response to achieve the required count
boolean quorumSatisfied = true;
if (pipelineData.getSuccesses() < required) {
long ellapsedNs = System.nanoTime() - beginTime;
long remainingNs = (timeoutMs * Time.NS_PER_MS) - ellapsedNs;
if (remainingNs > 0) {
try {
attemptsLatch.await(remainingNs, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
if (logger.isEnabledFor(Level.WARN))
logger.warn(e, e);
}
if (processResponses(responses, pipeline))
return;
}
if (pipelineData.getSuccesses() < required) {
pipelineData.setFatalError(new InsufficientOperationalNodesException(required + " " + pipeline.getOperation().getSimpleName() + "s required, but only " + pipelineData.getSuccesses() + " succeeded", pipelineData.getReplicationSet(), pipelineData.getNodes(), pipelineData.getFailedNodes(), pipelineData.getFailures()));
abortPipeline(pipeline);
quorumSatisfied = false;
}
}
if (quorumSatisfied) {
if (pipelineData.getZonesRequired() != null) {
int zonesSatisfied = pipelineData.getZoneResponses().size();
if (zonesSatisfied >= (pipelineData.getZonesRequired() + 1)) {
completePipeline(pipeline);
} else {
long timeMs = (System.nanoTime() - beginTime) / Time.NS_PER_MS;
if ((timeoutMs - timeMs) > 0) {
try {
attemptsLatch.await(timeoutMs - timeMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (logger.isEnabledFor(Level.WARN))
logger.warn(e, e);
}
if (processResponses(responses, pipeline))
return;
}
if (pipelineData.getZoneResponses().size() >= (pipelineData.getZonesRequired() + 1)) {
completePipeline(pipeline);
} else {
pipelineData.setFatalError(new InsufficientZoneResponsesException((pipelineData.getZonesRequired() + 1) + " " + pipeline.getOperation().getSimpleName() + "s required zone, but only " + zonesSatisfied + " succeeded"));
abortPipeline(pipeline);
}
}
} else {
completePipeline(pipeline);
}
}
}
use of voldemort.store.InsufficientOperationalNodesException in project voldemort by voldemort.
the class PerformParallelPutRequests method execute.
@Override
public void execute(final Pipeline pipeline) {
final Node masterNode = pipelineData.getMaster();
final List<Node> nodes = pipelineData.getNodes();
final Versioned<byte[]> versionedCopy = pipelineData.getVersionedCopy();
final Integer numNodesTouchedInSerialPut = nodes.indexOf(masterNode) + 1;
numNodesPendingResponse = nodes.size() - numNodesTouchedInSerialPut;
if (logger.isDebugEnabled())
logger.debug("PUT {key:" + key + "} MasterNode={id:" + masterNode.getId() + "} totalNodesToAsyncPut=" + numNodesPendingResponse);
// initiate parallel puts
for (int i = numNodesTouchedInSerialPut; i < nodes.size(); i++) {
final Node node = nodes.get(i);
pipelineData.incrementNodeIndex();
NonblockingStoreCallback callback = new NonblockingStoreCallback() {
@Override
public void requestComplete(Object result, long requestTime) {
boolean responseHandledByMaster = false;
if (logger.isDebugEnabled())
logger.debug("PUT {key:" + key + "} response received from node={id:" + node.getId() + "} in " + requestTime + " ms)");
Response<ByteArray, Object> response;
response = new Response<ByteArray, Object>(node, key, result, requestTime);
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} Parallel put thread trying to return result to main thread");
}
responseHandledByMaster = pipelineData.getSynchronizer().tryDelegateResponseHandling(response);
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} Master thread accepted the response: " + responseHandledByMaster);
}
if (!responseHandledByMaster) {
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} Master thread did not accept the response: will handle in worker thread");
}
if (PipelineRoutedStore.isSlopableFailure(response.getValue()) || response.getValue() instanceof QuotaExceededException) {
if (logger.isDebugEnabled())
logger.debug("PUT {key:" + key + "} failed on node={id:" + node.getId() + ",host:" + node.getHost() + "}");
if (isHintedHandoffEnabled()) {
boolean triedDelegateSlop = pipelineData.getSynchronizer().tryDelegateSlop(node);
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} triedDelegateSlop: " + triedDelegateSlop);
}
if (!triedDelegateSlop) {
Slop slop = new Slop(pipelineData.getStoreName(), Slop.Operation.PUT, key, versionedCopy.getValue(), transforms, node.getId(), new Date());
pipelineData.addFailedNode(node);
if (logger.isDebugEnabled())
logger.debug("PUT {key:" + key + "} Start registering Slop(node:" + node.getId() + ",host:" + node.getHost() + ")");
hintedHandoff.sendHintParallel(node, versionedCopy.getVersion(), slop);
if (logger.isDebugEnabled())
logger.debug("PUT {key:" + key + "} Sent out request to register Slop(node: " + node.getId() + ",host:" + node.getHost() + ")");
}
}
} else {
// the exception is ignorable
if (logger.isDebugEnabled()) {
if (result instanceof Exception) {
logger.debug("PUT {key:" + key + "} will not send hint. Response is ignorable exception: " + result.getClass().toString());
} else {
logger.debug("PUT {key:" + key + "} will not send hint. Response is success");
}
}
}
if (result instanceof Exception && !(result instanceof ObsoleteVersionException)) {
if (response.getValue() instanceof InvalidMetadataException) {
pipelineData.reportException((InvalidMetadataException) response.getValue());
logger.warn("Received invalid metadata problem after a successful " + pipeline.getOperation().getSimpleName() + " call on node " + node.getId() + ", store '" + pipelineData.getStoreName() + "'");
} else if (response.getValue() instanceof QuotaExceededException) {
/**
* TODO Not sure if we need to count this
* Exception for stats or silently ignore and
* just log a warning. While
* QuotaExceededException thrown from other
* places mean the operation failed, this one
* does not fail the operation but instead
* stores slops. Introduce a new Exception in
* client side to just monitor how mamy Async
* writes fail on exceeding Quota?
*/
logger.warn("Received QuotaExceededException after a successful " + pipeline.getOperation().getSimpleName() + " call on node " + node.getId() + ", store '" + pipelineData.getStoreName() + "', master-node '" + masterNode.getId() + "'");
} else {
handleResponseError(response, pipeline, failureDetector);
}
}
}
}
};
if (logger.isTraceEnabled())
logger.trace("Submitting " + pipeline.getOperation().getSimpleName() + " request on node " + node.getId() + " for key " + key);
NonblockingStore store = nonblockingStores.get(node.getId());
store.submitPutRequest(key, versionedCopy, transforms, callback, timeoutMs);
}
try {
boolean preferredSatisfied = false;
while (true) {
long elapsedNs = System.nanoTime() - pipelineData.getStartTimeNs();
long remainingNs = (timeoutMs * Time.NS_PER_MS) - elapsedNs;
remainingNs = Math.max(0, remainingNs);
// preferred check
if (numResponsesGot >= preferred - 1) {
preferredSatisfied = true;
}
quorumSatisfied = isQuorumSatisfied();
zonesSatisfied = isZonesSatisfied();
if (quorumSatisfied && zonesSatisfied && preferredSatisfied || remainingNs <= 0 || numNodesPendingResponse <= 0) {
pipelineData.getSynchronizer().cutoffHandling();
break;
} else {
if (logger.isTraceEnabled()) {
logger.trace("PUT {key:" + key + "} trying to poll from queue");
}
Response<ByteArray, Object> response = pipelineData.getSynchronizer().responseQueuePoll(remainingNs, TimeUnit.NANOSECONDS);
processResponse(response, pipeline);
if (logger.isTraceEnabled()) {
logger.trace("PUT {key:" + key + "} tried to poll from queue. Null?: " + (response == null) + " numResponsesGot:" + numResponsesGot + " parallelResponseToWait: " + numNodesPendingResponse + "; preferred-1: " + (preferred - 1) + "; preferredOK: " + preferredSatisfied + " quorumOK: " + quorumSatisfied + "; zoneOK: " + zonesSatisfied);
}
}
}
// leftover)
while (!pipelineData.getSynchronizer().responseQueueIsEmpty()) {
Response<ByteArray, Object> response = pipelineData.getSynchronizer().responseQueuePoll(0, TimeUnit.NANOSECONDS);
processResponse(response, pipeline);
}
quorumSatisfied = isQuorumSatisfied();
zonesSatisfied = isZonesSatisfied();
if (quorumSatisfied && zonesSatisfied) {
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} succeeded at parallel put stage");
}
pipelineData.getSynchronizer().disallowDelegateSlop();
pipeline.addEvent(completeEvent);
} else {
VoldemortException fatalError;
if (!quorumSatisfied) {
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} failed due to insufficient nodes. required=" + required + " success=" + pipelineData.getSuccesses());
}
fatalError = new InsufficientOperationalNodesException(required + " " + pipeline.getOperation().getSimpleName() + "s required, but only " + pipelineData.getSuccesses() + " succeeded", pipelineData.getReplicationSet(), pipelineData.getNodes(), pipelineData.getFailedNodes(), pipelineData.getFailures());
pipelineData.setFatalError(fatalError);
} else if (!zonesSatisfied) {
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} failed due to insufficient zones. required=" + pipelineData.getZonesRequired() + 1 + " success=" + pipelineData.getZoneResponses().size());
}
fatalError = new InsufficientZoneResponsesException((pipelineData.getZonesRequired() + 1) + " " + pipeline.getOperation().getSimpleName() + "s required zone, but only " + (pipelineData.getZoneResponses().size()) + " succeeded. Failing nodes : " + pipelineData.getFailedNodes());
pipelineData.setFatalError(fatalError);
}
pipeline.abort();
}
} catch (InterruptedException e) {
if (logger.isEnabledFor(Level.WARN))
logger.warn(e, e);
} catch (NoSuchElementException e) {
if (logger.isEnabledFor(Level.ERROR))
logger.error("Response Queue is empty. There may be a bug in PerformParallelPutRequest", e);
} finally {
if (logger.isDebugEnabled()) {
logger.debug("PUT {key:" + key + "} marking parallel put stage finished");
}
}
}
use of voldemort.store.InsufficientOperationalNodesException in project voldemort by voldemort.
the class ThreadPoolRoutedStore method getAll.
@Override
public Map<ByteArray, List<Versioned<byte[]>>> getAll(Iterable<ByteArray> keys, Map<ByteArray, byte[]> transforms) throws VoldemortException {
StoreUtils.assertValidKeys(keys);
Map<ByteArray, List<Versioned<byte[]>>> result = StoreUtils.newEmptyHashMap(keys);
// Keys for each node needed to satisfy storeDef.getPreferredReads() if
// no failures.
Map<Node, List<ByteArray>> nodeToKeysMap = Maps.newHashMap();
// Keep track of nodes per key that might be needed if there are
// failures during getAll
Map<ByteArray, List<Node>> keyToExtraNodesMap = Maps.newHashMap();
for (ByteArray key : keys) {
List<Node> availableNodes = availableNodes(routingStrategy.routeRequest(key.get()));
// quickly fail if there aren't enough nodes to meet the requirement
checkRequiredReads(availableNodes);
int preferredReads = storeDef.getPreferredReads();
List<Node> preferredNodes = Lists.newArrayListWithCapacity(preferredReads);
List<Node> extraNodes = Lists.newArrayListWithCapacity(3);
for (Node node : availableNodes) {
if (preferredNodes.size() < preferredReads)
preferredNodes.add(node);
else
extraNodes.add(node);
}
for (Node node : preferredNodes) {
List<ByteArray> nodeKeys = nodeToKeysMap.get(node);
if (nodeKeys == null) {
nodeKeys = Lists.newArrayList();
nodeToKeysMap.put(node, nodeKeys);
}
nodeKeys.add(key);
}
if (!extraNodes.isEmpty()) {
List<Node> nodes = keyToExtraNodesMap.get(key);
if (nodes == null)
keyToExtraNodesMap.put(key, extraNodes);
else
nodes.addAll(extraNodes);
}
}
List<Callable<GetAllResult>> callables = Lists.newArrayList();
for (Map.Entry<Node, List<ByteArray>> entry : nodeToKeysMap.entrySet()) {
final Node node = entry.getKey();
final Collection<ByteArray> nodeKeys = entry.getValue();
if (failureDetector.isAvailable(node))
callables.add(new GetAllCallable(node, nodeKeys, transforms));
}
// A list of thrown exceptions, indicating the number of failures
List<Throwable> failures = new CopyOnWriteArrayList<Throwable>();
List<NodeValue<ByteArray, byte[]>> nodeValues = Lists.newArrayList();
Map<ByteArray, MutableInt> keyToSuccessCount = Maps.newHashMap();
for (ByteArray key : keys) keyToSuccessCount.put(key, new MutableInt(0));
List<Future<GetAllResult>> futures;
long timeoutMs = timeoutConfig.getOperationTimeout(VoldemortOpCode.GET_ALL_OP_CODE);
try {
// TODO What to do about timeouts? They should be longer as getAll
// is likely to
// take longer. At the moment, it's just timeoutMs * 3, but should
// this be based on the number of the keys?
futures = executor.invokeAll(callables, timeoutMs * 3, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new InsufficientOperationalNodesException("getAll operation interrupted.", e);
}
for (Future<GetAllResult> f : futures) {
if (f.isCancelled()) {
logger.warn("Get operation timed out after " + timeoutMs + " ms.");
continue;
}
try {
GetAllResult getResult = f.get();
if (getResult.exception != null) {
if (getResult.exception instanceof VoldemortApplicationException) {
throw (VoldemortException) getResult.exception;
}
failures.add(getResult.exception);
continue;
}
for (ByteArray key : getResult.callable.nodeKeys) {
List<Versioned<byte[]>> retrieved = getResult.retrieved.get(key);
MutableInt successCount = keyToSuccessCount.get(key);
successCount.increment();
/*
* retrieved can be null if there are no values for the key
* provided
*/
if (retrieved != null) {
List<Versioned<byte[]>> existing = result.get(key);
if (existing == null)
result.put(key, Lists.newArrayList(retrieved));
else
existing.addAll(retrieved);
}
}
nodeValues.addAll(getResult.nodeValues);
} catch (InterruptedException e) {
throw new InsufficientOperationalNodesException("getAll operation interrupted.", e);
} catch (ExecutionException e) {
// should never happen
if (e.getCause() instanceof Error)
throw (Error) e.getCause();
else
logger.error(e.getMessage(), e);
}
}
for (ByteArray key : keys) {
MutableInt successCountWrapper = keyToSuccessCount.get(key);
int successCount = successCountWrapper.intValue();
if (successCount < storeDef.getPreferredReads()) {
List<Node> extraNodes = keyToExtraNodesMap.get(key);
if (extraNodes != null) {
for (Node node : extraNodes) {
long startNs = System.nanoTime();
try {
List<Versioned<byte[]>> values = innerStores.get(node.getId()).get(key, transforms == null ? null : transforms.get(key));
fillRepairReadsValues(nodeValues, key, node, values);
List<Versioned<byte[]>> versioneds = result.get(key);
if (versioneds == null)
result.put(key, Lists.newArrayList(values));
else
versioneds.addAll(values);
recordSuccess(node, startNs);
if (++successCount >= storeDef.getPreferredReads())
break;
} catch (UnreachableStoreException e) {
failures.add(e);
recordException(node, startNs, e);
} catch (VoldemortApplicationException e) {
throw e;
} catch (Exception e) {
logger.warn("Error in GET_ALL on node " + node.getId() + "(" + node.getHost() + ")", e);
failures.add(e);
}
}
}
}
successCountWrapper.setValue(successCount);
}
repairReads(nodeValues, repairReads && (transforms == null || transforms.size() == 0));
for (Map.Entry<ByteArray, MutableInt> mapEntry : keyToSuccessCount.entrySet()) {
int successCount = mapEntry.getValue().intValue();
if (successCount < storeDef.getRequiredReads())
throw new InsufficientOperationalNodesException(this.storeDef.getRequiredReads() + " reads required, but " + successCount + " succeeded.", failures);
}
return result;
}
use of voldemort.store.InsufficientOperationalNodesException in project voldemort by voldemort.
the class ThreadPoolRoutedStore method get.
/*
* 1. Attempt preferredReads, and then wait for these to complete 2. If we
* got all the reads we wanted, then we are done. 3. If not then continue
* serially attempting to read from each node until we get preferredReads or
* run out of nodes. 4. If we have multiple results do a read repair 5. If
* we have at least requiredReads return. Otherwise throw an exception.
*/
private <R> List<R> get(final ByteArray key, final byte[] transforms, StoreOp<R> fetcher, Function<List<GetResult<R>>, Void> preReturnProcedure) throws VoldemortException {
StoreUtils.assertValidKey(key);
final List<Node> nodes = availableNodes(routingStrategy.routeRequest(key.get()));
// quickly fail if there aren't enough nodes to meet the requirement
checkRequiredReads(nodes);
final List<GetResult<R>> retrieved = Lists.newArrayList();
// A count of the number of successful operations
int successes = 0;
// A list of thrown exceptions, indicating the number of failures
final List<Throwable> failures = new CopyOnWriteArrayList<Throwable>();
// Do the preferred number of reads in parallel
int attempts = Math.min(this.storeDef.getPreferredReads(), nodes.size());
int nodeIndex = 0;
List<Callable<GetResult<R>>> callables = Lists.newArrayListWithCapacity(attempts);
for (; nodeIndex < attempts; nodeIndex++) {
final Node node = nodes.get(nodeIndex);
callables.add(new GetCallable<R>(node, key, transforms, fetcher));
}
List<Future<GetResult<R>>> futures;
long timeoutMs = (fetcher == VERSION_OP) ? timeoutConfig.getOperationTimeout(VoldemortOpCode.GET_VERSION_OP_CODE) : timeoutConfig.getOperationTimeout(VoldemortOpCode.GET_OP_CODE);
try {
futures = executor.invokeAll(callables, timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new InsufficientOperationalNodesException("Get operation interrupted!", e);
}
for (Future<GetResult<R>> f : futures) {
if (f.isCancelled()) {
logger.warn("Get operation timed out after " + timeoutMs + " ms.");
continue;
}
try {
GetResult<R> getResult = f.get();
if (getResult.exception != null) {
if (getResult.exception instanceof VoldemortApplicationException) {
throw (VoldemortException) getResult.exception;
}
failures.add(getResult.exception);
continue;
}
++successes;
retrieved.add(getResult);
} catch (InterruptedException e) {
throw new InsufficientOperationalNodesException("Get operation interrupted!", e);
} catch (ExecutionException e) {
// part should never happen.
if (e.getCause() instanceof Error)
throw (Error) e.getCause();
else
logger.error(e.getMessage(), e);
}
}
// reads to make up for these.
while (successes < this.storeDef.getPreferredReads() && nodeIndex < nodes.size()) {
Node node = nodes.get(nodeIndex);
long startNs = System.nanoTime();
try {
retrieved.add(new GetResult<R>(node, key, fetcher.execute(innerStores.get(node.getId()), key, transforms), null));
++successes;
recordSuccess(node, startNs);
} catch (UnreachableStoreException e) {
failures.add(e);
recordException(node, startNs, e);
} catch (VoldemortApplicationException e) {
throw e;
} catch (Exception e) {
logger.warn("Error in GET on node " + node.getId() + "(" + node.getHost() + ")", e);
failures.add(e);
}
nodeIndex++;
}
if (logger.isTraceEnabled())
logger.trace("GET retrieved the following node values: " + formatNodeValues(retrieved));
if (preReturnProcedure != null)
preReturnProcedure.apply(retrieved);
if (successes >= this.storeDef.getRequiredReads()) {
List<R> result = Lists.newArrayListWithExpectedSize(retrieved.size());
for (GetResult<R> getResult : retrieved) result.addAll(getResult.retrieved);
return result;
} else
throw new InsufficientOperationalNodesException(this.storeDef.getRequiredReads() + " reads required, but " + successes + " succeeded.", failures);
}
Aggregations