private void copy() throws IOException, StoreException {
    setupState();
    List<LogSegmentName> logSegmentsUnderCompaction =
        compactionLog.getCompactionDetails().getLogSegmentsUnderCompaction();
    FileSpan duplicateSearchSpan = null;
    if (compactionLog.getCurrentIdx() > 0) {
      // only records in the very first log segment in the cycle could have been copied in a previous cycle
      logger.info("Continue compaction at cycle {} with {}", compactionLog.getCurrentIdx(), storeId);
      LogSegment firstLogSegment = srcLog.getSegment(logSegmentsUnderCompaction.get(0));
      LogSegment prevSegment = srcLog.getPrevSegment(firstLogSegment);
      logger.info("First log segment at this cycle is {}, previous log segment is {} with {}",
          firstLogSegment.getName(), prevSegment.getName(), storeId);
      if (prevSegment != null) {
        // duplicate data, if it exists, can only be in the log segment just before the first log segment in the cycle.
        Offset startOffset = new Offset(prevSegment.getName(), prevSegment.getStartOffset());
        Offset endOffset = new Offset(prevSegment.getName(), prevSegment.getEndOffset());
        duplicateSearchSpan = new FileSpan(startOffset, endOffset);
      }
      logger.info("Duplicate search span is {} for {}", duplicateSearchSpan, storeId);
    }

    for (LogSegmentName logSegmentName : logSegmentsUnderCompaction) {
      logger.info("Processing {} in {}", logSegmentName, storeId);
      LogSegment srcLogSegment = srcLog.getSegment(logSegmentName);
      Offset logSegmentEndOffset = new Offset(srcLogSegment.getName(), srcLogSegment.getEndOffset());
      if (needsCopying(logSegmentEndOffset) && !copyDataByLogSegment(srcLogSegment, duplicateSearchSpan)) {
        if (isActive) {
          // split the cycle only if there is no shutdown in progress
          logger.info("Splitting current cycle with segments {} at {} for {}", logSegmentsUnderCompaction,
              logSegmentName, storeId);
          compactionLog.splitCurrentCycle(logSegmentName);
        }
        break;
      }
      duplicateSearchSpan = null;
    }

    numSwapsUsed = tgtIndex.getLogSegmentCount();
    logger.debug("Swaps used to copy {} is {} for {}", compactionLog.getCompactionDetails(), numSwapsUsed, storeId);
    if (isActive) {
      // it is possible to double count based on the time at which shutdown occurs (if it occurs b/w this statement
      // and before the subsequent commit can take effect)
      long segmentCountDiff =
          compactionLog.getCompactionDetails().getLogSegmentsUnderCompaction().size() - numSwapsUsed;
      long savedBytes = srcLog.getSegmentCapacity() * segmentCountDiff; // segment size is 17,176,275,436 = 16 GB
      srcMetrics.compactionBytesReclaimedCount.inc(savedBytes); // the real reclaimed reserved space
    }
    tgtIndex.close(false);
    tgtLog.close(false);
    // persist the bloom of the "latest" index segment if it exists
    if (numSwapsUsed > 0) {
      tgtIndex.getIndexSegments().lastEntry().getValue().seal();
    } else {
      // there were no valid entries copied, return any temp segments back to the pool
      logger.trace("Cleaning up temp segments in {} because no swap spaces were used", storeId);
      cleanupUnusedTempSegments();
      fixAfterOffsetsInCompactionLogWhenTargetIndexIsEmpty();
    }
  }
Copies valid data from the source log segments into the swap spaces.
<p/>
Splits the compaction into two cycles if there aren't enough swap spaces and completes the copy for the current
cycle.
@throws IOException if there were I/O errors during copying.
@throws StoreException if there were exceptions reading to writing to store components.
ambry-store/src/main/java/com/github/ambry/store/BlobStoreCompactor.java