blob: aac1c87c16b13a0315899f72f0d44c7d05431b01 [file] [log] [blame]
Madan Jampani778f7ad2014-11-05 22:46:15 -08001package org.onlab.onos.store.service.impl;
2
3import static com.google.common.base.Preconditions.checkArgument;
4import static com.google.common.base.Preconditions.checkState;
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -08005import static com.google.common.base.Verify.verifyNotNull;
Madan Jampani348a9fe2014-11-09 01:37:51 -08006import static org.slf4j.LoggerFactory.getLogger;
Madan Jampani778f7ad2014-11-05 22:46:15 -08007
8import java.io.File;
9import java.io.IOException;
10import java.util.ArrayList;
11import java.util.Arrays;
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080012import java.util.Iterator;
Madan Jampani778f7ad2014-11-05 22:46:15 -080013import java.util.List;
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080014import java.util.Map;
Madan Jampani778f7ad2014-11-05 22:46:15 -080015import java.util.concurrent.ConcurrentNavigableMap;
16
17import net.kuujo.copycat.log.Entry;
18import net.kuujo.copycat.log.Log;
19import net.kuujo.copycat.log.LogIndexOutOfBoundsException;
20
21import org.mapdb.Atomic;
22import org.mapdb.BTreeMap;
23import org.mapdb.DB;
24import org.mapdb.DBMaker;
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -080025import org.mapdb.Serializer;
Madan Jampani778f7ad2014-11-05 22:46:15 -080026import org.mapdb.TxBlock;
27import org.mapdb.TxMaker;
28import org.onlab.onos.store.serializers.StoreSerializer;
Madan Jampani348a9fe2014-11-09 01:37:51 -080029import org.slf4j.Logger;
Madan Jampani778f7ad2014-11-05 22:46:15 -080030
Madan Jampani778f7ad2014-11-05 22:46:15 -080031/**
32 * MapDB based log implementation.
33 */
34public class MapDBLog implements Log {
35
Madan Jampani348a9fe2014-11-09 01:37:51 -080036 private final Logger log = getLogger(getClass());
37
Madan Jampani778f7ad2014-11-05 22:46:15 -080038 private final File dbFile;
39 private TxMaker txMaker;
40 private final StoreSerializer serializer;
41 private static final String LOG_NAME = "log";
42 private static final String SIZE_FIELD_NAME = "size";
43
Yuta HIGUCHI413573d2014-11-10 09:45:18 -080044 private int cacheSize = 256;
45
Madan Jampani2ee20002014-11-06 20:06:12 -080046 public MapDBLog(String dbFileName, StoreSerializer serializer) {
47 this.dbFile = new File(dbFileName);
Madan Jampani778f7ad2014-11-05 22:46:15 -080048 this.serializer = serializer;
49 }
50
51 @Override
52 public void open() throws IOException {
53 txMaker = DBMaker
54 .newFileDB(dbFile)
Yuta HIGUCHI413573d2014-11-10 09:45:18 -080055 .cacheSize(cacheSize)
Madan Jampani778f7ad2014-11-05 22:46:15 -080056 .makeTxMaker();
57 }
58
59 @Override
60 public void close() throws IOException {
61 assertIsOpen();
62 txMaker.close();
63 txMaker = null;
64 }
65
66 @Override
67 public boolean isOpen() {
68 return txMaker != null;
69 }
70
71 protected void assertIsOpen() {
72 checkState(isOpen(), "The log is not currently open.");
73 }
74
75 @Override
76 public long appendEntry(Entry entry) {
77 checkArgument(entry != null, "expecting non-null entry");
78 return appendEntries(entry).get(0);
79 }
80
81 @Override
82 public List<Long> appendEntries(Entry... entries) {
83 checkArgument(entries != null, "expecting non-null entries");
84 return appendEntries(Arrays.asList(entries));
85 }
86
87 @Override
88 public List<Long> appendEntries(List<Entry> entries) {
89 assertIsOpen();
90 checkArgument(entries != null, "expecting non-null entries");
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080091 final List<Long> indices = new ArrayList<>(entries.size());
Madan Jampani778f7ad2014-11-05 22:46:15 -080092
93 txMaker.execute(new TxBlock() {
94 @Override
95 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -080096 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -080097 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
98 long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1;
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080099 long addedBytes = 0;
Madan Jampani778f7ad2014-11-05 22:46:15 -0800100 for (Entry entry : entries) {
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800101 byte[] entryBytes = verifyNotNull(serializer.encode(entry),
102 "Writing LogEntry %s failed", nextIndex);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800103 log.put(nextIndex, entryBytes);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800104 addedBytes += entryBytes.length;
Madan Jampani778f7ad2014-11-05 22:46:15 -0800105 indices.add(nextIndex);
106 nextIndex++;
107 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800108 size.addAndGet(addedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800109 }
110 });
111
112 return indices;
113 }
114
115 @Override
116 public boolean containsEntry(long index) {
117 assertIsOpen();
118 DB db = txMaker.makeTx();
119 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800120 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800121 return log.containsKey(index);
122 } finally {
123 db.close();
124 }
125 }
126
127 @Override
128 public void delete() throws IOException {
129 assertIsOpen();
130 txMaker.execute(new TxBlock() {
131 @Override
132 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800133 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800134 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
135 log.clear();
136 size.set(0);
137 }
138 });
139 }
140
141 @Override
142 public <T extends Entry> T firstEntry() {
143 assertIsOpen();
144 DB db = txMaker.makeTx();
145 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800146 BTreeMap<Long, byte[]> log = getLogMap(db);
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800147 return log.isEmpty() ? null : verifyNotNull(serializer.decode(log.firstEntry().getValue()));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800148 } finally {
149 db.close();
150 }
151 }
152
153 @Override
154 public long firstIndex() {
155 assertIsOpen();
156 DB db = txMaker.makeTx();
157 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800158 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800159 return log.isEmpty() ? 0 : log.firstKey();
160 } finally {
161 db.close();
162 }
163 }
164
165 @Override
166 public <T extends Entry> List<T> getEntries(long from, long to) {
167 assertIsOpen();
168 DB db = txMaker.makeTx();
169 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800170 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800171 if (log.isEmpty()) {
172 throw new LogIndexOutOfBoundsException("Log is empty");
173 } else if (from < log.firstKey()) {
174 throw new LogIndexOutOfBoundsException("From index out of bounds.");
175 } else if (to > log.lastKey()) {
176 throw new LogIndexOutOfBoundsException("To index out of bounds.");
177 }
178 List<T> entries = new ArrayList<>((int) (to - from + 1));
179 for (long i = from; i <= to; i++) {
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800180 T entry = verifyNotNull(serializer.decode(log.get(i)), "LogEntry %s was null", i);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800181 entries.add(entry);
182 }
183 return entries;
184 } finally {
185 db.close();
186 }
187 }
188
189 @Override
190 public <T extends Entry> T getEntry(long index) {
191 assertIsOpen();
192 DB db = txMaker.makeTx();
193 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800194 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800195 byte[] entryBytes = log.get(index);
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800196 return entryBytes == null ? null : verifyNotNull(serializer.decode(entryBytes),
197 "LogEntry %s was null", index);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800198 } finally {
199 db.close();
200 }
201 }
202
203 @Override
204 public boolean isEmpty() {
205 assertIsOpen();
206 DB db = txMaker.makeTx();
207 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800208 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800209 return log.isEmpty();
210 } finally {
211 db.close();
212 }
213 }
214
215 @Override
216 public <T extends Entry> T lastEntry() {
217 assertIsOpen();
218 DB db = txMaker.makeTx();
219 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800220 BTreeMap<Long, byte[]> log = getLogMap(db);
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800221 return log.isEmpty() ? null : verifyNotNull(serializer.decode(log.lastEntry().getValue()));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800222 } finally {
223 db.close();
224 }
225 }
226
227 @Override
228 public long lastIndex() {
229 assertIsOpen();
230 DB db = txMaker.makeTx();
231 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800232 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800233 return log.isEmpty() ? 0 : log.lastKey();
234 } finally {
235 db.close();
236 }
237 }
238
239 @Override
240 public void removeAfter(long index) {
241 assertIsOpen();
242 txMaker.execute(new TxBlock() {
243 @Override
244 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800245 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800246 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800247 long removedBytes = 0;
248 ConcurrentNavigableMap<Long, byte[]> tailMap = log.tailMap(index, false);
249 Iterator<Map.Entry<Long, byte[]>> it = tailMap.entrySet().iterator();
250 while (it.hasNext()) {
251 Map.Entry<Long, byte[]> entry = it.next();
252 removedBytes += entry.getValue().length;
253 it.remove();
Madan Jampani778f7ad2014-11-05 22:46:15 -0800254 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800255 size.addAndGet(-removedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800256 }
257 });
258 }
259
260 @Override
261 public long size() {
262 assertIsOpen();
263 DB db = txMaker.makeTx();
264 try {
265 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
266 return size.get();
267 } finally {
268 db.close();
269 }
270 }
271
272 @Override
273 public void sync() throws IOException {
274 assertIsOpen();
275 }
276
277 @Override
278 public void compact(long index, Entry entry) throws IOException {
279
280 assertIsOpen();
281 txMaker.execute(new TxBlock() {
282 @Override
283 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800284 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800285 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
286 ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800287 Iterator<Map.Entry<Long, byte[]>> it = headMap.entrySet().iterator();
288
289 long deletedBytes = 0;
290 while (it.hasNext()) {
291 Map.Entry<Long, byte[]> e = it.next();
292 deletedBytes += e.getValue().length;
293 it.remove();
294 }
295 size.addAndGet(-deletedBytes);
296 byte[] entryBytes = verifyNotNull(serializer.encode(entry));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800297 byte[] existingEntry = log.put(index, entryBytes);
Madan Jampani348a9fe2014-11-09 01:37:51 -0800298 if (existingEntry != null) {
299 size.addAndGet(entryBytes.length - existingEntry.length);
300 } else {
301 size.addAndGet(entryBytes.length);
302 }
Madan Jampani778f7ad2014-11-05 22:46:15 -0800303 db.compact();
304 }
305 });
306 }
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800307
308 private BTreeMap<Long, byte[]> getLogMap(DB db) {
309 return db.createTreeMap(LOG_NAME)
310 .valuesOutsideNodesEnable()
311 .keySerializerWrap(Serializer.LONG)
312 .valueSerializer(Serializer.BYTE_ARRAY)
313 .makeOrGet();
314 }
Madan Jampani2ee20002014-11-06 20:06:12 -0800315}