public Repo call()

in server/manager/src/main/java/org/apache/accumulo/manager/tableOps/merge/MergeTablets.java [67:238]


  public Repo<Manager> call(FateId fateId, Manager manager) throws Exception {
    KeyExtent range = data.getMergeExtent();
    log.debug("{} Merging metadata for {}", fateId, range);

    var opid = TabletOperationId.from(TabletOperationType.MERGING, fateId);
    Set<TabletAvailability> tabletAvailabilities = new HashSet<>();
    MetadataTime maxLogicalTime = null;
    List<ReferenceFile> dirs = new ArrayList<>();
    Map<StoredTabletFile,DataFileValue> newFiles = new HashMap<>();
    TabletMetadata firstTabletMeta = null;
    TabletMetadata lastTabletMeta = null;

    try (var tabletsMetadata = manager.getContext().getAmple().readTablets()
        .forTable(range.tableId()).overlapping(range.prevEndRow(), range.endRow()).build()) {

      int tabletsSeen = 0;

      KeyExtent prevExtent = null;

      for (var tabletMeta : tabletsMetadata) {
        Preconditions.checkState(lastTabletMeta == null,
            "%s unexpectedly saw multiple last tablets %s %s", fateId, tabletMeta.getExtent(),
            range);
        validateTablet(tabletMeta, fateId, opid, data.tableId);

        if (firstTabletMeta == null) {
          firstTabletMeta = Objects.requireNonNull(tabletMeta);
        }

        // determine if this is the last tablet in the merge range
        boolean isLastTablet = (range.endRow() == null && tabletMeta.getExtent().endRow() == null)
            || (range.endRow() != null && tabletMeta.getExtent().contains(range.endRow()));

        if (prevExtent == null) {
          prevExtent = tabletMeta.getExtent();
        } else {
          boolean pointsToPrevious =
              Objects.equals(prevExtent.endRow(), tabletMeta.getExtent().prevEndRow());
          boolean isAlreadyMerged = isLastTablet && tabletMeta.hasMerged()
              && Objects.equals(firstTabletMeta.getPrevEndRow(), tabletMeta.getPrevEndRow());

          // Need to ensure the tablets being merged form a proper linked list. In the case where
          // this operation is running a second time the last tablet will not form a linked list and
          // that is ok.
          Preconditions.checkState(pointsToPrevious || isAlreadyMerged,
              "%s unexpectedly saw a hole in the metadata table %s %s", fateId, prevExtent,
              tabletMeta.getExtent());
          prevExtent = tabletMeta.getExtent();
        }

        tabletsSeen++;

        // want to gather the following for all tablets, including the last tablet
        maxLogicalTime = TabletTime.maxMetadataTime(maxLogicalTime, tabletMeta.getTime());
        tabletAvailabilities.add(tabletMeta.getTabletAvailability());

        if (isLastTablet) {
          lastTabletMeta = tabletMeta;
        } else {
          // only expect to see the merged marker in the last tablet
          Preconditions.checkState(!tabletMeta.hasMerged(),
              "%s tablet %s has unexpected merge marker", fateId, tabletMeta.getExtent());

          // files for the last tablet need to specially handled, so only add other tablets files
          // here
          tabletMeta.getFilesMap().forEach((file, dfv) -> {
            newFiles.put(fenceFile(tabletMeta.getExtent(), file), dfv);
          });

          // queue all tablets dirs except the last tablets to be added as GC candidates
          dirs.add(new AllVolumesDirectory(range.tableId(), tabletMeta.getDirName()));
          if (dirs.size() > 1000) {
            Preconditions.checkState(tabletsSeen > 1);
            manager.getContext().getAmple().putGcFileAndDirCandidates(range.tableId(), dirs);
            dirs.clear();
          }
        }
      }

      if (tabletsSeen == 1) {
        // The merge range overlaps a single tablet, so there is nothing to do. This could be
        // because there was only a single tablet before merge started or this operation completed
        // but the process died and now its running a 2nd time.
        return new FinishTableRangeOp(data);
      }

      Preconditions.checkState(lastTabletMeta != null, "%s no tablets seen in range %s", opid,
          lastTabletMeta);
    }

    log.info("{} merge low tablet {}", fateId, firstTabletMeta.getExtent());
    log.info("{} merge high tablet {}", fateId, lastTabletMeta.getExtent());

    // Check if the last tablet was already updated, this could happen if a process died and this
    // code is running a 2nd time. If running a 2nd time it possible the last tablet was updated and
    // only a subset of the other tablets were deleted. If the last tablet was never updated, then
    // the merged marker should not exist
    if (!lastTabletMeta.hasMerged()) {
      // update the last tablet
      try (var tabletsMutator = manager.getContext().getAmple().conditionallyMutateTablets()) {
        var lastExtent = lastTabletMeta.getExtent();
        var tabletMutator = tabletsMutator.mutateTablet(lastExtent).requireOperation(opid)
            .requireAbsentLocation().requireAbsentLogs();

        // fence the files in the last tablet if needed
        lastTabletMeta.getFilesMap().forEach((file, dfv) -> {
          var fencedFile = fenceFile(lastExtent, file);
          // If the existing metadata does not match then we need to delete the old
          // and replace with a new range
          if (!fencedFile.equals(file)) {
            tabletMutator.deleteFile(file);
            tabletMutator.putFile(fencedFile, dfv);
          }
        });

        newFiles.forEach(tabletMutator::putFile);
        tabletMutator.putTime(maxLogicalTime);
        lastTabletMeta.getExternalCompactions().keySet()
            .forEach(tabletMutator::deleteExternalCompaction);
        tabletMutator.putTabletAvailability(
            DeleteRows.getMergeTabletAvailability(range, tabletAvailabilities));
        tabletMutator.putPrevEndRow(firstTabletMeta.getPrevEndRow());
        tabletMutator.putTabletMergeability(lastTabletMeta.getTabletMergeability());

        // scan entries are related to a hosted tablet, this tablet is not hosted so can safely
        // delete these
        lastTabletMeta.getScans().forEach(tabletMutator::deleteScan);

        if (lastTabletMeta.getHostingRequested()) {
          // The range of the tablet is changing, so let's delete the hosting requested column in
          // case this tablet does not actually need to be hosted.
          tabletMutator.deleteHostingRequested();
        }

        if (lastTabletMeta.getSuspend() != null) {
          // This no longer the exact tablet that was suspended, so let's delete the suspend marker.
          tabletMutator.deleteSuspension();
        }

        if (lastTabletMeta.getLast() != null) {
          // This is no longer the same tablet, so let's delete the last location.
          tabletMutator.deleteLocation(lastTabletMeta.getLast());
        }

        if (lastTabletMeta.getUnSplittable() != null) {
          // This is no longer the same tablet, so let's delete the unsplittable marker
          tabletMutator.deleteUnSplittable();
        }

        if (lastTabletMeta.getMigration() != null) {
          // This is no longer the same tablet, so delete the migration
          tabletMutator.deleteMigration();
        }

        // Set merged marker on the last tablet when we are finished
        // so we know that we already updated metadata if the process restarts
        tabletMutator.setMerged();

        // if the tablet no longer exists (because changed prev end row, then the update was
        // successful.
        tabletMutator.submit(Ample.RejectionHandler.acceptAbsentTablet());

        verifyAccepted(tabletsMutator.process(), fateId);
      }
    }

    // add gc candidates for the tablet dirs that being merged away, once these dirs are empty the
    // Accumulo GC will delete the dir
    manager.getContext().getAmple().putGcFileAndDirCandidates(range.tableId(), dirs);

    return new DeleteTablets(data, lastTabletMeta.getEndRow());
  }