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

com.azure.cosmos.implementation.changefeed.common.AutoCheckpointer Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.cosmos.implementation.changefeed.common;

import com.azure.cosmos.implementation.changefeed.ChangeFeedObserver;
import com.azure.cosmos.implementation.changefeed.ChangeFeedObserverCloseReason;
import com.azure.cosmos.implementation.changefeed.ChangeFeedObserverContext;
import com.azure.cosmos.implementation.changefeed.CheckpointFrequency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Auto check-pointer implementation for {@link ChangeFeedObserver}.
 */
public class AutoCheckpointer implements ChangeFeedObserver {
    private final Logger logger = LoggerFactory.getLogger(AutoCheckpointer.class);
    private final CheckpointFrequency checkpointFrequency;
    private final ChangeFeedObserver observer;
    private final AtomicInteger processedDocCount;
    private volatile Instant lastCheckpointTime;

    public AutoCheckpointer(CheckpointFrequency checkpointFrequency, ChangeFeedObserver observer) {
        if (checkpointFrequency == null) {
            throw new IllegalArgumentException("checkpointFrequency");
        }

        if (observer == null) {
            throw new IllegalArgumentException("observer");
        }

        this.checkpointFrequency = checkpointFrequency;
        this.observer = observer;
        this.lastCheckpointTime = Instant.now();
        this.processedDocCount = new AtomicInteger();
    }

    @Override
    public void open(ChangeFeedObserverContext context) {
        this.observer.open(context);
    }

    @Override
    public void close(ChangeFeedObserverContext context, ChangeFeedObserverCloseReason reason) {
        this.observer.close(context, reason);
    }

    @Override
    public Mono processChanges(ChangeFeedObserverContext context, List docs) {
        return this.observer.processChanges(context, docs)
            .doOnError(throwable -> {
                logger.warn("Unexpected exception from thread {}", Thread.currentThread().getId(), throwable);
            })
            .then(this.afterProcessChanges(context));
    }

    private Mono afterProcessChanges(ChangeFeedObserverContext context) {
        this.processedDocCount.incrementAndGet();

        if (this.isCheckpointNeeded()) {
            return context.checkpoint()
                .doOnError(throwable -> {
                    logger.warn("Checkpoint failed; this worker will be killed", throwable);
                })
                .doOnSuccess((Void) -> {
                    this.processedDocCount.set(0);
                    this.lastCheckpointTime = Instant.now();
                })
                .then();
        }
        return Mono.empty();
    }

    private boolean isCheckpointNeeded() {
        if (this.checkpointFrequency.getProcessedDocumentCount() == 0 && this.checkpointFrequency.getTimeInterval() == null) {
            return true;
        }

        if (this.processedDocCount.get() >= this.checkpointFrequency.getProcessedDocumentCount()) {
            return true;
        }

        Duration delta = Duration.between(this.lastCheckpointTime, Instant.now());

        return delta.compareTo(this.checkpointFrequency.getTimeInterval()) >= 0;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy