All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.camunda.zeebe.backup.management.InProgressBackupImpl Maven / Gradle / Ivy

There is a newer version: 8.6.0-alpha5
Show newest version
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Zeebe Community License 1.1. You may not use this file
 * except in compliance with the Zeebe Community License 1.1.
 */
package io.camunda.zeebe.backup.management;

import io.camunda.zeebe.backup.api.Backup;
import io.camunda.zeebe.backup.api.BackupIdentifier;
import io.camunda.zeebe.backup.api.NamedFileSet;
import io.camunda.zeebe.backup.common.BackupDescriptorImpl;
import io.camunda.zeebe.backup.common.BackupImpl;
import io.camunda.zeebe.backup.common.NamedFileSetImpl;
import io.camunda.zeebe.scheduler.ConcurrencyControl;
import io.camunda.zeebe.scheduler.future.ActorFuture;
import io.camunda.zeebe.snapshots.PersistedSnapshot;
import io.camunda.zeebe.snapshots.PersistedSnapshotStore;
import io.camunda.zeebe.snapshots.SnapshotException.SnapshotNotFoundException;
import io.camunda.zeebe.snapshots.SnapshotReservation;
import io.camunda.zeebe.util.Either;
import io.camunda.zeebe.util.VersionUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class InProgressBackupImpl implements InProgressBackup {

  private static final Logger LOG = LoggerFactory.getLogger(InProgressBackupImpl.class);

  private static final String ERROR_MSG_NO_VALID_SNAPSHOT =
      "Cannot find a snapshot that can be included in the backup %d. All available snapshots (%s) have processedPosition or lastFollowupEventPosition > checkpointPosition %d";

  private final PersistedSnapshotStore snapshotStore;
  private final BackupIdentifier backupId;
  private final long checkpointPosition;
  private final int numberOfPartitions;
  private final ConcurrencyControl concurrencyControl;

  private final Path segmentsDirectory;

  private final Predicate isSegmentsFile;

  // Snapshot related data
  private boolean hasSnapshot = true;
  private Set availableValidSnapshots;
  private SnapshotReservation snapshotReservation;
  private PersistedSnapshot reservedSnapshot;
  private NamedFileSet snapshotFileSet;
  private NamedFileSet segmentsFileSet;

  InProgressBackupImpl(
      final PersistedSnapshotStore snapshotStore,
      final BackupIdentifier backupId,
      final long checkpointPosition,
      final int numberOfPartitions,
      final ConcurrencyControl concurrencyControl,
      final Path segmentsDirectory,
      final Predicate isSegmentsFile) {
    this.snapshotStore = snapshotStore;
    this.backupId = backupId;
    this.checkpointPosition = checkpointPosition;
    this.numberOfPartitions = numberOfPartitions;
    this.concurrencyControl = concurrencyControl;
    this.segmentsDirectory = segmentsDirectory;
    this.isSegmentsFile = isSegmentsFile;
  }

  @Override
  public long checkpointId() {
    return backupId.checkpointId();
  }

  @Override
  public long checkpointPosition() {
    return checkpointPosition;
  }

  @Override
  public BackupIdentifier id() {
    return backupId;
  }

  @Override
  public ActorFuture findValidSnapshot() {
    final ActorFuture result = concurrencyControl.createFuture();
    snapshotStore
        .getAvailableSnapshots()
        .onComplete(
            (snapshots, error) -> {
              if (error != null) {
                result.completeExceptionally(error);
              } else if (snapshots.isEmpty()) {
                // no snapshot is taken until now, so return successfully
                hasSnapshot = false;
                result.complete(null);
              } else {
                final var eitherSnapshots = findValidSnapshot(snapshots);
                if (eitherSnapshots.isLeft()) {
                  result.completeExceptionally(
                      new SnapshotNotFoundException(eitherSnapshots.getLeft()));
                } else {
                  availableValidSnapshots = eitherSnapshots.get();
                  result.complete(null);
                }
              }
            });

    return result;
  }

  @Override
  public ActorFuture reserveSnapshot() {
    final ActorFuture future = concurrencyControl.createFuture();
    if (hasSnapshot) {
      // Try reserve snapshot in the order - latest snapshot first
      final var snapshotIterator =
          availableValidSnapshots.stream()
              .sorted(Comparator.comparingLong(PersistedSnapshot::getCompactionBound).reversed())
              .iterator();

      tryReserveAnySnapshot(snapshotIterator, future);
    } else {
      // No snapshot to reserve
      future.complete(null);
    }

    return future;
  }

  @Override
  public ActorFuture findSnapshotFiles() {
    if (!hasSnapshot) {
      snapshotFileSet = new NamedFileSetImpl(Map.of());
      return concurrencyControl.createCompletedFuture();
    }

    final ActorFuture filesCollected = concurrencyControl.createFuture();

    final Path snapshotRoot = reservedSnapshot.getPath();
    try (final var stream = Files.list(snapshotRoot)) {
      final var snapshotFiles = stream.collect(Collectors.toSet());
      final var checksumFile = reservedSnapshot.getChecksumPath();

      final Map fileSet = new HashMap<>();
      snapshotFiles.forEach(
          path -> {
            final var name = snapshotRoot.relativize(path);
            fileSet.put(name.toString(), path);
          });

      fileSet.put(checksumFile.getFileName().toString(), checksumFile);

      snapshotFileSet = new NamedFileSetImpl(fileSet);

      filesCollected.complete(null);

    } catch (final IOException e) {
      filesCollected.completeExceptionally(e);
    }

    return filesCollected;
  }

  @Override
  public ActorFuture findSegmentFiles() {
    final ActorFuture filesCollected = concurrencyControl.createFuture();
    try (final var stream = Files.list(segmentsDirectory)) {
      final var fileSet =
          stream
              .filter(isSegmentsFile)
              .collect(
                  Collectors.toMap(
                      path -> segmentsDirectory.relativize(path).toString(), path -> path));

      if (fileSet.isEmpty()) {
        filesCollected.completeExceptionally(
            new IllegalStateException("Segments must not be empty"));
        return filesCollected;
      }

      segmentsFileSet = new NamedFileSetImpl(fileSet);
      filesCollected.complete(null);
    } catch (final IOException e) {
      filesCollected.completeExceptionally(e);
    }

    return filesCollected;
  }

  @Override
  public Backup createBackup() {
    final Optional snapshotId;

    if (hasSnapshot) {
      snapshotId = Optional.of(reservedSnapshot.getId());
    } else {
      snapshotId = Optional.empty();
    }

    final var backupDescriptor =
        new BackupDescriptorImpl(
            snapshotId, checkpointPosition, numberOfPartitions, VersionUtil.getVersion());
    return new BackupImpl(backupId, backupDescriptor, snapshotFileSet, segmentsFileSet);
  }

  @Override
  public void close() {
    if (snapshotReservation != null) {
      snapshotReservation.release();
      LOG.debug("Released reservation for snapshot {}", reservedSnapshot.getId());
    }
  }

  private Either> findValidSnapshot(
      final Set snapshots) {
    final var validSnapshots =
        snapshots.stream()
            .filter(s -> s.getMetadata().processedPosition() < checkpointPosition) // &&
            .filter(s -> s.getMetadata().lastFollowupEventPosition() < checkpointPosition)
            .collect(Collectors.toSet());

    if (validSnapshots.isEmpty()) {
      return Either.left(
          String.format(
              ERROR_MSG_NO_VALID_SNAPSHOT, checkpointId(), snapshots, checkpointPosition));
    } else {
      return Either.right(validSnapshots);
    }
  }

  private void tryReserveAnySnapshot(
      final Iterator snapshotIterator, final ActorFuture future) {
    final var snapshot = snapshotIterator.next();

    LOG.debug("Attempting to reserve snapshot {}", snapshot.getId());
    final ActorFuture reservationFuture = snapshot.reserve();
    reservationFuture.onComplete(
        (reservation, error) -> {
          if (error != null) {
            LOG.trace("Attempting to reserve snapshot {}, but failed", snapshot.getId(), error);
            if (snapshotIterator.hasNext()) {
              tryReserveAnySnapshot(snapshotIterator, future);
            } else {
              // fail future.
              future.completeExceptionally(
                  String.format(
                      "Attempted to reserve snapshots %s, but no snapshot could be reserved",
                      availableValidSnapshots),
                  error);
            }
          } else {
            // complete
            snapshotReservation = reservation;
            reservedSnapshot = snapshot;
            LOG.debug("Reserved snapshot {}", snapshot.getId());
            future.complete(null);
          }
        });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy