Committed by
Gerrit Code Review
[ONOS-3591] Anti-Entropy speed up via push/pull interaction
Adds an UpdateRequest message. This contains a set of keys that a node is missing updates for. The receiver will then send an UpdateEntry for each missing key to the requester. Change-Id: I2115f4a05833b51ae14d1191f09f083b5251f8ec
Showing
3 changed files
with
134 additions
and
14 deletions
| ... | @@ -25,6 +25,7 @@ import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.RE | ... | @@ -25,6 +25,7 @@ import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.RE |
| 25 | 25 | ||
| 26 | import java.util.Collection; | 26 | import java.util.Collection; |
| 27 | import java.util.Collections; | 27 | import java.util.Collections; |
| 28 | +import java.util.HashSet; | ||
| 28 | import java.util.List; | 29 | import java.util.List; |
| 29 | import java.util.Map; | 30 | import java.util.Map; |
| 30 | import java.util.Objects; | 31 | import java.util.Objects; |
| ... | @@ -90,6 +91,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -90,6 +91,7 @@ public class EventuallyConsistentMapImpl<K, V> |
| 90 | 91 | ||
| 91 | private final MessageSubject updateMessageSubject; | 92 | private final MessageSubject updateMessageSubject; |
| 92 | private final MessageSubject antiEntropyAdvertisementSubject; | 93 | private final MessageSubject antiEntropyAdvertisementSubject; |
| 94 | + private final MessageSubject updateRequestSubject; | ||
| 93 | 95 | ||
| 94 | private final Set<EventuallyConsistentMapListener<K, V>> listeners | 96 | private final Set<EventuallyConsistentMapListener<K, V>> listeners |
| 95 | = Sets.newCopyOnWriteArraySet(); | 97 | = Sets.newCopyOnWriteArraySet(); |
| ... | @@ -244,6 +246,12 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -244,6 +246,12 @@ public class EventuallyConsistentMapImpl<K, V> |
| 244 | serializer::encode, | 246 | serializer::encode, |
| 245 | this.backgroundExecutor); | 247 | this.backgroundExecutor); |
| 246 | 248 | ||
| 249 | + updateRequestSubject = new MessageSubject("ecm-" + mapName + "-update-request"); | ||
| 250 | + clusterCommunicator.addSubscriber(updateRequestSubject, | ||
| 251 | + serializer::decode, | ||
| 252 | + this::handleUpdateRequests, | ||
| 253 | + this.backgroundExecutor); | ||
| 254 | + | ||
| 247 | if (!tombstonesDisabled) { | 255 | if (!tombstonesDisabled) { |
| 248 | previousTombstonePurgeTime = 0; | 256 | previousTombstonePurgeTime = 0; |
| 249 | this.backgroundExecutor.scheduleWithFixedDelay(this::purgeTombstones, | 257 | this.backgroundExecutor.scheduleWithFixedDelay(this::purgeTombstones, |
| ... | @@ -513,6 +521,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -513,6 +521,7 @@ public class EventuallyConsistentMapImpl<K, V> |
| 513 | listeners.clear(); | 521 | listeners.clear(); |
| 514 | 522 | ||
| 515 | clusterCommunicator.removeSubscriber(updateMessageSubject); | 523 | clusterCommunicator.removeSubscriber(updateMessageSubject); |
| 524 | + clusterCommunicator.removeSubscriber(updateRequestSubject); | ||
| 516 | clusterCommunicator.removeSubscriber(antiEntropyAdvertisementSubject); | 525 | clusterCommunicator.removeSubscriber(antiEntropyAdvertisementSubject); |
| 517 | return CompletableFuture.completedFuture(null); | 526 | return CompletableFuture.completedFuture(null); |
| 518 | } | 527 | } |
| ... | @@ -579,6 +588,19 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -579,6 +588,19 @@ public class EventuallyConsistentMapImpl<K, V> |
| 579 | }); | 588 | }); |
| 580 | } | 589 | } |
| 581 | 590 | ||
| 591 | + private void sendUpdateRequestToPeer(NodeId peer, Set<K> keys) { | ||
| 592 | + UpdateRequest<K> request = new UpdateRequest<>(localNodeId, keys); | ||
| 593 | + clusterCommunicator.unicast(request, | ||
| 594 | + updateRequestSubject, | ||
| 595 | + serializer::encode, | ||
| 596 | + peer) | ||
| 597 | + .whenComplete((result, error) -> { | ||
| 598 | + if (error != null) { | ||
| 599 | + log.debug("Failed to send update request to {}", peer, error); | ||
| 600 | + } | ||
| 601 | + }); | ||
| 602 | + } | ||
| 603 | + | ||
| 582 | private AntiEntropyAdvertisement<K> createAdvertisement() { | 604 | private AntiEntropyAdvertisement<K> createAdvertisement() { |
| 583 | return new AntiEntropyAdvertisement<>(localNodeId, | 605 | return new AntiEntropyAdvertisement<>(localNodeId, |
| 584 | ImmutableMap.copyOf(Maps.transformValues(items, MapValue::digest))); | 606 | ImmutableMap.copyOf(Maps.transformValues(items, MapValue::digest))); |
| ... | @@ -591,18 +613,9 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -591,18 +613,9 @@ public class EventuallyConsistentMapImpl<K, V> |
| 591 | try { | 613 | try { |
| 592 | if (log.isTraceEnabled()) { | 614 | if (log.isTraceEnabled()) { |
| 593 | log.trace("Received anti-entropy advertisement from {} for {} with {} entries in it", | 615 | log.trace("Received anti-entropy advertisement from {} for {} with {} entries in it", |
| 594 | - mapName, ad.sender(), ad.digest().size()); | 616 | + ad.sender(), mapName, ad.digest().size()); |
| 595 | } | 617 | } |
| 596 | antiEntropyCheckLocalItems(ad).forEach(this::notifyListeners); | 618 | antiEntropyCheckLocalItems(ad).forEach(this::notifyListeners); |
| 597 | - | ||
| 598 | - if (!lightweightAntiEntropy) { | ||
| 599 | - // if remote ad has any entries that the local copy is missing, actively sync | ||
| 600 | - // TODO: Missing keys is not the way local copy can be behind. | ||
| 601 | - if (Sets.difference(ad.digest().keySet(), items.keySet()).size() > 0) { | ||
| 602 | - // TODO: Send ad for missing keys and for entries that are stale | ||
| 603 | - sendAdvertisementToPeer(ad.sender()); | ||
| 604 | - } | ||
| 605 | - } | ||
| 606 | } catch (Exception e) { | 619 | } catch (Exception e) { |
| 607 | log.warn("Error handling anti-entropy advertisement", e); | 620 | log.warn("Error handling anti-entropy advertisement", e); |
| 608 | return AntiEntropyResponse.FAILED; | 621 | return AntiEntropyResponse.FAILED; |
| ... | @@ -620,15 +633,20 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -620,15 +633,20 @@ public class EventuallyConsistentMapImpl<K, V> |
| 620 | AntiEntropyAdvertisement<K> ad) { | 633 | AntiEntropyAdvertisement<K> ad) { |
| 621 | final List<EventuallyConsistentMapEvent<K, V>> externalEvents = Lists.newLinkedList(); | 634 | final List<EventuallyConsistentMapEvent<K, V>> externalEvents = Lists.newLinkedList(); |
| 622 | final NodeId sender = ad.sender(); | 635 | final NodeId sender = ad.sender(); |
| 636 | + final List<NodeId> peers = ImmutableList.of(sender); | ||
| 637 | + Set<K> staleOrMissing = new HashSet<>(); | ||
| 638 | + Set<K> locallyUnknown = new HashSet<>(ad.digest().keySet()); | ||
| 639 | + | ||
| 623 | items.forEach((key, localValue) -> { | 640 | items.forEach((key, localValue) -> { |
| 641 | + locallyUnknown.remove(key); | ||
| 624 | MapValue.Digest remoteValueDigest = ad.digest().get(key); | 642 | MapValue.Digest remoteValueDigest = ad.digest().get(key); |
| 625 | if (remoteValueDigest == null || localValue.isNewerThan(remoteValueDigest.timestamp())) { | 643 | if (remoteValueDigest == null || localValue.isNewerThan(remoteValueDigest.timestamp())) { |
| 626 | // local value is more recent, push to sender | 644 | // local value is more recent, push to sender |
| 627 | - queueUpdate(new UpdateEntry<>(key, localValue), ImmutableList.of(sender)); | 645 | + queueUpdate(new UpdateEntry<>(key, localValue), peers); |
| 628 | - } | 646 | + } else if (remoteValueDigest != null |
| 629 | - if (remoteValueDigest != null | ||
| 630 | && remoteValueDigest.isNewerThan(localValue.digest()) | 647 | && remoteValueDigest.isNewerThan(localValue.digest()) |
| 631 | && remoteValueDigest.isTombstone()) { | 648 | && remoteValueDigest.isTombstone()) { |
| 649 | + // remote value is more recent and a tombstone: update local value | ||
| 632 | MapValue<V> tombstone = MapValue.tombstone(remoteValueDigest.timestamp()); | 650 | MapValue<V> tombstone = MapValue.tombstone(remoteValueDigest.timestamp()); |
| 633 | MapValue<V> previousValue = removeInternal(key, | 651 | MapValue<V> previousValue = removeInternal(key, |
| 634 | Optional.empty(), | 652 | Optional.empty(), |
| ... | @@ -636,14 +654,31 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -636,14 +654,31 @@ public class EventuallyConsistentMapImpl<K, V> |
| 636 | if (previousValue != null && previousValue.isAlive()) { | 654 | if (previousValue != null && previousValue.isAlive()) { |
| 637 | externalEvents.add(new EventuallyConsistentMapEvent<>(mapName, REMOVE, key, previousValue.get())); | 655 | externalEvents.add(new EventuallyConsistentMapEvent<>(mapName, REMOVE, key, previousValue.get())); |
| 638 | } | 656 | } |
| 657 | + } else if (remoteValueDigest.isNewerThan(localValue.digest())) { | ||
| 658 | + // Not a tombstone and remote is newer | ||
| 659 | + staleOrMissing.add(key); | ||
| 639 | } | 660 | } |
| 640 | }); | 661 | }); |
| 662 | + // Keys missing in local map | ||
| 663 | + staleOrMissing.addAll(locallyUnknown); | ||
| 664 | + // Request updates that we missed out on | ||
| 665 | + sendUpdateRequestToPeer(sender, staleOrMissing); | ||
| 641 | return externalEvents; | 666 | return externalEvents; |
| 642 | } | 667 | } |
| 643 | 668 | ||
| 669 | + private void handleUpdateRequests(UpdateRequest<K> request) { | ||
| 670 | + final Set<K> keys = request.keys(); | ||
| 671 | + final NodeId sender = request.sender(); | ||
| 672 | + final List<NodeId> peers = ImmutableList.of(sender); | ||
| 673 | + | ||
| 674 | + keys.forEach(key -> | ||
| 675 | + queueUpdate(new UpdateEntry<>(key, items.get(key)), peers) | ||
| 676 | + ); | ||
| 677 | + } | ||
| 678 | + | ||
| 644 | private void purgeTombstones() { | 679 | private void purgeTombstones() { |
| 645 | /* | 680 | /* |
| 646 | - * In order to mitigate the resource exhausation that can ensue due to an ever-growing set | 681 | + * In order to mitigate the resource exhaustion that can ensue due to an ever-growing set |
| 647 | * of tombstones we employ the following heuristic to purge old tombstones periodically. | 682 | * of tombstones we employ the following heuristic to purge old tombstones periodically. |
| 648 | * First, we keep track of the time (local system time) when we were able to have a successful | 683 | * First, we keep track of the time (local system time) when we were able to have a successful |
| 649 | * AE exchange with each peer. The smallest (or oldest) such time across *all* peers is regarded | 684 | * AE exchange with each peer. The smallest (or oldest) such time across *all* peers is regarded | ... | ... |
core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/UpdateRequest.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright 2016-present Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onosproject.store.primitives.impl; | ||
| 17 | + | ||
| 18 | +import com.google.common.base.MoreObjects; | ||
| 19 | +import com.google.common.collect.ImmutableSet; | ||
| 20 | +import org.onosproject.cluster.NodeId; | ||
| 21 | + | ||
| 22 | +import java.util.Set; | ||
| 23 | + | ||
| 24 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * Describes a request for update events in an EventuallyConsistentMap. | ||
| 28 | + */ | ||
| 29 | +final class UpdateRequest<K> { | ||
| 30 | + | ||
| 31 | + private final NodeId sender; | ||
| 32 | + private final Set<K> keys; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * Creates a new update request. | ||
| 36 | + * | ||
| 37 | + * @param sender the sender's node ID | ||
| 38 | + * @param keys keys requested | ||
| 39 | + */ | ||
| 40 | + public UpdateRequest(NodeId sender, Set<K> keys) { | ||
| 41 | + this.sender = checkNotNull(sender); | ||
| 42 | + this.keys = ImmutableSet.copyOf(keys); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * Returns the sender's node ID. | ||
| 47 | + * | ||
| 48 | + * @return the sender's node ID | ||
| 49 | + */ | ||
| 50 | + public NodeId sender() { | ||
| 51 | + return sender; | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * Returns the keys. | ||
| 56 | + * | ||
| 57 | + * @return the keys | ||
| 58 | + */ | ||
| 59 | + public Set<K> keys() { | ||
| 60 | + return keys; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Override | ||
| 64 | + public String toString() { | ||
| 65 | + return MoreObjects.toStringHelper(getClass()) | ||
| 66 | + .add("sender", sender) | ||
| 67 | + .add("keys", keys()) | ||
| 68 | + .toString(); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + @SuppressWarnings("unused") | ||
| 72 | + private UpdateRequest() { | ||
| 73 | + this.sender = null; | ||
| 74 | + this.keys = null; | ||
| 75 | + } | ||
| 76 | +} |
| ... | @@ -88,6 +88,8 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -88,6 +88,8 @@ public class EventuallyConsistentMapImplTest { |
| 88 | = new MessageSubject("ecm-" + MAP_NAME + "-update"); | 88 | = new MessageSubject("ecm-" + MAP_NAME + "-update"); |
| 89 | private static final MessageSubject ANTI_ENTROPY_MESSAGE_SUBJECT | 89 | private static final MessageSubject ANTI_ENTROPY_MESSAGE_SUBJECT |
| 90 | = new MessageSubject("ecm-" + MAP_NAME + "-anti-entropy"); | 90 | = new MessageSubject("ecm-" + MAP_NAME + "-anti-entropy"); |
| 91 | + private static final MessageSubject UPDATE_REQUEST_SUBJECT | ||
| 92 | + = new MessageSubject("ecm-" + MAP_NAME + "-update-request"); | ||
| 91 | 93 | ||
| 92 | private static final String KEY1 = "one"; | 94 | private static final String KEY1 = "one"; |
| 93 | private static final String KEY2 = "two"; | 95 | private static final String KEY2 = "two"; |
| ... | @@ -98,6 +100,7 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -98,6 +100,7 @@ public class EventuallyConsistentMapImplTest { |
| 98 | new DefaultControllerNode(new NodeId("local"), IpAddress.valueOf(1)); | 100 | new DefaultControllerNode(new NodeId("local"), IpAddress.valueOf(1)); |
| 99 | 101 | ||
| 100 | private Consumer<Collection<UpdateEntry<String, String>>> updateHandler; | 102 | private Consumer<Collection<UpdateEntry<String, String>>> updateHandler; |
| 103 | + private Consumer<Collection<UpdateRequest<String>>> requestHandler; | ||
| 101 | private Function<AntiEntropyAdvertisement<String>, AntiEntropyResponse> antiEntropyHandler; | 104 | private Function<AntiEntropyAdvertisement<String>, AntiEntropyResponse> antiEntropyHandler; |
| 102 | 105 | ||
| 103 | @Before | 106 | @Before |
| ... | @@ -123,6 +126,9 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -123,6 +126,9 @@ public class EventuallyConsistentMapImplTest { |
| 123 | anyObject(Function.class), | 126 | anyObject(Function.class), |
| 124 | anyObject(Executor.class)); | 127 | anyObject(Executor.class)); |
| 125 | expectLastCall().andDelegateTo(new TestClusterCommunicationService()).times(1); | 128 | expectLastCall().andDelegateTo(new TestClusterCommunicationService()).times(1); |
| 129 | + clusterCommunicator.<Object>addSubscriber(anyObject(MessageSubject.class), | ||
| 130 | + anyObject(Function.class), anyObject(Consumer.class), anyObject(Executor.class)); | ||
| 131 | + expectLastCall().andDelegateTo(new TestClusterCommunicationService()).times(1); | ||
| 126 | 132 | ||
| 127 | replay(clusterCommunicator); | 133 | replay(clusterCommunicator); |
| 128 | 134 | ||
| ... | @@ -627,6 +633,7 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -627,6 +633,7 @@ public class EventuallyConsistentMapImplTest { |
| 627 | @Test | 633 | @Test |
| 628 | public void testDestroy() throws Exception { | 634 | public void testDestroy() throws Exception { |
| 629 | clusterCommunicator.removeSubscriber(UPDATE_MESSAGE_SUBJECT); | 635 | clusterCommunicator.removeSubscriber(UPDATE_MESSAGE_SUBJECT); |
| 636 | + clusterCommunicator.removeSubscriber(UPDATE_REQUEST_SUBJECT); | ||
| 630 | clusterCommunicator.removeSubscriber(ANTI_ENTROPY_MESSAGE_SUBJECT); | 637 | clusterCommunicator.removeSubscriber(ANTI_ENTROPY_MESSAGE_SUBJECT); |
| 631 | 638 | ||
| 632 | replay(clusterCommunicator); | 639 | replay(clusterCommunicator); |
| ... | @@ -774,6 +781,8 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -774,6 +781,8 @@ public class EventuallyConsistentMapImplTest { |
| 774 | Executor executor) { | 781 | Executor executor) { |
| 775 | if (subject.equals(UPDATE_MESSAGE_SUBJECT)) { | 782 | if (subject.equals(UPDATE_MESSAGE_SUBJECT)) { |
| 776 | updateHandler = (Consumer<Collection<UpdateEntry<String, String>>>) handler; | 783 | updateHandler = (Consumer<Collection<UpdateEntry<String, String>>>) handler; |
| 784 | + } else if (subject.equals(UPDATE_REQUEST_SUBJECT)) { | ||
| 785 | + requestHandler = (Consumer<Collection<UpdateRequest<String>>>) handler; | ||
| 777 | } else { | 786 | } else { |
| 778 | throw new RuntimeException("Unexpected message subject " + subject.toString()); | 787 | throw new RuntimeException("Unexpected message subject " + subject.toString()); |
| 779 | } | 788 | } | ... | ... |
-
Please register or login to post a comment