ONOS-2322: Support for periodic purging of ECMap tombstones
Change-Id: I6fe5475a472c383c4a51bd61446fba8f1dba1d37
Showing
5 changed files
with
142 additions
and
11 deletions
... | @@ -17,10 +17,10 @@ package org.onosproject.store.primitives.impl; | ... | @@ -17,10 +17,10 @@ package org.onosproject.store.primitives.impl; |
17 | 17 | ||
18 | import com.google.common.base.MoreObjects; | 18 | import com.google.common.base.MoreObjects; |
19 | import com.google.common.collect.ImmutableMap; | 19 | import com.google.common.collect.ImmutableMap; |
20 | - | ||
21 | import org.onosproject.cluster.NodeId; | 20 | import org.onosproject.cluster.NodeId; |
22 | 21 | ||
23 | import java.util.Map; | 22 | import java.util.Map; |
23 | + | ||
24 | import static com.google.common.base.Preconditions.checkNotNull; | 24 | import static com.google.common.base.Preconditions.checkNotNull; |
25 | 25 | ||
26 | /** | 26 | /** |
... | @@ -28,6 +28,7 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -28,6 +28,7 @@ import static com.google.common.base.Preconditions.checkNotNull; |
28 | */ | 28 | */ |
29 | public class AntiEntropyAdvertisement<K> { | 29 | public class AntiEntropyAdvertisement<K> { |
30 | 30 | ||
31 | + private final long creationTime; | ||
31 | private final NodeId sender; | 32 | private final NodeId sender; |
32 | private final Map<K, MapValue.Digest> digest; | 33 | private final Map<K, MapValue.Digest> digest; |
33 | 34 | ||
... | @@ -39,11 +40,21 @@ public class AntiEntropyAdvertisement<K> { | ... | @@ -39,11 +40,21 @@ public class AntiEntropyAdvertisement<K> { |
39 | */ | 40 | */ |
40 | public AntiEntropyAdvertisement(NodeId sender, | 41 | public AntiEntropyAdvertisement(NodeId sender, |
41 | Map<K, MapValue.Digest> digest) { | 42 | Map<K, MapValue.Digest> digest) { |
43 | + this.creationTime = System.currentTimeMillis(); | ||
42 | this.sender = checkNotNull(sender); | 44 | this.sender = checkNotNull(sender); |
43 | this.digest = ImmutableMap.copyOf(checkNotNull(digest)); | 45 | this.digest = ImmutableMap.copyOf(checkNotNull(digest)); |
44 | } | 46 | } |
45 | 47 | ||
46 | /** | 48 | /** |
49 | + * Returns the ad creation time. | ||
50 | + * | ||
51 | + * @return ad creation time | ||
52 | + */ | ||
53 | + public long creationTime() { | ||
54 | + return creationTime; | ||
55 | + } | ||
56 | + | ||
57 | + /** | ||
47 | * Returns the sender's node ID. | 58 | * Returns the sender's node ID. |
48 | * | 59 | * |
49 | * @return the sender's node ID | 60 | * @return the sender's node ID | ... | ... |
core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/AntiEntropyResponse.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 | +/** | ||
19 | + * Status of anti-entropy exchange, returned by the receiver. | ||
20 | + * | ||
21 | + */ | ||
22 | +public enum AntiEntropyResponse { | ||
23 | + /** | ||
24 | + * Signifies a successfully processed anti-entropy message. | ||
25 | + */ | ||
26 | + PROCESSED, | ||
27 | + | ||
28 | + /** | ||
29 | + * Signifies a unexpected failure during anti-entropy message processing. | ||
30 | + */ | ||
31 | + FAILED, | ||
32 | + | ||
33 | + /** | ||
34 | + * Signifies a ignored anti-entropy message, potentially due to the receiver operating under high load. | ||
35 | + */ | ||
36 | + IGNORED | ||
37 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -102,6 +102,9 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -102,6 +102,9 @@ public class EventuallyConsistentMapImpl<K, V> |
102 | private final ExecutorService communicationExecutor; | 102 | private final ExecutorService communicationExecutor; |
103 | private final Map<NodeId, EventAccumulator> senderPending; | 103 | private final Map<NodeId, EventAccumulator> senderPending; |
104 | 104 | ||
105 | + private long previousTombstonePurgeTime; | ||
106 | + private final Map<NodeId, Long> antiEntropyTimes = Maps.newConcurrentMap(); | ||
107 | + | ||
105 | private final String mapName; | 108 | private final String mapName; |
106 | 109 | ||
107 | private volatile boolean destroyed = false; | 110 | private volatile boolean destroyed = false; |
... | @@ -250,8 +253,15 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -250,8 +253,15 @@ public class EventuallyConsistentMapImpl<K, V> |
250 | clusterCommunicator.addSubscriber(antiEntropyAdvertisementSubject, | 253 | clusterCommunicator.addSubscriber(antiEntropyAdvertisementSubject, |
251 | serializer::decode, | 254 | serializer::decode, |
252 | this::handleAntiEntropyAdvertisement, | 255 | this::handleAntiEntropyAdvertisement, |
256 | + serializer::encode, | ||
253 | this.backgroundExecutor); | 257 | this.backgroundExecutor); |
254 | 258 | ||
259 | + previousTombstonePurgeTime = 0; | ||
260 | + this.backgroundExecutor.scheduleWithFixedDelay(this::purgeTombstones, | ||
261 | + initialDelaySec, | ||
262 | + antiEntropyPeriod, | ||
263 | + TimeUnit.SECONDS); | ||
264 | + | ||
255 | this.tombstonesDisabled = tombstonesDisabled; | 265 | this.tombstonesDisabled = tombstonesDisabled; |
256 | this.lightweightAntiEntropy = !convergeFaster; | 266 | this.lightweightAntiEntropy = !convergeFaster; |
257 | } | 267 | } |
... | @@ -267,6 +277,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -267,6 +277,7 @@ public class EventuallyConsistentMapImpl<K, V> |
267 | .register(LogicalTimestamp.class) | 277 | .register(LogicalTimestamp.class) |
268 | .register(WallClockTimestamp.class) | 278 | .register(WallClockTimestamp.class) |
269 | .register(AntiEntropyAdvertisement.class) | 279 | .register(AntiEntropyAdvertisement.class) |
280 | + .register(AntiEntropyResponse.class) | ||
270 | .register(UpdateEntry.class) | 281 | .register(UpdateEntry.class) |
271 | .register(MapValue.class) | 282 | .register(MapValue.class) |
272 | .register(MapValue.Digest.class) | 283 | .register(MapValue.Digest.class) |
... | @@ -563,13 +574,17 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -563,13 +574,17 @@ public class EventuallyConsistentMapImpl<K, V> |
563 | } | 574 | } |
564 | 575 | ||
565 | private void sendAdvertisementToPeer(NodeId peer) { | 576 | private void sendAdvertisementToPeer(NodeId peer) { |
566 | - clusterCommunicator.unicast(createAdvertisement(), | 577 | + AntiEntropyAdvertisement<K> ad = createAdvertisement(); |
578 | + clusterCommunicator.sendAndReceive(ad, | ||
567 | antiEntropyAdvertisementSubject, | 579 | antiEntropyAdvertisementSubject, |
568 | serializer::encode, | 580 | serializer::encode, |
581 | + serializer::decode, | ||
569 | peer) | 582 | peer) |
570 | .whenComplete((result, error) -> { | 583 | .whenComplete((result, error) -> { |
571 | if (error != null) { | 584 | if (error != null) { |
572 | log.debug("Failed to send anti-entropy advertisement to {}", peer, error); | 585 | log.debug("Failed to send anti-entropy advertisement to {}", peer, error); |
586 | + } else if (result == AntiEntropyResponse.PROCESSED) { | ||
587 | + antiEntropyTimes.put(peer, ad.creationTime()); | ||
573 | } | 588 | } |
574 | }); | 589 | }); |
575 | } | 590 | } |
... | @@ -579,9 +594,9 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -579,9 +594,9 @@ public class EventuallyConsistentMapImpl<K, V> |
579 | ImmutableMap.copyOf(Maps.transformValues(items, MapValue::digest))); | 594 | ImmutableMap.copyOf(Maps.transformValues(items, MapValue::digest))); |
580 | } | 595 | } |
581 | 596 | ||
582 | - private void handleAntiEntropyAdvertisement(AntiEntropyAdvertisement<K> ad) { | 597 | + private AntiEntropyResponse handleAntiEntropyAdvertisement(AntiEntropyAdvertisement<K> ad) { |
583 | if (destroyed || underHighLoad()) { | 598 | if (destroyed || underHighLoad()) { |
584 | - return; | 599 | + return AntiEntropyResponse.IGNORED; |
585 | } | 600 | } |
586 | try { | 601 | try { |
587 | if (log.isTraceEnabled()) { | 602 | if (log.isTraceEnabled()) { |
... | @@ -600,7 +615,9 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -600,7 +615,9 @@ public class EventuallyConsistentMapImpl<K, V> |
600 | } | 615 | } |
601 | } catch (Exception e) { | 616 | } catch (Exception e) { |
602 | log.warn("Error handling anti-entropy advertisement", e); | 617 | log.warn("Error handling anti-entropy advertisement", e); |
618 | + return AntiEntropyResponse.FAILED; | ||
603 | } | 619 | } |
620 | + return AntiEntropyResponse.PROCESSED; | ||
604 | } | 621 | } |
605 | 622 | ||
606 | /** | 623 | /** |
... | @@ -634,13 +651,37 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -634,13 +651,37 @@ public class EventuallyConsistentMapImpl<K, V> |
634 | return externalEvents; | 651 | return externalEvents; |
635 | } | 652 | } |
636 | 653 | ||
654 | + private void purgeTombstones() { | ||
655 | + /* | ||
656 | + * In order to mitigate the resource exhausation that can ensue due to an ever-growing set | ||
657 | + * of tombstones we employ the following heuristic to purge old tombstones periodically. | ||
658 | + * First, we keep track of the time (local system time) when we were able to have a successful | ||
659 | + * AE exchange with each peer. The smallest (or oldest) such time across *all* peers is regarded | ||
660 | + * as the time before which all tombstones are considered safe to purge. | ||
661 | + */ | ||
662 | + if (tombstonesDisabled || antiEntropyTimes.size() != clusterService.getNodes().size() - 1) { | ||
663 | + return; | ||
664 | + } | ||
665 | + long currentSafeTombstonePurgeTime = antiEntropyTimes.values().stream().reduce(Math::min).orElse(0L); | ||
666 | + if (currentSafeTombstonePurgeTime == previousTombstonePurgeTime) { | ||
667 | + return; | ||
668 | + } | ||
669 | + List<Map.Entry<K, MapValue<V>>> tombStonesToDelete = items.entrySet() | ||
670 | + .stream() | ||
671 | + .filter(e -> e.getValue().isTombstone()) | ||
672 | + .filter(e -> e.getValue().creationTime() <= currentSafeTombstonePurgeTime) | ||
673 | + .collect(Collectors.toList()); | ||
674 | + previousTombstonePurgeTime = currentSafeTombstonePurgeTime; | ||
675 | + tombStonesToDelete.forEach(entry -> items.remove(entry.getKey(), entry.getValue())); | ||
676 | + } | ||
677 | + | ||
637 | private void processUpdates(Collection<UpdateEntry<K, V>> updates) { | 678 | private void processUpdates(Collection<UpdateEntry<K, V>> updates) { |
638 | if (destroyed) { | 679 | if (destroyed) { |
639 | return; | 680 | return; |
640 | } | 681 | } |
641 | updates.forEach(update -> { | 682 | updates.forEach(update -> { |
642 | final K key = update.key(); | 683 | final K key = update.key(); |
643 | - final MapValue<V> value = update.value(); | 684 | + final MapValue<V> value = update.value() == null ? null : update.value().copy(); |
644 | if (value == null || value.isTombstone()) { | 685 | if (value == null || value.isTombstone()) { |
645 | MapValue<V> previousValue = removeInternal(key, Optional.empty(), Optional.ofNullable(value)); | 686 | MapValue<V> previousValue = removeInternal(key, Optional.empty(), Optional.ofNullable(value)); |
646 | if (previousValue != null && previousValue.isAlive()) { | 687 | if (previousValue != null && previousValue.isAlive()) { | ... | ... |
... | @@ -30,6 +30,7 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -30,6 +30,7 @@ import static com.google.common.base.Preconditions.checkNotNull; |
30 | public class MapValue<V> implements Comparable<MapValue<V>> { | 30 | public class MapValue<V> implements Comparable<MapValue<V>> { |
31 | private final Timestamp timestamp; | 31 | private final Timestamp timestamp; |
32 | private final V value; | 32 | private final V value; |
33 | + private long creationTime; | ||
33 | 34 | ||
34 | /** | 35 | /** |
35 | * Creates a tombstone value with the specified timestamp. | 36 | * Creates a tombstone value with the specified timestamp. |
... | @@ -39,12 +40,35 @@ public class MapValue<V> implements Comparable<MapValue<V>> { | ... | @@ -39,12 +40,35 @@ public class MapValue<V> implements Comparable<MapValue<V>> { |
39 | * @param <U> value type | 40 | * @param <U> value type |
40 | */ | 41 | */ |
41 | public static <U> MapValue<U> tombstone(Timestamp timestamp) { | 42 | public static <U> MapValue<U> tombstone(Timestamp timestamp) { |
42 | - return new MapValue<>(null, timestamp); | 43 | + return new MapValue<>(null, timestamp, System.currentTimeMillis()); |
43 | } | 44 | } |
44 | 45 | ||
45 | public MapValue(V value, Timestamp timestamp) { | 46 | public MapValue(V value, Timestamp timestamp) { |
47 | + this(value, timestamp, System.currentTimeMillis()); | ||
48 | + } | ||
49 | + | ||
50 | + /** | ||
51 | + * Constructor. | ||
52 | + * | ||
53 | + * @param value value | ||
54 | + * @param timestamp value timestamp. | ||
55 | + * @param creationTime the system time (on local instance) of construction | ||
56 | + */ | ||
57 | + public MapValue(V value, Timestamp timestamp, long creationTime) { | ||
46 | this.value = value; | 58 | this.value = value; |
47 | this.timestamp = checkNotNull(timestamp, "Timestamp cannot be null"); | 59 | this.timestamp = checkNotNull(timestamp, "Timestamp cannot be null"); |
60 | + this.creationTime = creationTime; | ||
61 | + } | ||
62 | + | ||
63 | + /** | ||
64 | + * Creates a copy of MapValue. | ||
65 | + * <p> | ||
66 | + * The copy will have an updated creation time corresponding to when the copy was constructed. | ||
67 | + * | ||
68 | + * @return MapValue copy | ||
69 | + */ | ||
70 | + public MapValue<V> copy() { | ||
71 | + return new MapValue<>(this.value, this.timestamp, System.currentTimeMillis()); | ||
48 | } | 72 | } |
49 | 73 | ||
50 | public boolean isTombstone() { | 74 | public boolean isTombstone() { |
... | @@ -63,6 +87,10 @@ public class MapValue<V> implements Comparable<MapValue<V>> { | ... | @@ -63,6 +87,10 @@ public class MapValue<V> implements Comparable<MapValue<V>> { |
63 | return value; | 87 | return value; |
64 | } | 88 | } |
65 | 89 | ||
90 | + public long creationTime() { | ||
91 | + return creationTime; | ||
92 | + } | ||
93 | + | ||
66 | @Override | 94 | @Override |
67 | public int compareTo(MapValue<V> o) { | 95 | public int compareTo(MapValue<V> o) { |
68 | return this.timestamp.compareTo(o.timestamp); | 96 | return this.timestamp.compareTo(o.timestamp); | ... | ... |
... | @@ -103,7 +103,7 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -103,7 +103,7 @@ public class EventuallyConsistentMapImplTest { |
103 | new DefaultControllerNode(new NodeId("local"), IpAddress.valueOf(1)); | 103 | new DefaultControllerNode(new NodeId("local"), IpAddress.valueOf(1)); |
104 | 104 | ||
105 | private Consumer<Collection<UpdateEntry<String, String>>> updateHandler; | 105 | private Consumer<Collection<UpdateEntry<String, String>>> updateHandler; |
106 | - private Consumer<AntiEntropyAdvertisement<String>> antiEntropyHandler; | 106 | + private Function<AntiEntropyAdvertisement<String>, AntiEntropyResponse> antiEntropyHandler; |
107 | 107 | ||
108 | /* | 108 | /* |
109 | * Serialization is a bit tricky here. We need to serialize in the tests | 109 | * Serialization is a bit tricky here. We need to serialize in the tests |
... | @@ -144,9 +144,15 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -144,9 +144,15 @@ public class EventuallyConsistentMapImplTest { |
144 | // delegate to our ClusterCommunicationService implementation. This | 144 | // delegate to our ClusterCommunicationService implementation. This |
145 | // allows us to get a reference to the map's internal cluster message | 145 | // allows us to get a reference to the map's internal cluster message |
146 | // handlers so we can induce events coming in from a peer. | 146 | // handlers so we can induce events coming in from a peer. |
147 | - clusterCommunicator.<String>addSubscriber(anyObject(MessageSubject.class), | 147 | + clusterCommunicator.<Object>addSubscriber(anyObject(MessageSubject.class), |
148 | anyObject(Function.class), anyObject(Consumer.class), anyObject(Executor.class)); | 148 | anyObject(Function.class), anyObject(Consumer.class), anyObject(Executor.class)); |
149 | - expectLastCall().andDelegateTo(new TestClusterCommunicationService()).times(2); | 149 | + expectLastCall().andDelegateTo(new TestClusterCommunicationService()).times(1); |
150 | + clusterCommunicator.<Object, Object>addSubscriber(anyObject(MessageSubject.class), | ||
151 | + anyObject(Function.class), | ||
152 | + anyObject(Function.class), | ||
153 | + anyObject(Function.class), | ||
154 | + anyObject(Executor.class)); | ||
155 | + expectLastCall().andDelegateTo(new TestClusterCommunicationService()).times(1); | ||
150 | 156 | ||
151 | replay(clusterCommunicator); | 157 | replay(clusterCommunicator); |
152 | 158 | ||
... | @@ -798,8 +804,16 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -798,8 +804,16 @@ public class EventuallyConsistentMapImplTest { |
798 | Executor executor) { | 804 | Executor executor) { |
799 | if (subject.equals(UPDATE_MESSAGE_SUBJECT)) { | 805 | if (subject.equals(UPDATE_MESSAGE_SUBJECT)) { |
800 | updateHandler = (Consumer<Collection<UpdateEntry<String, String>>>) handler; | 806 | updateHandler = (Consumer<Collection<UpdateEntry<String, String>>>) handler; |
801 | - } else if (subject.equals(ANTI_ENTROPY_MESSAGE_SUBJECT)) { | 807 | + } else { |
802 | - antiEntropyHandler = (Consumer<AntiEntropyAdvertisement<String>>) handler; | 808 | + throw new RuntimeException("Unexpected message subject " + subject.toString()); |
809 | + } | ||
810 | + } | ||
811 | + | ||
812 | + @Override | ||
813 | + public <M, R> void addSubscriber(MessageSubject subject, | ||
814 | + Function<byte[], M> decoder, Function<M, R> handler, Function<R, byte[]> encoder, Executor executor) { | ||
815 | + if (subject.equals(ANTI_ENTROPY_MESSAGE_SUBJECT)) { | ||
816 | + antiEntropyHandler = (Function<AntiEntropyAdvertisement<String>, AntiEntropyResponse>) handler; | ||
803 | } else { | 817 | } else { |
804 | throw new RuntimeException("Unexpected message subject " + subject.toString()); | 818 | throw new RuntimeException("Unexpected message subject " + subject.toString()); |
805 | } | 819 | } | ... | ... |
-
Please register or login to post a comment