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

com.azure.cosmos.implementation.changefeed.epkversion.PkRangeIdVersionLeaseStoreBootstrapperImpl Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.cosmos.implementation.changefeed.epkversion;

import com.azure.cosmos.implementation.CosmosSchedulers;
import com.azure.cosmos.implementation.Strings;
import com.azure.cosmos.implementation.changefeed.Bootstrapper;
import com.azure.cosmos.implementation.changefeed.LeaseStore;
import com.azure.cosmos.implementation.changefeed.LeaseStoreManager;
import com.azure.cosmos.implementation.changefeed.common.ChangeFeedMode;
import com.azure.cosmos.implementation.changefeed.common.ChangeFeedState;
import com.azure.cosmos.implementation.changefeed.common.LeaseVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

import java.time.Duration;

import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument;
import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull;

/***
 * In order to support merge (which requires epk version leases) and align with long term designs,
 * we have introduced a new API {@code ChangeFeedProcessorBuilder.handleLatestVersionChanges}, which internally use epk version leases.
 * Semantically it is the same for {@code ChangeFeedProcessorBuilder.handleChanges} which uses pkRangeId version leases.
 *
 * When customer onboard to the new API {@code ChangeFeedProcessorBuilder.handleLatestVersionChanges}, for a better experience,
 * we are going to bootstrap from the pkRangeId version lease store if it has been initialized
 * or initialize the pkRangeId version lease store to prevent it being initialized again through the old API {@code ChangeFeedProcessorBuilder.handleChanges}.
 */
public class PkRangeIdVersionLeaseStoreBootstrapperImpl implements Bootstrapper {
    private final Logger logger = LoggerFactory.getLogger(PkRangeIdVersionLeaseStoreBootstrapperImpl.class);
    private final PartitionSynchronizer synchronizer;
    private final LeaseStore leaseStore;
    private final LeaseStoreManager pkRangeIdVersionLeaseStoreManager;
    private final LeaseStoreManager epkRangeVersionLeaseStoreManager;
    private final Duration lockTime;
    private final Duration sleepTime;
    private final ChangeFeedMode changeFeedModeToStart;

    private volatile boolean isInitialized;
    private volatile boolean isLockAcquired;
    private volatile boolean isPkRangeIdVersionLeaseStoreLockAcquired;

    public PkRangeIdVersionLeaseStoreBootstrapperImpl(
        PartitionSynchronizer synchronizer,
        LeaseStore leaseStore,
        Duration lockTime,
        Duration sleepTime,
        LeaseStoreManager pkRangeIdVersionLeaseStoreManager,
        LeaseStoreManager epkRangeVersionLeaseStoreManager,
        ChangeFeedMode changeFeedModeToStart) {
        checkNotNull(synchronizer, "Argument 'synchronizer' can not be null");
        checkNotNull(leaseStore, "Argument 'leaseStore' can not be null");
        checkArgument(lockTime != null && this.isPositive(lockTime), "lockTime should be non-null and positive");
        checkArgument(sleepTime != null && this.isPositive(sleepTime), "sleepTime should be non-null and positive");
        checkArgument(
            pkRangeIdVersionLeaseStoreManager != null,
            "Argument 'pkRangeIdVersionLeaseStoreManager' should not be null");

        this.synchronizer = synchronizer;
        this.leaseStore = leaseStore;
        this.pkRangeIdVersionLeaseStoreManager = pkRangeIdVersionLeaseStoreManager;
        this.epkRangeVersionLeaseStoreManager = epkRangeVersionLeaseStoreManager;
        this.changeFeedModeToStart = changeFeedModeToStart;
        this.lockTime = lockTime;
        this.sleepTime = sleepTime;

        this.isInitialized = false;
    }

    private boolean isPositive(Duration duration) {
        return !duration.isNegative() && !duration.isZero();
    }

