diff --git a/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupQueue.java b/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupQueue.java
index 515f0ebb01c..00c047171f1 100644
--- a/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupQueue.java
+++ b/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupQueue.java
@@ -24,140 +24,364 @@
import javax.annotation.concurrent.ThreadSafe;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.apache.fluss.utils.Preconditions.checkArgument;
+import static org.apache.fluss.utils.Preconditions.checkState;
/**
- * A queue that buffers the pending lookup operations and provides a list of {@link LookupQuery}
- * when call method {@link #drain()}.
+ * A queue that buffers pending lookup operations by lookup queue key and drains globally bounded
+ * batches.
+ *
+ *
Lookups within a queue key preserve FIFO order. A drain consumes one key continuously before
+ * moving to the next key. If a key still has pending lookups after the global batch is full, it is
+ * moved to the tail so the next drain starts from another key. Before a drained batch is sent, its
+ * keys are counted as in-flight until the corresponding requests complete.
*/
@ThreadSafe
@Internal
class LookupQueue {
- private volatile boolean closed;
- // buffering both the Lookup and PrefixLookup.
- // TODO This queue could be refactored into a memory-managed queue similar to
- // RecordAccumulator, which would significantly improve the efficiency of lookup batching. Trace
- // by https://github.com/apache/fluss/issues/2124
- private final ArrayBlockingQueue> lookupQueue;
- private final BlockingQueue> reEnqueuedLookupQueue;
+ private final ReentrantLock stateLock = new ReentrantLock();
+ private final Condition appendCondition = stateLock.newCondition();
+ private final Condition drainCondition = stateLock.newCondition();
+
+ private final Map>> lookupQueues;
+ private final Deque lookupOrder;
+ private final Deque> reEnqueuedLookups;
+ // Counts started send batches, including batches waiting to be submitted to the network.
+ private final Map inFlightRequestsByKey;
+ private final int queueSize;
private final int maxBatchSize;
+ private final int maxInFlightRequestsPerKey;
private final long batchTimeoutNanos;
+ private boolean closed;
+ private boolean forceClosed;
+ private int queuedSize;
+
LookupQueue(Configuration conf) {
- this.lookupQueue =
- new ArrayBlockingQueue<>(conf.get(ConfigOptions.CLIENT_LOOKUP_QUEUE_SIZE));
- this.reEnqueuedLookupQueue = new LinkedBlockingQueue<>();
+ this.queueSize = conf.get(ConfigOptions.CLIENT_LOOKUP_QUEUE_SIZE);
this.maxBatchSize = conf.get(ConfigOptions.CLIENT_LOOKUP_MAX_BATCH_SIZE);
+ this.maxInFlightRequestsPerKey =
+ conf.get(ConfigOptions.CLIENT_LOOKUP_MAX_INFLIGHT_REQUESTS_PER_BUCKET);
this.batchTimeoutNanos = conf.get(ConfigOptions.CLIENT_LOOKUP_BATCH_TIMEOUT).toNanos();
- this.closed = false;
+ checkArgument(queueSize > 0, "Lookup queue size must be greater than 0.");
+ checkArgument(maxBatchSize > 0, "Lookup batch size must be greater than 0.");
+ checkArgument(
+ maxInFlightRequestsPerKey > 0,
+ "Maximum in-flight lookup requests per lookup queue key must be greater than 0.");
+
+ this.lookupQueues = new HashMap<>();
+ this.lookupOrder = new ArrayDeque<>();
+ this.reEnqueuedLookups = new ArrayDeque<>();
+ this.inFlightRequestsByKey = new HashMap<>();
}
void appendLookup(AbstractLookupQuery> lookup) {
- if (closed) {
- throw new IllegalStateException(
- "Can not append lookup operation since the LookupQueue is closed.");
+ InterruptedException interruptedException = null;
+ stateLock.lock();
+ try {
+ while (queuedSize >= queueSize && !closed) {
+ try {
+ appendCondition.await();
+ } catch (InterruptedException e) {
+ interruptedException = e;
+ break;
+ }
+ }
+
+ if (interruptedException == null) {
+ if (closed) {
+ throw new IllegalStateException(
+ "Can not append lookup operation since the LookupQueue is closed.");
+ }
+
+ LookupQueueKey lookupQueueKey = LookupQueueKey.fromLookup(lookup);
+ Deque> lookupQueue = lookupQueues.get(lookupQueueKey);
+ if (lookupQueue == null) {
+ lookupQueue = new ArrayDeque<>();
+ lookupQueues.put(lookupQueueKey, lookupQueue);
+ lookupOrder.addLast(lookupQueueKey);
+ }
+ lookupQueue.addLast(lookup);
+ queuedSize++;
+ drainCondition.signal();
+ }
+ } finally {
+ stateLock.unlock();
}
- try {
- lookupQueue.put(lookup);
- } catch (InterruptedException e) {
- lookup.future().completeExceptionally(e);
+ if (interruptedException != null) {
+ Thread.currentThread().interrupt();
+ lookup.future().completeExceptionally(interruptedException);
}
}
+ /** Re-enqueues a retry without blocking an RPC callback thread on regular queue capacity. */
void reEnqueue(AbstractLookupQuery> lookup) {
- if (closed) {
- throw new IllegalStateException(
- "Can not re-enqueue lookup operation since the LookupQueue is closed.");
+ stateLock.lock();
+ try {
+ if (closed) {
+ throw new IllegalStateException(
+ "Can not re-enqueue lookup operation since the LookupQueue is closed.");
+ }
+ reEnqueuedLookups.addLast(lookup);
+ drainCondition.signal();
+ } finally {
+ stateLock.unlock();
}
+ }
+ boolean hasUnDrained() {
+ stateLock.lock();
try {
- reEnqueuedLookupQueue.put(lookup);
- } catch (InterruptedException e) {
- lookup.future().completeExceptionally(e);
+ return hasUnDrainedUnsafe();
+ } finally {
+ stateLock.unlock();
}
}
- boolean hasUnDrained() {
- return !lookupQueue.isEmpty() || !reEnqueuedLookupQueue.isEmpty();
+ /** Drain a globally bounded batch of lookup operations. */
+ List> drain() throws InterruptedException {
+ return drain(false);
}
- /** Drain a batch of {@link LookupQuery}s from the lookup queue. */
- List> drain() throws Exception {
- final long startNanos = System.nanoTime();
- List> lookupOperations = new ArrayList<>(maxBatchSize);
- int count = 0;
- while (true) {
- long waitNanos = batchTimeoutNanos - (System.nanoTime() - startNanos);
- if (waitNanos <= 0) {
- break;
+ /** Drain all lookup operations without waiting for the batch timeout. */
+ List> drainAll() throws InterruptedException {
+ return drain(true);
+ }
+
+ void startInFlightRequests(Set lookupQueueKeys) {
+ stateLock.lock();
+ try {
+ for (LookupQueueKey lookupQueueKey : lookupQueueKeys) {
+ inFlightRequestsByKey.merge(lookupQueueKey, 1, Integer::sum);
}
+ } finally {
+ stateLock.unlock();
+ }
+ }
- long nextRetryDelayNanos = Long.MAX_VALUE;
- int reEnqueuedToCheck = reEnqueuedLookupQueue.size();
- while (reEnqueuedToCheck > 0 && count < maxBatchSize) {
- AbstractLookupQuery> lookup = reEnqueuedLookupQueue.poll();
- if (lookup == null) {
- break;
+ void completeInFlightRequests(Set lookupQueueKeys) {
+ stateLock.lock();
+ try {
+ boolean keyBecameSendable = false;
+ for (LookupQueueKey lookupQueueKey : lookupQueueKeys) {
+ int inFlightRequests = inFlightRequestsByKey.getOrDefault(lookupQueueKey, 0);
+ checkState(
+ inFlightRequests > 0,
+ "No in-flight lookup request exists for lookup queue key %s.",
+ lookupQueueKey);
+ if (inFlightRequests == maxInFlightRequestsPerKey) {
+ keyBecameSendable = true;
}
- long retryDelayMs = lookup.nextRetryTimeMs() - System.currentTimeMillis();
- if (retryDelayMs <= 0) {
- lookupOperations.add(lookup);
- count++;
+ if (inFlightRequests == 1) {
+ inFlightRequestsByKey.remove(lookupQueueKey);
} else {
- nextRetryDelayNanos =
- Math.min(
- nextRetryDelayNanos,
- TimeUnit.MILLISECONDS.toNanos(retryDelayMs));
- reEnqueuedLookupQueue.add(lookup);
+ inFlightRequestsByKey.put(lookupQueueKey, inFlightRequests - 1);
}
- reEnqueuedToCheck--;
}
+ if (keyBecameSendable) {
+ drainCondition.signal();
+ }
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ public void close() {
+ close(false);
+ }
- long lookupWaitNanos = waitNanos;
- if (count == 0 && nextRetryDelayNanos != Long.MAX_VALUE) {
- lookupWaitNanos = Math.min(waitNanos, Math.max(1L, nextRetryDelayNanos));
+ void forceClose() {
+ close(true);
+ }
+
+ private void close(boolean forceClose) {
+ stateLock.lock();
+ try {
+ closed = true;
+ forceClosed |= forceClose;
+ appendCondition.signalAll();
+ drainCondition.signalAll();
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ private List> drain(boolean drainAll) throws InterruptedException {
+ final long startNanos = System.nanoTime();
+ final int drainLimit = drainAll ? Integer.MAX_VALUE : maxBatchSize;
+ List> lookupOperations = new ArrayList<>(maxBatchSize);
+ Set drainedKeys = new HashSet<>();
+ stateLock.lock();
+ try {
+ while (lookupOperations.size() < drainLimit) {
+ if (forceClosed) {
+ lookupOperations.clear();
+ return lookupOperations;
+ }
+
+ long nowNanos = System.nanoTime();
+ long nextRetryDelayNanos =
+ drainReEnqueuedLookups(
+ lookupOperations,
+ drainedKeys,
+ drainLimit,
+ drainAll,
+ System.currentTimeMillis());
+ drainLookups(lookupOperations, drainedKeys, drainLimit);
+
+ if (lookupOperations.size() >= drainLimit) {
+ return lookupOperations;
+ }
+ if (drainAll) {
+ if (!hasUnDrainedUnsafe()) {
+ return lookupOperations;
+ }
+ drainCondition.await();
+ continue;
+ }
+ if (closed) {
+ return lookupOperations;
+ }
+
+ long waitNanos = batchTimeoutNanos - (nowNanos - startNanos);
+ if (waitNanos <= 0) {
+ return lookupOperations;
+ }
+ if (nextRetryDelayNanos != Long.MAX_VALUE) {
+ waitNanos = Math.min(waitNanos, Math.max(1L, nextRetryDelayNanos));
+ }
+ drainCondition.awaitNanos(waitNanos);
}
- AbstractLookupQuery> lookup = lookupQueue.poll(lookupWaitNanos, TimeUnit.NANOSECONDS);
- if (lookup == null) {
- break;
+ return lookupOperations;
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ private long drainReEnqueuedLookups(
+ List> lookupOperations,
+ Set drainedKeys,
+ int drainLimit,
+ boolean drainAll,
+ long nowMs) {
+ long nextRetryDelayNanos = Long.MAX_VALUE;
+ int retriesToCheck = reEnqueuedLookups.size();
+ while (retriesToCheck > 0 && lookupOperations.size() < drainLimit) {
+ AbstractLookupQuery> lookup = reEnqueuedLookups.removeFirst();
+ long retryDelayMs = lookup.nextRetryTimeMs() - nowMs;
+ if (!drainAll && retryDelayMs > 0) {
+ nextRetryDelayNanos =
+ Math.min(nextRetryDelayNanos, TimeUnit.MILLISECONDS.toNanos(retryDelayMs));
+ reEnqueuedLookups.addLast(lookup);
+ } else if (!tryDrainKeyUnsafe(LookupQueueKey.fromLookup(lookup), drainedKeys)) {
+ reEnqueuedLookups.addLast(lookup);
+ } else {
+ lookupOperations.add(lookup);
}
- lookupOperations.add(lookup);
- count++;
- int transferred = lookupQueue.drainTo(lookupOperations, maxBatchSize - count);
- count += transferred;
- if (count >= maxBatchSize) {
- break;
+ retriesToCheck--;
+ }
+ return nextRetryDelayNanos;
+ }
+
+ private void drainLookups(
+ List> lookupOperations,
+ Set drainedKeys,
+ int drainLimit) {
+ int keysToCheck = lookupOrder.size();
+ int drainedLookups = 0;
+ while (keysToCheck > 0 && lookupOperations.size() < drainLimit) {
+ LookupQueueKey lookupQueueKey = lookupOrder.removeFirst();
+ Deque> lookupQueue = lookupQueues.get(lookupQueueKey);
+ checkState(
+ lookupQueue != null && !lookupQueue.isEmpty(),
+ "Lookup queue key %s is active without pending lookups.",
+ lookupQueueKey);
+
+ if (!tryDrainKeyUnsafe(lookupQueueKey, drainedKeys)) {
+ lookupOrder.addLast(lookupQueueKey);
+ keysToCheck--;
+ continue;
}
+
+ while (!lookupQueue.isEmpty() && lookupOperations.size() < drainLimit) {
+ lookupOperations.add(lookupQueue.removeFirst());
+ queuedSize--;
+ drainedLookups++;
+ }
+ if (lookupQueue.isEmpty()) {
+ lookupQueues.remove(lookupQueueKey);
+ } else {
+ lookupOrder.addLast(lookupQueueKey);
+ }
+ keysToCheck--;
+ }
+
+ if (drainedLookups > 0) {
+ appendCondition.signalAll();
}
- return lookupOperations;
}
- /** Drain all the {@link LookupQuery}s from the lookup queue. */
- List> drainAll() {
- List> lookupOperations = new ArrayList<>(lookupQueue.size());
- lookupQueue.drainTo(lookupOperations);
- reEnqueuedLookupQueue.drainTo(lookupOperations);
- return lookupOperations;
+ private boolean tryDrainKeyUnsafe(
+ LookupQueueKey lookupQueueKey, Set drainedKeys) {
+ if (drainedKeys.contains(lookupQueueKey)) {
+ return true;
+ }
+ if (!canSendMoreRequestsUnsafe(lookupQueueKey)) {
+ return false;
+ }
+ drainedKeys.add(lookupQueueKey);
+ return true;
}
- public void close() {
- closed = true;
+ private boolean canSendMoreRequestsUnsafe(LookupQueueKey lookupQueueKey) {
+ return inFlightRequestsByKey.getOrDefault(lookupQueueKey, 0) < maxInFlightRequestsPerKey;
+ }
+
+ private boolean hasUnDrainedUnsafe() {
+ return queuedSize > 0 || !reEnqueuedLookups.isEmpty();
}
@VisibleForTesting
- ArrayBlockingQueue> getLookupQueue() {
- return lookupQueue;
+ int queuedSize() {
+ stateLock.lock();
+ try {
+ return queuedSize;
+ } finally {
+ stateLock.unlock();
+ }
}
@VisibleForTesting
- BlockingQueue> getReEnqueuedLookupQueue() {
- return reEnqueuedLookupQueue;
+ int reEnqueuedLookupCount() {
+ stateLock.lock();
+ try {
+ return reEnqueuedLookups.size();
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ @VisibleForTesting
+ int inFlightRequestCount(LookupQueueKey lookupQueueKey) {
+ stateLock.lock();
+ try {
+ return inFlightRequestsByKey.getOrDefault(lookupQueueKey, 0);
+ } finally {
+ stateLock.unlock();
+ }
}
}
diff --git a/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupQueueKey.java b/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupQueueKey.java
new file mode 100644
index 00000000000..16e642a3071
--- /dev/null
+++ b/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupQueueKey.java
@@ -0,0 +1,83 @@
+/*
+ * 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.fluss.client.lookup;
+
+import org.apache.fluss.metadata.TableBucket;
+
+import java.util.Objects;
+
+/** Identifies lookup operations that can share one queue and one RPC request. */
+final class LookupQueueKey {
+ private final TableBucket tableBucket;
+ private final LookupType lookupType;
+ private final boolean historical;
+
+ private LookupQueueKey(TableBucket tableBucket, LookupType lookupType, boolean historical) {
+ this.tableBucket = tableBucket;
+ this.lookupType = lookupType;
+ this.historical = historical;
+ }
+
+ static LookupQueueKey of(TableBucket tableBucket, LookupType lookupType, boolean historical) {
+ return new LookupQueueKey(tableBucket, lookupType, historical);
+ }
+
+ static LookupQueueKey fromLookup(AbstractLookupQuery> lookup) {
+ return of(
+ lookup.tableBucket(), lookup.lookupType(), lookup.originalPartitionName() != null);
+ }
+
+ TableBucket tableBucket() {
+ return tableBucket;
+ }
+
+ LookupType lookupType() {
+ return lookupType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof LookupQueueKey)) {
+ return false;
+ }
+ LookupQueueKey that = (LookupQueueKey) o;
+ return historical == that.historical
+ && tableBucket.equals(that.tableBucket)
+ && lookupType == that.lookupType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(tableBucket, lookupType, historical);
+ }
+
+ @Override
+ public String toString() {
+ return "LookupQueueKey{"
+ + "tableBucket="
+ + tableBucket
+ + ", lookupType="
+ + lookupType
+ + ", historical="
+ + historical
+ + '}';
+ }
+}
diff --git a/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupSender.java b/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupSender.java
index d1b59a75cc2..a5840ee4987 100644
--- a/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupSender.java
+++ b/fluss-client/src/main/java/org/apache/fluss/client/lookup/LookupSender.java
@@ -51,11 +51,13 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import static org.apache.fluss.client.utils.ClientRpcMessageUtils.makeLookupRequest;
@@ -78,7 +80,7 @@ class LookupSender implements Runnable {
private final LookupQueue lookupQueue;
- private final Semaphore maxInFlightReuqestsSemaphore;
+ private final Semaphore maxInFlightRequestsSemaphore;
private final int maxRetries;
@@ -91,13 +93,13 @@ class LookupSender implements Runnable {
LookupSender(
MetadataUpdater metadataUpdater,
LookupQueue lookupQueue,
- int maxFlightRequests,
+ int maxInFlightRequests,
int maxRetries,
short acks,
int maxRequestTimeoutMs) {
this.metadataUpdater = metadataUpdater;
this.lookupQueue = lookupQueue;
- this.maxInFlightReuqestsSemaphore = new Semaphore(maxFlightRequests);
+ this.maxInFlightRequestsSemaphore = new Semaphore(maxInFlightRequests);
this.maxRetries = maxRetries;
this.running = true;
this.acks = acks;
@@ -123,7 +125,7 @@ public void run() {
// okay we stopped accepting requests but there may still be requests in the accumulator or
// waiting for acknowledgment, wait until these are completed.
// TODO Check the in flight request count in the accumulator.
- if (!forceClose && lookupQueue.hasUnDrained()) {
+ while (!forceClose && lookupQueue.hasUnDrained()) {
try {
runOnce(true);
} catch (Exception e) {
@@ -139,13 +141,14 @@ public void run() {
private void runOnce(boolean drainAll) throws Exception {
List> lookups =
drainAll ? lookupQueue.drainAll() : lookupQueue.drain();
+ if (lookups.isEmpty() || forceClose) {
+ return;
+ }
+
sendLookups(lookups);
}
private void sendLookups(List> lookups) throws Exception {
- if (lookups.isEmpty()) {
- return;
- }
// group by to lookup batches
Map, List>> lookupBatches =
groupByLeaderAndType(lookups);
@@ -165,33 +168,50 @@ private void sendLookups(List> lookups) throws Exception
private Map, List>> groupByLeaderAndType(
List> lookups) {
+ Map>> lookupsByQueueKey = new LinkedHashMap<>();
+ for (AbstractLookupQuery> lookup : lookups) {
+ lookupsByQueueKey
+ .computeIfAbsent(LookupQueueKey.fromLookup(lookup), key -> new ArrayList<>())
+ .add(lookup);
+ }
+
// -> lookup batches
Map, List>> lookupBatchesByLeader =
new HashMap<>();
- for (AbstractLookupQuery> lookup : lookups) {
+ for (Map.Entry>> entry :
+ lookupsByQueueKey.entrySet()) {
+ LookupQueueKey lookupQueueKey = entry.getKey();
+ List> lookupsForKey = entry.getValue();
+ AbstractLookupQuery> representativeLookup = lookupsForKey.get(0);
int leader;
// lookup the leader node
- TableBucket tb = lookup.tableBucket();
try {
// TODO Metadata requests are being sent too frequently here. consider first
// collecting the tables that need to be updated and then sending them together in
// one request.
- leader = metadataUpdater.leaderFor(lookup.tablePath(), tb);
+ leader =
+ metadataUpdater.leaderFor(
+ representativeLookup.tablePath(), lookupQueueKey.tableBucket());
} catch (PartitionNotExistException e) {
- // Metadata refresh confirmed that the queued lookup carries a deleted partition
- // id. Complete it instead of repeatedly enqueueing the stale TableBucket; a
- // primary key lookuper can then reroute by partition name.
- lookup.future().completeExceptionally(e);
+ // Metadata refresh confirmed that the queued lookups carry a deleted partition id.
+ // Complete them instead of repeatedly enqueueing the stale TableBucket; a primary
+ // key lookuper can then reroute by partition name.
+ lookupsForKey.forEach(lookup -> lookup.future().completeExceptionally(e));
continue;
} catch (Exception e) {
- // if leader is not found, re-enqueue the lookup to send again.
- LOG.warn("Failed to lookup the leader for {} when lookup", tb, e);
- reEnqueueLookup(lookup);
+ // if leader is not found, re-enqueue the lookups to send again.
+ LOG.warn(
+ "Failed to lookup the leader for {} when lookup",
+ lookupQueueKey.tableBucket(),
+ e);
+ lookupsForKey.forEach(this::reEnqueueLookup);
continue;
}
lookupBatchesByLeader
- .computeIfAbsent(Tuple2.of(leader, lookup.lookupType()), k -> new ArrayList<>())
- .add(lookup);
+ .computeIfAbsent(
+ Tuple2.of(leader, lookupQueueKey.lookupType()),
+ key -> new ArrayList<>())
+ .addAll(lookupsForKey);
}
return lookupBatchesByLeader;
}
@@ -225,37 +245,48 @@ private void sendLookupRequest(
.addLookup(lookup);
}
- TabletServerGateway gateway = metadataUpdater.newTabletServerClientForNode(destination);
+ TabletServerGateway gateway;
+ Throwable gatewayFailure;
+ try {
+ gateway = metadataUpdater.newTabletServerClientForNode(destination);
+ gatewayFailure = null;
+ } catch (Throwable t) {
+ gateway = null;
+ gatewayFailure = t;
+ }
if (gateway == null) {
+ if (gatewayFailure == null) {
+ gatewayFailure =
+ new LeaderNotAvailableException(
+ "Server " + destination + " is not found in metadata cache.");
+ }
+ final Throwable requestFailure = gatewayFailure;
lookupByTableId.forEach(
(tableId, lookupsByBatchKey) ->
handleLookupRequestException(
- new LeaderNotAvailableException(
- "Server "
- + destination
- + " is not found in metadata cache."),
- destination,
- lookupsByBatchKey.values()));
+ requestFailure, destination, lookupsByBatchKey.values()));
return;
}
+ final TabletServerGateway requestGateway = gateway;
lookupByTableId.forEach(
(tableId, lookupsByBatchKey) -> {
List