@Override
  public StoreInfo get(List<? extends StoreKey> ids, EnumSet<StoreGetOptions> storeGetOptions) throws StoreException {
    checkStarted();
    // allows concurrent gets
    final Timer.Context context = metrics.getResponse.time();
    if (ids.size() > 1 && ids.size() != new HashSet<>(ids).size()) {
      metrics.duplicateKeysInBatch.inc();
      throw new IllegalArgumentException("The list of IDs provided contains duplicates");
    }
    try {
      List<BlobReadOptions> readOptions = new ArrayList<BlobReadOptions>(ids.size());
      Map<StoreKey, MessageInfo> indexMessages = new HashMap<StoreKey, MessageInfo>(ids.size());
      for (StoreKey key : ids) {
        BlobReadOptions readInfo = index.getBlobReadInfo(key, storeGetOptions);
        readOptions.add(readInfo);
        indexMessages.put(key, readInfo.getMessageInfo());
        // validate accountId and containerId
        if (!key.isAccountContainerMatch(readInfo.getMessageInfo().getAccountId(),
            readInfo.getMessageInfo().getContainerId())) {
          if (config.storeValidateAuthorization) {
            throw new StoreException(
                "GET authorization failure. Key: " + key.getID() + " Actually accountId: " + readInfo.getMessageInfo()
                    .getAccountId() + " Actually containerId: " + readInfo.getMessageInfo().getContainerId(),
                StoreErrorCodes.AuthorizationFailure);
          } else {
            logger.warn("GET authorization failure. Key: {} Actually accountId: {} Actually containerId: {}",
                key.getID(), readInfo.getMessageInfo().getAccountId(), readInfo.getMessageInfo().getContainerId());
            metrics.getAuthorizationFailureCount.inc();
          }
        }
      }

      // We call onSuccess and onError in the MessageReadSet IOPHandler instead of at the end of this method call like
      // other write methods because we are not really reading any bytes from disks in the get method, we are only
      // returning an object that triggers the reads when in different thread.
      // Thus we have to to call onSuccess and onError when we actually read bytes from disk in MessageReadSet.
      MessageReadSet readSet = new StoreMessageReadSet(readOptions, new StoreMessageReadSet.IOPHandler() {
        @Override
        public void onSuccess() {
          BlobStore.this.onSuccess("GET");
        }

        @Override
        public void onError() {
          try {
            BlobStore.this.onError();
          } catch (Exception e) {
          }
        }
      });
      // We ensure that the metadata list is ordered with the order of the message read set view that the
      // log provides. This ensures ordering of all messages across the log and metadata from the index.
      List<MessageInfo> messageInfoList = new ArrayList<MessageInfo>(readSet.count());
      for (int i = 0; i < readSet.count(); i++) {
        messageInfoList.add(indexMessages.get(readSet.getKeyAt(i)));
      }
      return new StoreInfo(readSet, messageInfoList);
    } catch (StoreException e) {
      if (e.getErrorCode() == StoreErrorCodes.IOError) {
        onError();
      }
      throw e;
    } catch (Exception e) {
      throw new StoreException("Unknown exception while trying to fetch blobs from store " + dataDir, e,
          StoreErrorCodes.UnknownError);
    } finally {
      context.stop();
    }
  }
Check if the store is stale.
If yes, throw exception
ambry-store/src/main/java/com/github/ambry/store/BlobStore.java