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

io.zeebe.logstreams.state.ReplicationController Maven / Gradle / Ivy

/*
 * 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.0. You may not use this file
 * except in compliance with the Zeebe Community License 1.0.
 */
package io.zeebe.logstreams.state;

import io.zeebe.logstreams.impl.Loggers;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.agrona.collections.Object2NullableObjectHashMap;
import org.slf4j.Logger;

final class ReplicationController {
  private static final Logger LOG = Loggers.SNAPSHOT_LOGGER;
  private static final ReplicationContext INVALID_SNAPSHOT = new ReplicationContext(-1, -1);

  private final SnapshotReplication replication;
  private final Map receivedSnapshots =
      new Object2NullableObjectHashMap<>();
  private final SnapshotReplicationMetrics metrics;

  private final SnapshotConsumer snapshotConsumer;

  ReplicationController(final SnapshotReplication replication, final SnapshotStorage storage) {
    this.replication = replication;
    this.snapshotConsumer = new FileSnapshotConsumer(storage, LOG);
    this.metrics = storage.getMetrics().getReplication();
    this.metrics.setCount(0);
  }

  void replicate(final String snapshotId, final int totalCount, final File snapshotChunkFile) {
    try {
      final SnapshotChunk chunkToReplicate =
          SnapshotChunkUtil.createSnapshotChunkFromFile(snapshotChunkFile, snapshotId, totalCount);
      replication.replicate(chunkToReplicate);
    } catch (final IOException ioe) {
      LOG.error(
          "Unexpected error on reading snapshot chunk from file '{}'.", snapshotChunkFile, ioe);
    }
  }

  /** Registering for consuming snapshot chunks. */
  void consumeReplicatedSnapshots() {
    replication.consume(this::consumeSnapshotChunk);
  }

  /**
   * This is called by the snapshot replication implementation on each snapshot chunk
   *
   * @param snapshotChunk the chunk to consume
   */
  private void consumeSnapshotChunk(final SnapshotChunk snapshotChunk) {
    final String snapshotId = snapshotChunk.getSnapshotId();
    final String chunkName = snapshotChunk.getChunkName();

    final ReplicationContext context =
        receivedSnapshots.computeIfAbsent(snapshotId, this::newReplication);
    if (context == INVALID_SNAPSHOT) {
      LOG.trace(
          "Ignore snapshot chunk {}, because snapshot {} is marked as invalid.",
          chunkName,
          snapshotId);
      return;
    }

    if (snapshotConsumer.consumeSnapshotChunk(snapshotChunk)) {
      validateWhenReceivedAllChunks(snapshotChunk, context);
    } else {
      markSnapshotAsInvalid(snapshotChunk);
    }
  }

  private void markSnapshotAsInvalid(final SnapshotChunk chunk) {
    snapshotConsumer.invalidateSnapshot(chunk.getSnapshotId());
    receivedSnapshots.put(chunk.getSnapshotId(), INVALID_SNAPSHOT);
    metrics.decrementCount();
  }

  private void validateWhenReceivedAllChunks(
      final SnapshotChunk snapshotChunk, final ReplicationContext context) {
    final int totalChunkCount = snapshotChunk.getTotalCount();
    context.chunkCount++;

    if (context.chunkCount == totalChunkCount) {
      LOG.debug(
          "Received all snapshot chunks ({}/{}), snapshot is valid",
          context.chunkCount,
          totalChunkCount);
      if (!tryToMarkSnapshotAsValid(snapshotChunk, context)) {
        LOG.debug("Failed to mark snapshot {} as valid", snapshotChunk.getSnapshotId());
      }
    } else {
      LOG.debug(
          "Waiting for more snapshot chunks, currently have {}/{}",
          context.chunkCount,
          totalChunkCount);
    }
  }

  private boolean tryToMarkSnapshotAsValid(
      final SnapshotChunk snapshotChunk, final ReplicationContext context) {
    if (snapshotConsumer.completeSnapshot(snapshotChunk.getSnapshotId())) {
      final var elapsed = System.currentTimeMillis() - context.startTimestamp;
      receivedSnapshots.remove(snapshotChunk.getSnapshotId());
      metrics.decrementCount();
      metrics.observeDuration(elapsed);

      return true;
    } else {
      markSnapshotAsInvalid(snapshotChunk);
      return false;
    }
  }

  private ReplicationContext newReplication(final String ignored) {
    final var context = new ReplicationContext(0L, System.currentTimeMillis());
    metrics.incrementCount();
    return context;
  }

  private static final class ReplicationContext {
    private final long startTimestamp;
    private long chunkCount;

    private ReplicationContext(final long chunkCount, final long startTimestamp) {
      this.chunkCount = chunkCount;
      this.startTimestamp = startTimestamp;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy