void performRecovery() throws StoreException {
    try {
      readCleanupTokenAndPopulateRecoveryRange();
      if (hardDeleteRecoveryRange.getSize() == 0) {
        return;
      }

      /* First create the readOptionsList */
      List<BlobReadOptions> readOptionsList = new ArrayList<>();
      List<byte[]> messageStoreRecoveryInfoList = new ArrayList<>();
      for (HardDeletePersistItem item : hardDeleteRecoveryRange.getHardDeletePersistItemList()) {
        readOptionsList.add(item.blobReadOptions);
        messageStoreRecoveryInfoList.add(item.messagesStoreRecoveryInfo);
      }
      hardDeleteRecoveryRange.clear();

        /* Next, perform the log write. The token file does not have to be persisted again as only entries that are
           currently in it are being hard deleted as part of recovery. */
      StoreMessageReadSet readSet = new StoreMessageReadSet(readOptionsList);
      Iterator<HardDeleteInfo> hardDeleteIterator =
          hardDelete.getHardDeleteMessages(readSet, factory, messageStoreRecoveryInfoList);

      Iterator<BlobReadOptions> readOptionsIterator = readOptionsList.iterator();
      while (hardDeleteIterator.hasNext()) {
        if (!enabled.get()) {
          throw new StoreException("Aborting hard deletes as store is shutting down",
              StoreErrorCodes.StoreShuttingDown);
        }
        HardDeleteInfo hardDeleteInfo = hardDeleteIterator.next();
        BlobReadOptions readOptions = readOptionsIterator.next();
        if (hardDeleteInfo == null) {
          metrics.hardDeleteFailedCount.inc(1);
        } else {
          LogSegment logSegment = log.getSegment(readOptions.getLogSegmentName());
          logSegment.writeFrom(hardDeleteInfo.getHardDeleteChannel(),
              readOptions.getOffset() + hardDeleteInfo.getStartOffsetInMessage(),
              hardDeleteInfo.getHardDeletedMessageSize());
          metrics.hardDeleteDoneCount.inc(1);
        }
      }
    } catch (IOException e) {
      metrics.hardDeleteExceptionsCount.inc();
      StoreErrorCodes errorCode = StoreException.resolveErrorCode(e);
      throw new StoreException(errorCode.toString() + " occurred while performing hard delete ", e, errorCode);
    }
      /* Now that all the blobs in the range were successfully hard deleted, the next time hard deletes can be resumed
         at recoveryEndToken. */
    startToken = endToken = recoveryEndToken;
  }
Does the recovery of hard deleted blobs (means redoing the hard deletes)

This method first populates hardDeleteRecoveryRange with the blob information read from the cleanup
token file. It then passes the messageStoreRecoveryInfo to the MessageStoreHardDelete component so that the latter
can use it for recovering the information that may be required to hard delete the messages that are being hard
deleted as part of recovery.

@throws StoreException on version mismatch.
ambry-store/src/main/java/com/github/ambry/store/HardDeleter.java