IndexValue markAsDeleted(StoreKey id, FileSpan fileSpan, MessageInfo info, long deletionTimeMs, short lifeVersion)
      throws StoreException {
    boolean hasLifeVersion = IndexValue.hasLifeVersion(lifeVersion);
    validateFileSpan(fileSpan, true);
    IndexValue value = findKey(id);
    if (value == null && info == null) {
      throw new StoreException("Id " + id + " not present in index " + dataDir, StoreErrorCodes.IDNotFound);
    } else if (value != null) {
      if (hasLifeVersion) {
        // When this method is invoked in either recovery or replication, delete can follow any index value.
        if ((value.isDelete() && value.getLifeVersion() >= lifeVersion) || (value.getLifeVersion() > lifeVersion)) {
          throw new StoreException("LifeVersion conflict in index. Id " + id + " LifeVersion: " + value.getLifeVersion()
              + " Delete LifeVersion: " + lifeVersion, StoreErrorCodes.LifeVersionConflict);
        }
      } else {
        if (value.isDelete()) {
          throw new StoreException("Id " + id + " already deleted in index " + dataDir, StoreErrorCodes.IDDeleted);
        }
      }
    }
    long size = fileSpan.getEndOffset().getOffset() - fileSpan.getStartOffset().getOffset();
    IndexValue newValue;
    if (value == null) {
      // It is possible that the PUT has been cleaned by compaction or forceDelete by the replication
      if (!hasLifeVersion) {
        throw new StoreException("MessageInfo of delete carries invalid lifeVersion",
            StoreErrorCodes.InitializationError);
      }
      newValue =
          new IndexValue(size, fileSpan.getStartOffset(), IndexValue.FLAGS_DEFAULT_VALUE, info.getExpirationTimeInMs(),
              info.getOperationTimeMs(), info.getAccountId(), info.getContainerId(), lifeVersion);
      newValue.clearOriginalMessageOffset();
    } else {
      lifeVersion = hasLifeVersion ? lifeVersion : value.getLifeVersion();
      newValue =
          new IndexValue(value.getSize(), value.getOffset(), value.getFlags(), value.getExpiresAtMs(), deletionTimeMs,
              value.getAccountId(), value.getContainerId(), lifeVersion);
      newValue.setNewOffset(fileSpan.getStartOffset());
      // Only set the original message offset when the value is put
      if (!value.isPut()) {
        newValue.clearOriginalMessageOffset();
      }
      newValue.setNewSize(size);
    }
    newValue.clearFlag(IndexValue.Flags.Undelete_Index);
    newValue.setFlag(IndexValue.Flags.Delete_Index);
    addToIndex(new IndexEntry(id, newValue, null), fileSpan);
    return newValue;
  }
Marks the index entry represented by the key for delete
@param id The id of the entry that needs to be deleted
@param fileSpan The file span represented by this entry in the log
@param info this needs to be non-null in the case of recovery. Can be {@code null} otherwise. Used if the PUT
            record could not be found
@param deletionTimeMs deletion time of the blob. In-case of recovery, deletion time is obtained from {@code info}.
@param lifeVersion lifeVersion of this delete record.
@return the {@link IndexValue} of the delete record
@throws StoreException
ambry-store/src/main/java/com/github/ambry/store/PersistentIndex.java