Jon Hall
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
...@@ -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
......
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 }
......