6767import java .util .List ;
6868import java .util .Map ;
6969import java .util .Optional ;
70+ import java .util .SortedMap ;
7071import java .util .UUID ;
7172import java .util .stream .Collectors ;
7273
@@ -392,22 +393,94 @@ public void release() {
392393 }
393394
394395 protected static final PreviousSnapshot EMPTY_PREVIOUS_SNAPSHOT =
395- new PreviousSnapshot (Collections . emptyList () );
396+ new PreviousSnapshot (null , - 1L );
396397
397398 /** Previous snapshot with uploaded sst files. */
398399 protected static class PreviousSnapshot {
399400
400401 @ Nonnull private final Map <String , StreamStateHandle > confirmedSstFiles ;
401402
402- protected PreviousSnapshot (@ Nullable Collection <HandleAndLocalPath > confirmedSstFiles ) {
403+ /**
404+ * Constructor of PreviousSnapshot. Giving a map of uploaded sst files in previous
405+ * checkpoints, prune the sst files which have been re-uploaded in the following
406+ * checkpoints. The prune logic is used to resolve the mismatch between TM and JM due to
407+ * notification delay. Following steps for example:
408+ *
409+ * <ul>
410+ * <li>1) checkpoint 1 uses file 00001.SST uploaded as xxx.sst.
411+ * <li>2) checkpoint 2 uses the same file 00001.SST but re-uploads it as yyy.sst because
412+ * CP 1 wasn't yet confirmed.
413+ * <li>3) TM get a confirmation of checkpoint 1.
414+ * <li>4) JM completes checkpoint 2 and subsumes checkpoint 1 - removing xxx.sst.
415+ * <li>5) checkpoint 3 tries to re-use file 00001.SST uploaded as xxx.sst in checkpoint 1,
416+ * but it was deleted in (4) by JM.
417+ * </ul>
418+ *
419+ * @param currentUploadedSstFiles the sst files uploaded in previous checkpoints.
420+ * @param lastCompletedCheckpoint the last completed checkpoint id.
421+ */
422+ protected PreviousSnapshot (
423+ @ Nullable SortedMap <Long , Collection <HandleAndLocalPath >> currentUploadedSstFiles ,
424+ long lastCompletedCheckpoint ) {
403425 this .confirmedSstFiles =
404- confirmedSstFiles != null
405- ? confirmedSstFiles .stream ()
426+ currentUploadedSstFiles != null
427+ ? pruneFirstCheckpointSstFiles (
428+ currentUploadedSstFiles , lastCompletedCheckpoint )
429+ : Collections .emptyMap ();
430+ }
431+
432+ /**
433+ * The last completed checkpoint's uploaded sst files are all included, then for each
434+ * following checkpoint, if a sst file has been re-uploaded, remove it from the first
435+ * checkpoint's sst files.
436+ *
437+ * @param currentUploadedSstFiles the sst files uploaded in the following checkpoint.
438+ * @param lastCompletedCheckpoint the last completed checkpoint id.
439+ */
440+ private Map <String , StreamStateHandle > pruneFirstCheckpointSstFiles (
441+ @ Nonnull SortedMap <Long , Collection <HandleAndLocalPath >> currentUploadedSstFiles ,
442+ long lastCompletedCheckpoint ) {
443+ Map <String , StreamStateHandle > prunedSstFiles = null ;
444+ int removedCount = 0 ;
445+ for (Map .Entry <Long , Collection <HandleAndLocalPath >> entry :
446+ currentUploadedSstFiles .entrySet ()) {
447+ // Iterate checkpoints in ascending order of checkpoint id.
448+ if (entry .getKey () == lastCompletedCheckpoint ) {
449+ // The first checkpoint's uploaded sst files are all included.
450+ prunedSstFiles =
451+ entry .getValue ().stream ()
406452 .collect (
407453 Collectors .toMap (
408454 HandleAndLocalPath ::getLocalPath ,
409- HandleAndLocalPath ::getHandle ))
410- : Collections .emptyMap ();
455+ HandleAndLocalPath ::getHandle ));
456+ } else if (prunedSstFiles == null ) {
457+ // The last completed checkpoint's uploaded sst files are not existed.
458+ // So we skip the pruning process.
459+ break ;
460+ } else if (!prunedSstFiles .isEmpty ()) {
461+ // Prune sst files which have been re-uploaded in the following checkpoints.
462+ for (HandleAndLocalPath handleAndLocalPath : entry .getValue ()) {
463+ if (!(handleAndLocalPath .getHandle ()
464+ instanceof PlaceholderStreamStateHandle )) {
465+ // If it's not a placeholder handle, it means the sst file has been
466+ // re-uploaded in the following checkpoint.
467+ if (prunedSstFiles .remove (handleAndLocalPath .getLocalPath ()) != null ) {
468+ removedCount ++;
469+ }
470+ }
471+ }
472+ }
473+ }
474+ if (removedCount > 0 && LOG .isTraceEnabled ()) {
475+ LOG .trace (
476+ "Removed {} re-uploaded sst files from base file set for incremental "
477+ + "checkpoint. Base checkpoint id: {}" ,
478+ removedCount ,
479+ currentUploadedSstFiles .firstKey ());
480+ }
481+ return (prunedSstFiles != null && !prunedSstFiles .isEmpty ())
482+ ? Collections .unmodifiableMap (prunedSstFiles )
483+ : Collections .emptyMap ();
411484 }
412485
413486 protected Optional <StreamStateHandle > getUploaded (String filename ) {
@@ -425,5 +498,10 @@ protected Optional<StreamStateHandle> getUploaded(String filename) {
425498 return Optional .empty ();
426499 }
427500 }
501+
502+ @ Override
503+ public String toString () {
504+ return "PreviousSnapshot{" + "confirmedSstFiles=" + confirmedSstFiles + '}' ;
505+ }
428506 }
429507}
0 commit comments