Hash range hole confidence check

This commit is contained in:
Ashwin Giridharan 2020-05-02 03:22:59 -07:00
parent eb00229602
commit a6922d9d7e

View file

@ -17,6 +17,7 @@ package software.amazon.kinesis.coordinator;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull;
import lombok.Value; import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -61,6 +62,8 @@ class PeriodicShardSyncManager {
private static final long PERIODIC_SHARD_SYNC_INTERVAL_MILLIS = 5 * 60 * 1000L; private static final long PERIODIC_SHARD_SYNC_INTERVAL_MILLIS = 5 * 60 * 1000L;
private static final BigInteger MIN_HASH_KEY = BigInteger.ZERO; private static final BigInteger MIN_HASH_KEY = BigInteger.ZERO;
private static final BigInteger MAX_HASH_KEY = new BigInteger("2").pow(128).subtract(BigInteger.ONE); private static final BigInteger MAX_HASH_KEY = new BigInteger("2").pow(128).subtract(BigInteger.ONE);
private static final int CONSECUTIVE_HOLES_FOR_TRIGGERING_RECOVERY = 3;
private Map<StreamIdentifier, HashRangeHoleTracker> hashRangeHoleTrackerMap = new HashMap<>();
private final String workerId; private final String workerId;
private final LeaderDecider leaderDecider; private final LeaderDecider leaderDecider;
@ -145,6 +148,7 @@ class PeriodicShardSyncManager {
try { try {
final Map<StreamIdentifier, List<Lease>> streamToLeasesMap = getStreamToLeasesMap(currentStreamConfigMap.keySet()); final Map<StreamIdentifier, List<Lease>> streamToLeasesMap = getStreamToLeasesMap(currentStreamConfigMap.keySet());
for (Map.Entry<StreamIdentifier, StreamConfig> streamConfigEntry : currentStreamConfigMap.entrySet()) { for (Map.Entry<StreamIdentifier, StreamConfig> streamConfigEntry : currentStreamConfigMap.entrySet()) {
final ShardSyncTaskManager shardSyncTaskManager = shardSyncTaskManagerProvider.apply(streamConfigEntry.getValue()); final ShardSyncTaskManager shardSyncTaskManager = shardSyncTaskManagerProvider.apply(streamConfigEntry.getValue());
if (!shardSyncTaskManager.syncShardAndLeaseInfo()) { if (!shardSyncTaskManager.syncShardAndLeaseInfo()) {
log.warn( log.warn(
@ -160,18 +164,19 @@ class PeriodicShardSyncManager {
} }
} }
private Map<StreamIdentifier, List<Lease>> getStreamToLeasesMap(final Set<StreamIdentifier> streamIdentifiersToFilter) private Map<StreamIdentifier, List<Lease>> getStreamToLeasesMap(
final Set<StreamIdentifier> streamIdentifiersToFilter)
throws DependencyException, ProvisionedThroughputException, InvalidStateException { throws DependencyException, ProvisionedThroughputException, InvalidStateException {
final List<Lease> leases = leaseRefresher.listLeases(); final List<Lease> leases = leaseRefresher.listLeases();
if(!isMultiStreamingMode) { if (!isMultiStreamingMode) {
Validate.isTrue(streamIdentifiersToFilter.size() == 1); Validate.isTrue(streamIdentifiersToFilter.size() == 1);
return Collections.singletonMap(streamIdentifiersToFilter.iterator().next(), leases); return Collections.singletonMap(streamIdentifiersToFilter.iterator().next(), leases);
} else { } else {
final Map<StreamIdentifier, List<Lease>> streamToLeasesMap = new HashMap<>(); final Map<StreamIdentifier, List<Lease>> streamToLeasesMap = new HashMap<>();
for(Lease lease : leases) { for (Lease lease : leases) {
StreamIdentifier streamIdentifier = StreamIdentifier StreamIdentifier streamIdentifier = StreamIdentifier
.multiStreamInstance(((MultiStreamLease) lease).streamIdentifier()); .multiStreamInstance(((MultiStreamLease) lease).streamIdentifier());
if(streamIdentifiersToFilter.contains(streamIdentifier)) { if (streamIdentifiersToFilter.contains(streamIdentifier)) {
streamToLeasesMap.computeIfAbsent(streamIdentifier, s -> new ArrayList<>()).add(lease); streamToLeasesMap.computeIfAbsent(streamIdentifier, s -> new ArrayList<>()).add(lease);
} }
} }
@ -179,25 +184,37 @@ class PeriodicShardSyncManager {
} }
} }
/** // TODO : Catch exception
* Checks if the entire hash range is covered private boolean shouldDoShardSync(StreamIdentifier streamIdentifier, List<Lease> leases) {
* @return true if covered, false otherwise
*/
private boolean isHashRangeCompleteForLeases(List<Lease> leases) {
if (CollectionUtils.isNullOrEmpty(leases)) { if (CollectionUtils.isNullOrEmpty(leases)) {
return false; throw new IllegalArgumentException("No leases found to validate for the stream " + streamIdentifier);
}
// Check if there are any holes in the leases and return the first hole if present.
Optional<HashRangeHole> hashRangeHoleOpt = hasHoleInLeases(streamIdentifier, leases);
if (hashRangeHoleOpt.isPresent()) {
// If hole is present, check if the hole is detected consecutively in previous occurrences.
// If hole is determined with high confidence return true; return false otherwise
return hashRangeHoleTrackerMap.computeIfAbsent(streamIdentifier, s -> new HashRangeHoleTracker())
.hasHighConfidenceOfHoleWith(hashRangeHoleOpt.get());
} else { } else {
List<HashKeyRangeForLease> hashRangesForActiveLeases = leases.stream() // If hole is not present, clear any previous tracking for this stream and return false;
.filter(lease -> lease.checkpoint() != null && !lease.checkpoint().isShardEnd()) hashRangeHoleTrackerMap.remove(streamIdentifier);
.map(lease -> lease.hashKeyRangeForLease()) return false;
.collect(Collectors.toList());
return !checkForHoleInHashKeyRanges(hashRangesForActiveLeases, MIN_HASH_KEY, MAX_HASH_KEY).isPresent();
} }
} }
private Optional<HashRangeHole> hasHoleInLeases(StreamIdentifier streamIdentifier, List<Lease> leases) {
// Filter the hashranges of leases which has any checkpoint other than shard end.
List<HashKeyRangeForLease> hashRangesForActiveLeases = leases.stream()
.filter(lease -> lease.checkpoint() != null && !lease.checkpoint().isShardEnd())
.map(lease -> lease.hashKeyRangeForLease()).collect(Collectors.toList());
return checkForHoleInHashKeyRanges(streamIdentifier, hashRangesForActiveLeases, MIN_HASH_KEY, MAX_HASH_KEY);
}
@VisibleForTesting @VisibleForTesting
static Optional<HashRangeHole> checkForHoleInHashKeyRanges(List<HashKeyRangeForLease> hashKeyRanges, static Optional<HashRangeHole> checkForHoleInHashKeyRanges(StreamIdentifier streamIdentifier,
BigInteger minHashKey, BigInteger maxHashKey) { List<HashKeyRangeForLease> hashKeyRanges, BigInteger minHashKey, BigInteger maxHashKey) {
List<HashKeyRangeForLease> mergedHashKeyRanges = sortAndMergeOverlappingHashRanges(hashKeyRanges); List<HashKeyRangeForLease> mergedHashKeyRanges = sortAndMergeOverlappingHashRanges(hashKeyRanges);
if (!mergedHashKeyRanges.get(0).startingHashKey().equals(minHashKey) || !mergedHashKeyRanges if (!mergedHashKeyRanges.get(0).startingHashKey().equals(minHashKey) || !mergedHashKeyRanges
@ -225,8 +242,9 @@ class PeriodicShardSyncManager {
} }
@VisibleForTesting @VisibleForTesting
static List<HashKeyRangeForLease> sortAndMergeOverlappingHashRanges(List<HashKeyRangeForLease> hashKeyRanges) { static List<HashKeyRangeForLease> sortAndMergeOverlappingHashRanges(
if(hashKeyRanges.size() == 0 || hashKeyRanges.size() == 1) List<HashKeyRangeForLease> hashKeyRanges) {
if (hashKeyRanges.size() == 0 || hashKeyRanges.size() == 1)
return hashKeyRanges; return hashKeyRanges;
Collections.sort(hashKeyRanges, new HashKeyRangeComparator()); Collections.sort(hashKeyRanges, new HashKeyRangeComparator());
@ -257,6 +275,21 @@ class PeriodicShardSyncManager {
private final HashKeyRangeForLease hashRangeAtEndOfPossibleHole; private final HashKeyRangeForLease hashRangeAtEndOfPossibleHole;
} }
private static class HashRangeHoleTracker {
private HashRangeHole hashRangeHole;
private Integer numConsecutiveHoles;
public boolean hasHighConfidenceOfHoleWith(@NonNull HashRangeHole hashRangeHole) {
if (hashRangeHole.equals(this.hashRangeHole)) {
++this.numConsecutiveHoles;
} else {
this.hashRangeHole = hashRangeHole;
this.numConsecutiveHoles = 1;
}
return numConsecutiveHoles >= CONSECUTIVE_HOLES_FOR_TRIGGERING_RECOVERY;
}
}
/** /**
* Helper class to compare leases based on their hash range. * Helper class to compare leases based on their hash range.
*/ */
@ -264,8 +297,7 @@ class PeriodicShardSyncManager {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override public int compare(HashKeyRangeForLease hashKeyRange, HashKeyRangeForLease otherHashKeyRange) {
public int compare(HashKeyRangeForLease hashKeyRange, HashKeyRangeForLease otherHashKeyRange) {
return hashKeyRange.startingHashKey().compareTo(otherHashKeyRange.startingHashKey()); return hashKeyRange.startingHashKey().compareTo(otherHashKeyRange.startingHashKey());
} }
} }