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