List<IndexValue> findAllIndexValuesForKey(StoreKey key, FileSpan fileSpan, EnumSet<IndexEntryType> types,
      ConcurrentSkipListMap<Offset, IndexSegment> indexSegments) throws StoreException {
    List<IndexValue> result = new ArrayList<>();
    if (fileSpan != null && fileSpan.isEmpty()) {
      return result;
    }
    final Timer.Context context = metrics.findTime.time();
    try {
      ConcurrentNavigableMap<Offset, IndexSegment> segmentsMapToSearch;
      if (fileSpan == null) {
        logger.trace("Searching all indexes for {} in the entire index", key);
        segmentsMapToSearch = indexSegments.descendingMap();
      } else {
        logger.trace("Searching all indexes for {} in index with fileSpan ranging from {} to {}", key,
            fileSpan.getStartOffset(), fileSpan.getEndOffset());
        Offset fromIndexOffset = indexSegments.floorKey(fileSpan.getStartOffset());
        Offset toIndexOffset = indexSegments.lowerKey(fileSpan.getEndOffset());
        if (toIndexOffset == null) {
          segmentsMapToSearch = new ConcurrentSkipListMap<>();
        } else {
          segmentsMapToSearch =
              indexSegments.subMap(fromIndexOffset == null ? indexSegments.firstKey() : fromIndexOffset, true,
                  indexSegments.lowerKey(fileSpan.getEndOffset()), true).descendingMap();
        }
        metrics.segmentSizeForExists.update(segmentsMapToSearch.size());
      }
      int segmentsSearched = 0;
      for (Map.Entry<Offset, IndexSegment> entry : segmentsMapToSearch.entrySet()) {
        segmentsSearched++;
        logger.trace("Index : {} searching all indexes with start offset {}", dataDir, entry.getKey());
        NavigableSet<IndexValue> values = entry.getValue().find(key);
        if (values != null) {
          Iterator<IndexValue> it = values.descendingIterator();
          while (it.hasNext()) {
            IndexValue value = it.next();
            if (fileSpan != null) {
              // Start <= value.offset < End
              if (value.getOffset().compareTo(fileSpan.getEndOffset()) >= 0) {
                continue;
              }
              if (value.getOffset().compareTo(fileSpan.getStartOffset()) < 0) {
                break;
              }
            }
            if ((types.contains(IndexEntryType.DELETE) && value.isDelete()) || (types.contains(IndexEntryType.UNDELETE)
                && value.isUndelete()) || (types.contains(IndexEntryType.TTL_UPDATE) && !value.isDelete()
                && !value.isUndelete() && value.isTtlUpdate()) || (types.contains(IndexEntryType.PUT)
                && value.isPut())) {
              // Add a copy of the value to the result since we return a modifiable list to the caller.
              result.add(new IndexValue(value));
            }
          }
        }
      }
      metrics.segmentsAccessedPerBlobCount.update(segmentsSearched);
    } finally {
      context.stop();
    }
    if (!result.isEmpty()) {
      logger.trace("Index: {} Returning values {}", dataDir, result);
    }
    return result.isEmpty() ? null : result;
  }
Finds all the {@link IndexValue}s associated with the given {@code key} that matches any of the provided {@code types}
if present in the index with the given {@code fileSpan} and return them in reversed chronological order. If there is
no matched {@link IndexValue}, this method would return null;
@param key the {@link StoreKey} whose {@link IndexValue} is required.
@param fileSpan {@link FileSpan} which specifies the range within which search should be made.
@param types the types of {@link IndexEntryType} to look for.
@param indexSegments the map of index segment start {@link Offset} to {@link IndexSegment} instances
@return The list of the {@link IndexValue}s for {@code key} conforming to one of the types {@code types} ordered
        from most to least recent.
@throws StoreException any error.
ambry-store/src/main/java/com/github/ambry/store/PersistentIndex.java