Committed by
Ray Milkey
Add persistence option to ECMap
Work towards ONOS-1337 Change-Id: I24e6a42e2f8856b363e79786829c51344797b81b
Showing
7 changed files
with
278 additions
and
12 deletions
... | @@ -166,6 +166,16 @@ public interface EventuallyConsistentMapBuilder<K, V> { | ... | @@ -166,6 +166,16 @@ public interface EventuallyConsistentMapBuilder<K, V> { |
166 | public EventuallyConsistentMapBuilder<K, V> withFasterConvergence(); | 166 | public EventuallyConsistentMapBuilder<K, V> withFasterConvergence(); |
167 | 167 | ||
168 | /** | 168 | /** |
169 | + * Configure the map to persist data to disk. | ||
170 | + * <p> | ||
171 | + * The default behavior is no persistence | ||
172 | + * </p> | ||
173 | + * | ||
174 | + * @return this EventuallyConsistentMapBuilder | ||
175 | + */ | ||
176 | + public EventuallyConsistentMapBuilder<K, V> withPersistence(); | ||
177 | + | ||
178 | + /** | ||
169 | * Builds an eventually consistent map based on the configuration options | 179 | * Builds an eventually consistent map based on the configuration options |
170 | * supplied to this builder. | 180 | * supplied to this builder. |
171 | * | 181 | * | ... | ... |
... | @@ -65,7 +65,7 @@ | ... | @@ -65,7 +65,7 @@ |
65 | <dependency> | 65 | <dependency> |
66 | <groupId>org.mapdb</groupId> | 66 | <groupId>org.mapdb</groupId> |
67 | <artifactId>mapdb</artifactId> | 67 | <artifactId>mapdb</artifactId> |
68 | - <version>1.0.6</version> | 68 | + <version>1.0.7</version> |
69 | </dependency> | 69 | </dependency> |
70 | 70 | ||
71 | <dependency> | 71 | <dependency> | ... | ... |
... | @@ -51,6 +51,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V> | ... | @@ -51,6 +51,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V> |
51 | private long antiEntropyPeriod = 5; | 51 | private long antiEntropyPeriod = 5; |
52 | private TimeUnit antiEntropyTimeUnit = TimeUnit.SECONDS; | 52 | private TimeUnit antiEntropyTimeUnit = TimeUnit.SECONDS; |
53 | private boolean convergeFaster = false; | 53 | private boolean convergeFaster = false; |
54 | + private boolean persistent = false; | ||
54 | 55 | ||
55 | /** | 56 | /** |
56 | * Creates a new eventually consistent map builder. | 57 | * Creates a new eventually consistent map builder. |
... | @@ -131,6 +132,12 @@ public class EventuallyConsistentMapBuilderImpl<K, V> | ... | @@ -131,6 +132,12 @@ public class EventuallyConsistentMapBuilderImpl<K, V> |
131 | } | 132 | } |
132 | 133 | ||
133 | @Override | 134 | @Override |
135 | + public EventuallyConsistentMapBuilder<K, V> withPersistence() { | ||
136 | + persistent = true; | ||
137 | + return this; | ||
138 | + } | ||
139 | + | ||
140 | + @Override | ||
134 | public EventuallyConsistentMap<K, V> build() { | 141 | public EventuallyConsistentMap<K, V> build() { |
135 | checkNotNull(name, "name is a mandatory parameter"); | 142 | checkNotNull(name, "name is a mandatory parameter"); |
136 | checkNotNull(serializerBuilder, "serializerBuilder is a mandatory parameter"); | 143 | checkNotNull(serializerBuilder, "serializerBuilder is a mandatory parameter"); |
... | @@ -148,6 +155,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V> | ... | @@ -148,6 +155,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V> |
148 | tombstonesDisabled, | 155 | tombstonesDisabled, |
149 | antiEntropyPeriod, | 156 | antiEntropyPeriod, |
150 | antiEntropyTimeUnit, | 157 | antiEntropyTimeUnit, |
151 | - convergeFaster); | 158 | + convergeFaster, |
159 | + persistent); | ||
152 | } | 160 | } |
153 | } | 161 | } | ... | ... |
... | @@ -93,7 +93,6 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -93,7 +93,6 @@ public class EventuallyConsistentMapImpl<K, V> |
93 | = new CopyOnWriteArraySet<>(); | 93 | = new CopyOnWriteArraySet<>(); |
94 | 94 | ||
95 | private final ExecutorService executor; | 95 | private final ExecutorService executor; |
96 | - | ||
97 | private final ScheduledExecutorService backgroundExecutor; | 96 | private final ScheduledExecutorService backgroundExecutor; |
98 | private final BiFunction<K, V, Collection<NodeId>> peerUpdateFunction; | 97 | private final BiFunction<K, V, Collection<NodeId>> peerUpdateFunction; |
99 | 98 | ||
... | @@ -116,6 +115,9 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -116,6 +115,9 @@ public class EventuallyConsistentMapImpl<K, V> |
116 | private static final int LOAD_WINDOW = 2; | 115 | private static final int LOAD_WINDOW = 2; |
117 | private SlidingWindowCounter counter = new SlidingWindowCounter(WINDOW_SIZE); | 116 | private SlidingWindowCounter counter = new SlidingWindowCounter(WINDOW_SIZE); |
118 | 117 | ||
118 | + private final boolean persistent; | ||
119 | + private final PersistentStore<K, V> persistentStore; | ||
120 | + | ||
119 | /** | 121 | /** |
120 | * Creates a new eventually consistent map shared amongst multiple instances. | 122 | * Creates a new eventually consistent map shared amongst multiple instances. |
121 | * <p> | 123 | * <p> |
... | @@ -140,9 +142,9 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -140,9 +142,9 @@ public class EventuallyConsistentMapImpl<K, V> |
140 | * @param tombstonesDisabled true if this map should not maintain | 142 | * @param tombstonesDisabled true if this map should not maintain |
141 | * tombstones | 143 | * tombstones |
142 | * @param antiEntropyPeriod period that the anti-entropy task should run | 144 | * @param antiEntropyPeriod period that the anti-entropy task should run |
143 | - * in seconds | 145 | + * @param antiEntropyTimeUnit time unit for anti-entropy period |
144 | - * @param antiEntropyTimeUnit time unit for anti-entropy task scheduling | ||
145 | * @param convergeFaster make anti-entropy try to converge faster | 146 | * @param convergeFaster make anti-entropy try to converge faster |
147 | + * @param persistent persist data to disk | ||
146 | */ | 148 | */ |
147 | EventuallyConsistentMapImpl(String mapName, | 149 | EventuallyConsistentMapImpl(String mapName, |
148 | ClusterService clusterService, | 150 | ClusterService clusterService, |
... | @@ -156,7 +158,8 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -156,7 +158,8 @@ public class EventuallyConsistentMapImpl<K, V> |
156 | boolean tombstonesDisabled, | 158 | boolean tombstonesDisabled, |
157 | long antiEntropyPeriod, | 159 | long antiEntropyPeriod, |
158 | TimeUnit antiEntropyTimeUnit, | 160 | TimeUnit antiEntropyTimeUnit, |
159 | - boolean convergeFaster) { | 161 | + boolean convergeFaster, |
162 | + boolean persistent) { | ||
160 | items = new ConcurrentHashMap<>(); | 163 | items = new ConcurrentHashMap<>(); |
161 | removedItems = new ConcurrentHashMap<>(); | 164 | removedItems = new ConcurrentHashMap<>(); |
162 | senderPending = Maps.newConcurrentMap(); | 165 | senderPending = Maps.newConcurrentMap(); |
... | @@ -195,6 +198,21 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -195,6 +198,21 @@ public class EventuallyConsistentMapImpl<K, V> |
195 | newFixedThreadPool(8, groupedThreads("onos/ecm", mapName + "-publish-%d")); | 198 | newFixedThreadPool(8, groupedThreads("onos/ecm", mapName + "-publish-%d")); |
196 | } | 199 | } |
197 | 200 | ||
201 | + this.persistent = persistent; | ||
202 | + | ||
203 | + if (this.persistent) { | ||
204 | + String dataDirectory = System.getProperty("karaf.data", "./data"); | ||
205 | + String filename = dataDirectory + "/" + "mapdb-ecm-" + mapName; | ||
206 | + | ||
207 | + ExecutorService dbExecutor = | ||
208 | + newFixedThreadPool(1, groupedThreads("onos/ecm", mapName + "-dbwriter")); | ||
209 | + | ||
210 | + persistentStore = new MapDbPersistentStore<>(filename, dbExecutor, serializer); | ||
211 | + persistentStore.readInto(items, removedItems); | ||
212 | + } else { | ||
213 | + this.persistentStore = null; | ||
214 | + } | ||
215 | + | ||
198 | if (backgroundExecutor != null) { | 216 | if (backgroundExecutor != null) { |
199 | this.backgroundExecutor = backgroundExecutor; | 217 | this.backgroundExecutor = backgroundExecutor; |
200 | } else { | 218 | } else { |
... | @@ -232,6 +250,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -232,6 +250,7 @@ public class EventuallyConsistentMapImpl<K, V> |
232 | .register(ArrayList.class) | 250 | .register(ArrayList.class) |
233 | .register(AntiEntropyAdvertisement.class) | 251 | .register(AntiEntropyAdvertisement.class) |
234 | .register(HashMap.class) | 252 | .register(HashMap.class) |
253 | + .register(Timestamped.class) | ||
235 | .build(); | 254 | .build(); |
236 | } | 255 | } |
237 | }; | 256 | }; |
... | @@ -321,6 +340,11 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -321,6 +340,11 @@ public class EventuallyConsistentMapImpl<K, V> |
321 | if (success && removed != null) { | 340 | if (success && removed != null) { |
322 | removedItems.remove(key, removed); | 341 | removedItems.remove(key, removed); |
323 | } | 342 | } |
343 | + | ||
344 | + if (success && persistent) { | ||
345 | + persistentStore.put(key, value, timestamp); | ||
346 | + } | ||
347 | + | ||
324 | return success; | 348 | return success; |
325 | } | 349 | } |
326 | 350 | ||
... | @@ -363,18 +387,23 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -363,18 +387,23 @@ public class EventuallyConsistentMapImpl<K, V> |
363 | return false; | 387 | return false; |
364 | } | 388 | } |
365 | 389 | ||
390 | + boolean updatedTombstone = false; | ||
391 | + | ||
366 | if (!tombstonesDisabled) { | 392 | if (!tombstonesDisabled) { |
367 | Timestamp removedTimestamp = removedItems.get(key); | 393 | Timestamp removedTimestamp = removedItems.get(key); |
368 | if (removedTimestamp == null) { | 394 | if (removedTimestamp == null) { |
369 | - return removedItems.putIfAbsent(key, timestamp) == null; | 395 | + //Timestamp removed = removedItems.putIfAbsent(key, timestamp); |
396 | + updatedTombstone = (removedItems.putIfAbsent(key, timestamp) == null); | ||
370 | } else if (timestamp.isNewerThan(removedTimestamp)) { | 397 | } else if (timestamp.isNewerThan(removedTimestamp)) { |
371 | - return removedItems.replace(key, removedTimestamp, timestamp); | 398 | + updatedTombstone = removedItems.replace(key, removedTimestamp, timestamp); |
372 | - } else { | ||
373 | - return false; | ||
374 | } | 399 | } |
375 | } | 400 | } |
376 | 401 | ||
377 | - return updated.booleanValue(); | 402 | + if (updated.booleanValue() && persistent) { |
403 | + persistentStore.remove(key, timestamp); | ||
404 | + } | ||
405 | + | ||
406 | + return (!tombstonesDisabled && updatedTombstone) || updated.booleanValue(); | ||
378 | } | 407 | } |
379 | 408 | ||
380 | @Override | 409 | @Override | ... | ... |
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 | + | ||
17 | +package org.onosproject.store.ecmap; | ||
18 | + | ||
19 | +import org.apache.commons.lang3.mutable.MutableBoolean; | ||
20 | +import org.mapdb.DB; | ||
21 | +import org.mapdb.DBMaker; | ||
22 | +import org.mapdb.Hasher; | ||
23 | +import org.mapdb.Serializer; | ||
24 | +import org.onosproject.store.Timestamp; | ||
25 | +import org.onosproject.store.impl.Timestamped; | ||
26 | +import org.onosproject.store.serializers.KryoSerializer; | ||
27 | + | ||
28 | +import java.io.File; | ||
29 | +import java.util.Map; | ||
30 | +import java.util.concurrent.ExecutorService; | ||
31 | + | ||
32 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
33 | + | ||
34 | +/** | ||
35 | + * MapDB based implementation of a persistent store. | ||
36 | + */ | ||
37 | +class MapDbPersistentStore<K, V> implements PersistentStore<K, V> { | ||
38 | + | ||
39 | + private final ExecutorService executor; | ||
40 | + private final KryoSerializer serializer; | ||
41 | + | ||
42 | + private final DB database; | ||
43 | + | ||
44 | + private final Map<byte[], byte[]> items; | ||
45 | + private final Map<byte[], byte[]> tombstones; | ||
46 | + | ||
47 | + /** | ||
48 | + * Creates a new MapDB based persistent store. | ||
49 | + * | ||
50 | + * @param filename filename of the database on disk | ||
51 | + * @param executor executor to use for tasks that write to the disk | ||
52 | + * @param serializer serializer for keys and values | ||
53 | + */ | ||
54 | + MapDbPersistentStore(String filename, ExecutorService executor, | ||
55 | + KryoSerializer serializer) { | ||
56 | + this.executor = checkNotNull(executor); | ||
57 | + this.serializer = checkNotNull(serializer); | ||
58 | + | ||
59 | + File databaseFile = new File(filename); | ||
60 | + | ||
61 | + database = DBMaker.newFileDB(databaseFile).make(); | ||
62 | + | ||
63 | + items = database.createHashMap("items") | ||
64 | + .keySerializer(Serializer.BYTE_ARRAY) | ||
65 | + .valueSerializer(Serializer.BYTE_ARRAY) | ||
66 | + .hasher(Hasher.BYTE_ARRAY) | ||
67 | + .makeOrGet(); | ||
68 | + | ||
69 | + tombstones = database.createHashMap("tombstones") | ||
70 | + .keySerializer(Serializer.BYTE_ARRAY) | ||
71 | + .valueSerializer(Serializer.BYTE_ARRAY) | ||
72 | + .hasher(Hasher.BYTE_ARRAY) | ||
73 | + .makeOrGet(); | ||
74 | + } | ||
75 | + | ||
76 | + @Override | ||
77 | + public void readInto(Map<K, Timestamped<V>> items, Map<K, Timestamp> tombstones) { | ||
78 | + this.items.forEach((keyBytes, valueBytes) -> | ||
79 | + items.put(serializer.decode(keyBytes), | ||
80 | + serializer.decode(valueBytes))); | ||
81 | + | ||
82 | + this.tombstones.forEach((keyBytes, valueBytes) -> | ||
83 | + tombstones.put(serializer.decode(keyBytes), | ||
84 | + serializer.decode(valueBytes))); | ||
85 | + } | ||
86 | + | ||
87 | + @Override | ||
88 | + public void put(K key, V value, Timestamp timestamp) { | ||
89 | + executor.submit(() -> putInternal(key, value, timestamp)); | ||
90 | + } | ||
91 | + | ||
92 | + private void putInternal(K key, V value, Timestamp timestamp) { | ||
93 | + byte[] keyBytes = serializer.encode(key); | ||
94 | + byte[] removedBytes = tombstones.get(keyBytes); | ||
95 | + | ||
96 | + Timestamp removed = removedBytes == null ? null : | ||
97 | + serializer.decode(removedBytes); | ||
98 | + if (removed != null && removed.isNewerThan(timestamp)) { | ||
99 | + return; | ||
100 | + } | ||
101 | + | ||
102 | + final MutableBoolean updated = new MutableBoolean(false); | ||
103 | + | ||
104 | + items.compute(keyBytes, (k, existingBytes) -> { | ||
105 | + Timestamped<V> existing = existingBytes == null ? null : | ||
106 | + serializer.decode(existingBytes); | ||
107 | + if (existing != null && existing.isNewerThan(timestamp)) { | ||
108 | + updated.setFalse(); | ||
109 | + return existingBytes; | ||
110 | + } else { | ||
111 | + updated.setTrue(); | ||
112 | + return serializer.encode(new Timestamped<>(value, timestamp)); | ||
113 | + } | ||
114 | + }); | ||
115 | + | ||
116 | + boolean success = updated.booleanValue(); | ||
117 | + | ||
118 | + if (success && removed != null) { | ||
119 | + tombstones.remove(keyBytes, removedBytes); | ||
120 | + } | ||
121 | + | ||
122 | + database.commit(); | ||
123 | + } | ||
124 | + | ||
125 | + @Override | ||
126 | + public void remove(K key, Timestamp timestamp) { | ||
127 | + executor.submit(() -> removeInternal(key, timestamp)); | ||
128 | + } | ||
129 | + | ||
130 | + private void removeInternal(K key, Timestamp timestamp) { | ||
131 | + byte[] keyBytes = serializer.encode(key); | ||
132 | + | ||
133 | + final MutableBoolean updated = new MutableBoolean(false); | ||
134 | + | ||
135 | + items.compute(keyBytes, (k, existingBytes) -> { | ||
136 | + Timestamp existing = existingBytes == null ? null : | ||
137 | + serializer.decode(existingBytes); | ||
138 | + if (existing != null && existing.isNewerThan(timestamp)) { | ||
139 | + updated.setFalse(); | ||
140 | + return existingBytes; | ||
141 | + } else { | ||
142 | + updated.setTrue(); | ||
143 | + // remove from items map | ||
144 | + return null; | ||
145 | + } | ||
146 | + }); | ||
147 | + | ||
148 | + if (!updated.booleanValue()) { | ||
149 | + return; | ||
150 | + } | ||
151 | + | ||
152 | + byte[] timestampBytes = serializer.encode(timestamp); | ||
153 | + byte[] removedBytes = tombstones.get(keyBytes); | ||
154 | + | ||
155 | + Timestamp removedTimestamp = removedBytes == null ? null : | ||
156 | + serializer.decode(removedBytes); | ||
157 | + if (removedTimestamp == null) { | ||
158 | + tombstones.putIfAbsent(keyBytes, timestampBytes); | ||
159 | + } else if (timestamp.isNewerThan(removedTimestamp)) { | ||
160 | + tombstones.replace(keyBytes, removedBytes, timestampBytes); | ||
161 | + } | ||
162 | + | ||
163 | + database.commit(); | ||
164 | + } | ||
165 | + | ||
166 | +} |
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 | + | ||
17 | +package org.onosproject.store.ecmap; | ||
18 | + | ||
19 | +import org.onosproject.store.Timestamp; | ||
20 | +import org.onosproject.store.impl.Timestamped; | ||
21 | + | ||
22 | +import java.util.Map; | ||
23 | + | ||
24 | +/** | ||
25 | + * A persistent store for an eventually consistent map. | ||
26 | + */ | ||
27 | +interface PersistentStore<K, V> { | ||
28 | + | ||
29 | + /** | ||
30 | + * Read the contents of the disk into the given maps. | ||
31 | + * | ||
32 | + * @param items items map | ||
33 | + * @param tombstones tombstones map | ||
34 | + */ | ||
35 | + void readInto(Map<K, Timestamped<V>> items, Map<K, Timestamp> tombstones); | ||
36 | + | ||
37 | + /** | ||
38 | + * Puts a new key,value pair into the map on disk. | ||
39 | + * | ||
40 | + * @param key the key | ||
41 | + * @param value the value | ||
42 | + * @param timestamp the timestamp of the update | ||
43 | + */ | ||
44 | + void put(K key, V value, Timestamp timestamp); | ||
45 | + | ||
46 | + /** | ||
47 | + * Removes a key from the map on disk. | ||
48 | + * | ||
49 | + * @param key the key | ||
50 | + * @param timestamp the timestamp of the update | ||
51 | + */ | ||
52 | + void remove(K key, Timestamp timestamp); | ||
53 | +} |
... | @@ -64,7 +64,7 @@ | ... | @@ -64,7 +64,7 @@ |
64 | <bundle>mvn:com.typesafe/config/1.2.1</bundle> | 64 | <bundle>mvn:com.typesafe/config/1.2.1</bundle> |
65 | <bundle>mvn:org.onosproject/onlab-thirdparty/@ONOS-VERSION</bundle> | 65 | <bundle>mvn:org.onosproject/onlab-thirdparty/@ONOS-VERSION</bundle> |
66 | 66 | ||
67 | - <bundle>mvn:org.mapdb/mapdb/1.0.6</bundle> | 67 | + <bundle>mvn:org.mapdb/mapdb/1.0.7</bundle> |
68 | </feature> | 68 | </feature> |
69 | 69 | ||
70 | <feature name="onos-thirdparty-web" version="@FEATURE-VERSION" | 70 | <feature name="onos-thirdparty-web" version="@FEATURE-VERSION" | ... | ... |
-
Please register or login to post a comment