private CompletableFuture<List<CloudBlobMetadata>> getBlobMetadataChunked(List<BlobId> blobIds) {
    CompletableFuture<List<CloudBlobMetadata>> resultFuture = new CompletableFuture<>();
    if (blobIds.isEmpty() || blobIds.size() > queryBatchSize) {
      resultFuture.completeExceptionally(new IllegalArgumentException("Invalid input list size: " + blobIds.size()));
      return resultFuture;
    }
    String quotedBlobIds = blobIds.stream().map(s -> '"' + s.getID() + '"').collect(Collectors.joining(","));
    String query = String.format(BATCH_ID_QUERY_TEMPLATE, quotedBlobIds);
    String partitionPath = blobIds.get(0).getPartition().toPathString();
    cosmosDataAccessor.queryMetadataAsync(partitionPath, query, azureMetrics.missingKeysQueryTime)
        .whenComplete((blobMetadataList, throwable) -> {
          Exception ex = Utils.extractFutureExceptionCause(throwable);
          if (throwable != null) {
            resultFuture.completeExceptionally(toCloudStorageException(
                "Failed to query metadata for " + blobIds.size() + " blobs in partition " + partitionPath, ex));
          } else {
            resultFuture.complete(blobMetadataList);
          }
        });
    return resultFuture;
  }
Get metadata for specified list of blobs asynchronously.
@param blobIds {@link List} of {@link BlobId}s to get metadata of.
@return a {@link CompletableFuture} that will eventually contain either the {@link List} of
        {@link CloudBlobMetadata} for the blobs list or an exception if an error occurred.
ambry-cloud/src/main/java/com/github/ambry/cloud/azure/AzureCloudDestination.java