Jonathan Hart
Committed by Ray Milkey

Don't run anti-entropy when under high load

Change-Id: I9e480708b9eced73da98e5c4cb27a18aeb08f09a
...@@ -19,6 +19,7 @@ import org.apache.commons.lang3.RandomUtils; ...@@ -19,6 +19,7 @@ import org.apache.commons.lang3.RandomUtils;
19 import org.apache.commons.lang3.mutable.MutableBoolean; 19 import org.apache.commons.lang3.mutable.MutableBoolean;
20 import org.apache.commons.lang3.tuple.Pair; 20 import org.apache.commons.lang3.tuple.Pair;
21 import org.onlab.util.KryoNamespace; 21 import org.onlab.util.KryoNamespace;
22 +import org.onlab.util.SlidingWindowCounter;
22 import org.onosproject.cluster.ClusterService; 23 import org.onosproject.cluster.ClusterService;
23 import org.onosproject.cluster.ControllerNode; 24 import org.onosproject.cluster.ControllerNode;
24 import org.onosproject.cluster.NodeId; 25 import org.onosproject.cluster.NodeId;
...@@ -49,6 +50,7 @@ import java.util.concurrent.ExecutorService; ...@@ -49,6 +50,7 @@ import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Executors; 50 import java.util.concurrent.Executors;
50 import java.util.concurrent.ScheduledExecutorService; 51 import java.util.concurrent.ScheduledExecutorService;
51 import java.util.concurrent.TimeUnit; 52 import java.util.concurrent.TimeUnit;
53 +import java.util.concurrent.atomic.AtomicLong;
52 import java.util.stream.Collectors; 54 import java.util.stream.Collectors;
53 55
54 import static com.google.common.base.Preconditions.checkNotNull; 56 import static com.google.common.base.Preconditions.checkNotNull;
...@@ -100,6 +102,12 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -100,6 +102,12 @@ public class EventuallyConsistentMapImpl<K, V>
100 private long periodSec = 5; 102 private long periodSec = 5;
101 private boolean lightweightAntiEntropy = true; 103 private boolean lightweightAntiEntropy = true;
102 104
105 + private static final int WINDOW_SIZE = 5;
106 + private static final int HIGH_LOAD_THRESHOLD = 0;
107 + private static final int LOAD_WINDOW = 2;
108 + SlidingWindowCounter counter = new SlidingWindowCounter(WINDOW_SIZE);
109 + AtomicLong operations = new AtomicLong();
110 +
103 /** 111 /**
104 * Creates a new eventually consistent map shared amongst multiple instances. 112 * Creates a new eventually consistent map shared amongst multiple instances.
105 * <p> 113 * <p>
...@@ -162,7 +170,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -162,7 +170,7 @@ public class EventuallyConsistentMapImpl<K, V>
162 //FIXME anti-entropy can take >60 seconds and it blocks fg workers 170 //FIXME anti-entropy can take >60 seconds and it blocks fg workers
163 // ... dropping minPriority to try to help until this can be parallel 171 // ... dropping minPriority to try to help until this can be parallel
164 newSingleThreadScheduledExecutor(//minPriority( 172 newSingleThreadScheduledExecutor(//minPriority(
165 - groupedThreads("onos/ecm", mapName + "-bg-%d"))/*)*/; 173 + groupedThreads("onos/ecm", mapName + "-bg-%d"))/*)*/;
166 174
167 // start anti-entropy thread 175 // start anti-entropy thread
168 //TODO disable anti-entropy for now in testing (it is unstable) 176 //TODO disable anti-entropy for now in testing (it is unstable)
...@@ -271,6 +279,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -271,6 +279,7 @@ public class EventuallyConsistentMapImpl<K, V>
271 } 279 }
272 280
273 private boolean putInternal(K key, V value, Timestamp timestamp) { 281 private boolean putInternal(K key, V value, Timestamp timestamp) {
282 + counter.incrementCount();
274 Timestamp removed = removedItems.get(key); 283 Timestamp removed = removedItems.get(key);
275 if (removed != null && removed.isNewerThan(timestamp)) { 284 if (removed != null && removed.isNewerThan(timestamp)) {
276 log.debug("ecmap - removed was newer {}", value); 285 log.debug("ecmap - removed was newer {}", value);
...@@ -318,6 +327,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -318,6 +327,7 @@ public class EventuallyConsistentMapImpl<K, V>
318 } 327 }
319 328
320 private boolean removeInternal(K key, Timestamp timestamp) { 329 private boolean removeInternal(K key, Timestamp timestamp) {
330 + counter.incrementCount();
321 final MutableBoolean updated = new MutableBoolean(false); 331 final MutableBoolean updated = new MutableBoolean(false);
322 332
323 items.compute(key, (k, existing) -> { 333 items.compute(key, (k, existing) -> {
...@@ -515,6 +525,10 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -515,6 +525,10 @@ public class EventuallyConsistentMapImpl<K, V>
515 clusterCommunicator.unicast(message, peer); 525 clusterCommunicator.unicast(message, peer);
516 } 526 }
517 527
528 + private boolean underHighLoad() {
529 + return counter.get(LOAD_WINDOW) > HIGH_LOAD_THRESHOLD;
530 + }
531 +
518 private final class SendAdvertisementTask implements Runnable { 532 private final class SendAdvertisementTask implements Runnable {
519 @Override 533 @Override
520 public void run() { 534 public void run() {
...@@ -523,6 +537,10 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -523,6 +537,10 @@ public class EventuallyConsistentMapImpl<K, V>
523 return; 537 return;
524 } 538 }
525 539
540 + if (underHighLoad()) {
541 + return;
542 + }
543 +
526 try { 544 try {
527 final NodeId self = clusterService.getLocalNode().id(); 545 final NodeId self = clusterService.getLocalNode().id();
528 Set<ControllerNode> nodes = clusterService.getNodes(); 546 Set<ControllerNode> nodes = clusterService.getNodes();
...@@ -745,7 +763,9 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -745,7 +763,9 @@ public class EventuallyConsistentMapImpl<K, V>
745 message.sender()); 763 message.sender());
746 AntiEntropyAdvertisement<K> advertisement = serializer.decode(message.payload()); 764 AntiEntropyAdvertisement<K> advertisement = serializer.decode(message.payload());
747 try { 765 try {
748 - handleAntiEntropyAdvertisement(advertisement); 766 + if (!underHighLoad()) {
767 + handleAntiEntropyAdvertisement(advertisement);
768 + }
749 } catch (Exception e) { 769 } catch (Exception e) {
750 log.warn("Exception thrown handling advertisements", e); 770 log.warn("Exception thrown handling advertisements", e);
751 } 771 }
......
1 +/*
2 + * Copyright 2015 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.onlab.util;
17 +
18 +import java.util.ArrayList;
19 +import java.util.Collections;
20 +import java.util.List;
21 +import java.util.concurrent.Executors;
22 +import java.util.concurrent.ScheduledExecutorService;
23 +import java.util.concurrent.TimeUnit;
24 +import java.util.concurrent.atomic.AtomicLong;
25 +import java.util.stream.Collectors;
26 +
27 +import static com.google.common.base.Preconditions.checkArgument;
28 +
29 +/**
30 + * Maintains a sliding window of value counts. The sliding window counter is
31 + * initialized with a number of window slots. Calls to #incrementCount() will
32 + * increment the value in the current window slot. Periodically the window
33 + * slides and the oldest value count is dropped. Calls to #get() will get the
34 + * total count for the last N window slots.
35 + */
36 +public final class SlidingWindowCounter {
37 +
38 + private volatile int headSlot;
39 + private final int windowSlots;
40 +
41 + private final List<AtomicLong> counters;
42 +
43 + private final ScheduledExecutorService background;
44 +
45 + private static final int SLIDE_WINDOW_PERIOD_SECONDS = 1;
46 +
47 + /**
48 + * Creates a new sliding window counter with the given total number of
49 + * window slots.
50 + *
51 + * @param windowSlots total number of window slots
52 + */
53 + public SlidingWindowCounter(int windowSlots) {
54 + checkArgument(windowSlots > 0, "Window size must be a positive integer");
55 +
56 + this.windowSlots = windowSlots;
57 + this.headSlot = 0;
58 +
59 + // Initialize each item in the list to an AtomicLong of 0
60 + this.counters = Collections.nCopies(windowSlots, 0)
61 + .stream()
62 + .map(AtomicLong::new)
63 + .collect(Collectors.toCollection(ArrayList::new));
64 +
65 + background = Executors.newSingleThreadScheduledExecutor();
66 + background.scheduleWithFixedDelay(this::advanceHead, 0,
67 + SLIDE_WINDOW_PERIOD_SECONDS, TimeUnit.SECONDS);
68 + }
69 +
70 + /**
71 + * Releases resources used by the SlidingWindowCounter.
72 + */
73 + public void destroy() {
74 + background.shutdownNow();
75 + }
76 +
77 + /**
78 + * Increments the count of the current window slot by 1.
79 + */
80 + public void incrementCount() {
81 + incrementCount(headSlot, 1);
82 + }
83 +
84 + /**
85 + * Increments the count of the current window slot by the given value.
86 + *
87 + * @param value value to increment by
88 + */
89 + public void incrementCount(long value) {
90 + incrementCount(headSlot, value);
91 + }
92 +
93 + private void incrementCount(int slot, long value) {
94 + counters.get(slot).addAndGet(value);
95 + }
96 +
97 + /**
98 + * Gets the total count for the last N window slots.
99 + *
100 + * @param slots number of slots to include in the count
101 + * @return total count for last N slots
102 + */
103 + public long get(int slots) {
104 + checkArgument(slots <= windowSlots,
105 + "Requested window must be less than the total window slots");
106 +
107 + long sum = 0;
108 +
109 + for (int i = 0; i < slots; i++) {
110 + int currentIndex = headSlot - i;
111 + if (currentIndex < 0) {
112 + currentIndex = counters.size() + currentIndex;
113 + }
114 + sum += counters.get(currentIndex).get();
115 + }
116 +
117 + return sum;
118 + }
119 +
120 + void advanceHead() {
121 + counters.get(slotAfter(headSlot)).set(0);
122 + headSlot = slotAfter(headSlot);
123 + }
124 +
125 + private int slotAfter(int slot) {
126 + return (slot + 1) % windowSlots;
127 + }
128 +
129 +}
1 +/*
2 + * Copyright 2015 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.onlab.util;
17 +
18 +import org.junit.After;
19 +import org.junit.Before;
20 +import org.junit.Test;
21 +
22 +import static junit.framework.TestCase.fail;
23 +import static org.junit.Assert.assertEquals;
24 +import static org.junit.Assert.assertTrue;
25 +
26 +/**
27 + * Unit tests for the sliding window counter.
28 + */
29 +public class SlidingWindowCounterTest {
30 +
31 + private SlidingWindowCounter counter;
32 +
33 + @Before
34 + public void setUp() {
35 + counter = new SlidingWindowCounter(2);
36 + }
37 +
38 + @After
39 + public void tearDown() {
40 + counter.destroy();
41 + }
42 +
43 + @Test
44 + public void testIncrementCount() {
45 + assertEquals(0, counter.get(1));
46 + assertEquals(0, counter.get(2));
47 + counter.incrementCount();
48 + assertEquals(1, counter.get(1));
49 + assertEquals(1, counter.get(2));
50 + counter.incrementCount(2);
51 + assertEquals(3, counter.get(2));
52 + }
53 +
54 + @Test
55 + public void testSlide() {
56 + counter.incrementCount();
57 + counter.advanceHead();
58 + assertEquals(0, counter.get(1));
59 + assertEquals(1, counter.get(2));
60 + counter.incrementCount(2);
61 + assertEquals(2, counter.get(1));
62 + assertEquals(3, counter.get(2));
63 + }
64 +
65 + @Test
66 + public void testWrap() {
67 + counter.incrementCount();
68 + counter.advanceHead();
69 + counter.incrementCount(2);
70 + counter.advanceHead();
71 + assertEquals(0, counter.get(1));
72 + assertEquals(2, counter.get(2));
73 + counter.advanceHead();
74 + assertEquals(0, counter.get(1));
75 + assertEquals(0, counter.get(2));
76 +
77 + }
78 +
79 + @Test
80 + public void testCornerCases() {
81 + try {
82 + counter.get(3);
83 + fail("Exception should have been thrown");
84 + } catch (IllegalArgumentException e) {
85 + assertTrue(true);
86 + }
87 +
88 + try {
89 + new SlidingWindowCounter(0);
90 + fail("Exception should have been thrown");
91 + } catch (IllegalArgumentException e) {
92 + assertTrue(true);
93 + }
94 +
95 + try {
96 + new SlidingWindowCounter(-1);
97 + fail("Exception should have been thrown");
98 + } catch (IllegalArgumentException e) {
99 + assertTrue(true);
100 + }
101 + }
102 +}