    @Override
    public Mono initialize() {
        this.isInitialized = false;

        return Mono.just(this)
            .flatMap(value -> this.leaseStore.isInitialized())
            .flatMap(initialized -> {
                this.isInitialized = initialized;

                if (initialized) {
                    return this.validateLeaseCFModeInteroperabilityForEpkRangeBasedLease();
                } else {
                    logger.info("Acquire initialization lock");
                    return this.acquireInitializationLock()
                        .flatMap(lockAcquired -> {

                            if (!lockAcquired) {
                                logger.info("Another instance is initializing the store");
                                return Mono.just(isLockAcquired).delayElement(this.sleepTime, CosmosSchedulers.COSMOS_PARALLEL);
                            } else {
                                return this.pkRangeIdVersionLeaseStoreManager.isInitialized()
                                    .flatMap(pkRangeIdVersionLeaseStoreInitialized -> {
                                        if (pkRangeIdVersionLeaseStoreInitialized) {
                                           return this.bootstrapFromPkRangeVersionLeases();
                                        } else {
                                           return this.bootstrapFromScratch();
                                        }
                                    });
                            }
                        })
                        .onErrorResume(throwable -> {
                            logger.warn("Unexpected exception caught while initializing the lock", throwable);
                            return Mono.just(this.isLockAcquired);
                        })
                        .flatMap(lockAcquired -> {
                            if (this.isLockAcquired) {
                                return this.leaseStore.releaseInitializationLock()
                                    .then(this.releasePkRangeIdVersionLeaseStoreLock());
                            }

                            return Mono.just(lockAcquired);
                        });
                }
            })
            .repeat(() -> !this.isInitialized)
            .then();
    }

    private Mono releasePkRangeIdVersionLeaseStoreLock() {
        if (this.isPkRangeIdVersionLeaseStoreLockAcquired) {
            return this.pkRangeIdVersionLeaseStoreManager.releaseInitializationLock();
        }

        return Mono.just(Boolean.FALSE);
    }

    private Mono bootstrapFromPkRangeVersionLeases() {
        return this.pkRangeIdVersionLeaseStoreManager.getAllLeases()
            .collectList()
            .flatMap(pkRangeIdVersionLeases -> {
                return this.synchronizer.createMissingLeases(pkRangeIdVersionLeases)
                    .thenReturn(pkRangeIdVersionLeases);
            })
            .flatMap(pkRangeIdVersionLeases -> {
                return this.leaseStore.markInitialized()
                    .flatMap(isInitialized -> {
                        if (isInitialized) {
                            return this.pkRangeIdVersionLeaseStoreManager.deleteAll(pkRangeIdVersionLeases);
                        }
                        return Mono.empty();
                    })
                    .thenReturn(this.isLockAcquired);

            });
    }

    private Mono validateLeaseCFModeInteroperabilityForEpkRangeBasedLease() {

        // fetches only 1 epk-based leases for a given lease prefix
        return this.epkRangeVersionLeaseStoreManager
            .getTopLeases(1)
            // pick one lease corresponding to a lease prefix (lease prefix denotes a unique feed)
            .next()
            .flatMap(lease -> {

                if (lease.getVersion() == LeaseVersion.EPK_RANGE_BASED_LEASE) {
                    if (!Strings.isNullOrEmpty(lease.getId())) {

                        if (!Strings.isNullOrEmpty(lease.getContinuationToken())) {
                            ChangeFeedState changeFeedState = ChangeFeedState.fromString(lease.getContinuationToken());

                            if (changeFeedState.getMode() != this.changeFeedModeToStart) {
                                return Mono.error(new IllegalStateException("Change feed mode in the pre-existing lease is : " + changeFeedState.getMode() + " while the expected change feed mode is : " + this.changeFeedModeToStart));
                            }
                        }
                    }
                }

                return Mono.empty();
            });
    }

    private Mono bootstrapFromScratch() {
        return this.synchronizer.createMissingLeases()
            .thenReturn(this)
            .flatMap(bootstrapper -> this.leaseStore.markInitialized())
            .flatMap(leaseStoreInitialized -> {
                if (leaseStoreInitialized) {
                    return this.pkRangeIdVersionLeaseStoreManager.markInitialized();
                }

                return Mono.empty();
            })
            .thenReturn(this.isLockAcquired);
    }

    private Mono acquireInitializationLock() {
        return this.leaseStore.acquireInitializationLock(this.lockTime)
            .flatMap(isLockAcquired -> {
                this.isLockAcquired = isLockAcquired;

                if (!isLockAcquired) {
                    return Mono.just(Boolean.FALSE);
                } else {
                    return this.pkRangeIdVersionLeaseStoreManager.isInitialized()
                        .flatMap(pkRangeIdVersionLeaseStoreInitialized -> {
                            if (pkRangeIdVersionLeaseStoreInitialized) {
                                return Mono.just(Boolean.TRUE);
                            } else {
                                return this.pkRangeIdVersionLeaseStoreManager.acquireInitializationLock(this.lockTime)
                                    .flatMap(pkRangeIdVersionLeaseStoreLockAcquired -> {
                                        this.isPkRangeIdVersionLeaseStoreLockAcquired = pkRangeIdVersionLeaseStoreLockAcquired;

                                        return Mono.just(this.isPkRangeIdVersionLeaseStoreLockAcquired);
                                    });
                            }
                        });
                }
            });
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy