private void forEachIndexEntry(IndexSegment indexSegment, long deleteReferenceTimeInMs, long expiryReferenceTimeInMs,
      FileSpan fileSpanUnderCompaction, Map<StoreKey, IndexFinalState> keyFinalStates, boolean removeFinalStateOnPut,
      IndexEntryAction indexEntryAction) throws StoreException {
    Objects.requireNonNull(indexEntryAction, "IndexEntryAction callback is null");
    ListIterator<IndexEntry> it = indexSegment.listIterator(indexSegment.size());
    while (it.hasPrevious()) {
      IndexEntry indexEntry = it.previous();
      IndexValue indexValue = indexEntry.getValue();
      StoreKey key = indexEntry.getKey();
      boolean isValid = false;
      if (indexValue.isDelete()) {
        if (keyFinalStates.containsKey(key)) {
          IndexFinalState state = keyFinalStates.get(key);
          if (state.isUndelete() || (state.isDelete() && state.getLifeVersion() != indexValue.getLifeVersion())
              || state.isTtlUpdate()) {
            // This DELETE is not valid, when the final state of this storeKey is
            // 1. UNDELETE, or
            // 2. DELETE, but the current lifeVersion is not the same, or
            // 3. TTL_UPDATE
          } else {
            isValid = true;
          }
        } else {
          long operationTimeInMs =
              indexValue.getOperationTimeInMs() == Utils.Infinite_Time ? indexSegment.getLastModifiedTimeMs()
                  : indexValue.getOperationTimeInMs();
          keyFinalStates.put(indexEntry.getKey(),
              new IndexFinalState(indexValue.getFlags(), operationTimeInMs, indexValue.getLifeVersion(),
                  indexValue.getSize(), indexValue.getExpiresAtMs(), indexValue.getOffset()));
          isValid = true;
        }
      } else if (indexValue.isUndelete()) {
        if (keyFinalStates.containsKey(key)) {
          IndexFinalState state = keyFinalStates.get(key);
          if (state.isDelete() || (state.getLifeVersion() != indexValue.getLifeVersion())) {
            // This UNDELETE is not valid, when the final state of this storeKey is
            // 1. DELETE, or
            // 2. the current lifeVersion is not the same
          } else {
            if (state.isTtlUpdate()) {
              indexValue.setExpiresAtMs(Utils.Infinite_Time);
            }
            isValid = !isExpired(indexValue.getExpiresAtMs(), expiryReferenceTimeInMs);
          }
        } else {
          long operationTimeInMs =
              indexValue.getOperationTimeInMs() == Utils.Infinite_Time ? indexSegment.getLastModifiedTimeMs()
                  : indexValue.getOperationTimeInMs();
          keyFinalStates.put(indexEntry.getKey(),
              new IndexFinalState(indexValue.getFlags(), operationTimeInMs, indexValue.getLifeVersion(),
                  indexValue.getSize(), indexValue.getExpiresAtMs(), indexValue.getOffset()));
          isValid = !isExpired(indexValue.getExpiresAtMs(), expiryReferenceTimeInMs);
        }
      } else if (indexValue.isTtlUpdate()) {
        isValid =
            isTtlUpdateEntryValid(key, indexValue, deleteReferenceTimeInMs, fileSpanUnderCompaction, keyFinalStates);
      } else {
        IndexFinalState finalState = keyFinalStates.get(key);
        if (finalState != null && finalState.isDelete() && finalState.getOperationTime() < deleteReferenceTimeInMs) {
          // Put is deleted before reference time, it's not valid.
        } else {
          if (finalState != null && finalState.isTtlUpdate()) {
            // indexValue is a clone of the existing IndexValue, changing IndexValue will not change existing one.
            indexValue.setExpiresAtMs(Utils.Infinite_Time);
          }
          isValid = !isExpired(indexValue.getExpiresAtMs(), expiryReferenceTimeInMs);
        }
      }
      indexEntryAction.accept(indexEntry, isValid);
      if (indexValue.isPut() && removeFinalStateOnPut) {
        keyFinalStates.remove(key);
      }
    }
  }
Perform an action each {@link IndexEntry} from a given {@link IndexSegment}. IndexEntry will be passed to the callback method
as the first parameter and a boolean value indicating if this IndexEntry is valid or not, will be passed to the callback method
as the second parameter.
@param indexSegment the {@link IndexSegment} where the entries came from
@param deleteReferenceTimeInMs the reference time in ms until which deletes are relevant
@param expiryReferenceTimeInMs the reference time in ms until which expiration are relevant
@param keyFinalStates a {@link Map} of key to {@link IndexFinalState}.
@param removeFinalStateOnPut if {@code True}, then remove the {@link IndexFinalState} from the given map {@code keyFinalStates}
                        when encountering PUT IndexValue. This method iterates through IndexValues from most recent one to
                        earliest one, so PUT IndexValue is the last IndexValue for the same key.
@param indexEntryAction the action to take on each {@link IndexEntry} found.
@throws StoreException if there are problems reading the index.
ambry-store/src/main/java/com/github/ambry/store/BlobStoreStats.java