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());
}