blob: c2e3d8f9dfd99bf2c7081c548951e2602cdba171 [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 .cacheLRUEnable()
56 .cacheSize(cacheSize)
Madan Jampani778f7ad2014-11-05 22:46:15 -080057 .makeTxMaker();
58 }
59
60 @Override
61 public void close() throws IOException {
62 assertIsOpen();
63 txMaker.close();
64 txMaker = null;
65 }
66
67 @Override
68 public boolean isOpen() {
69 return txMaker != null;
70 }
71
72 protected void assertIsOpen() {
73 checkState(isOpen(), "The log is not currently open.");
74 }
75
76 @Override
77 public long appendEntry(Entry entry) {
78 checkArgument(entry != null, "expecting non-null entry");
79 return appendEntries(entry).get(0);
80 }
81
82 @Override
83 public List<Long> appendEntries(Entry... entries) {
84 checkArgument(entries != null, "expecting non-null entries");
85 return appendEntries(Arrays.asList(entries));
86 }
87
88 @Override
89 public List<Long> appendEntries(List<Entry> entries) {
90 assertIsOpen();
91 checkArgument(entries != null, "expecting non-null entries");
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080092 final List<Long> indices = new ArrayList<>(entries.size());
Madan Jampani778f7ad2014-11-05 22:46:15 -080093
94 txMaker.execute(new TxBlock() {
95 @Override
96 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -080097 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -080098 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
99 long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1;
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800100 long addedBytes = 0;
Madan Jampani778f7ad2014-11-05 22:46:15 -0800101 for (Entry entry : entries) {
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800102 byte[] entryBytes = verifyNotNull(serializer.encode(entry),
103 "Writing LogEntry %s failed", nextIndex);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800104 log.put(nextIndex, entryBytes);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800105 addedBytes += entryBytes.length;
Madan Jampani778f7ad2014-11-05 22:46:15 -0800106 indices.add(nextIndex);
107 nextIndex++;
108 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800109 size.addAndGet(addedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800110 }
111 });
112
113 return indices;
114 }
115
116 @Override
117 public boolean containsEntry(long index) {
118 assertIsOpen();
119 DB db = txMaker.makeTx();
120 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800121 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800122 return log.containsKey(index);
123 } finally {
124 db.close();
125 }
126 }
127
128 @Override
129 public void delete() throws IOException {
130 assertIsOpen();
131 txMaker.execute(new TxBlock() {
132 @Override
133 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800134 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800135 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
136 log.clear();
137 size.set(0);
138 }
139 });
140 }
141
142 @Override
143 public <T extends Entry> T firstEntry() {
144 assertIsOpen();
145 DB db = txMaker.makeTx();
146 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800147 BTreeMap<Long, byte[]> log = getLogMap(db);
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800148 return log.isEmpty() ? null : verifyNotNull(serializer.decode(log.firstEntry().getValue()));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800149 } finally {
150 db.close();
151 }
152 }
153
154 @Override
155 public long firstIndex() {
156 assertIsOpen();
157 DB db = txMaker.makeTx();
158 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800159 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800160 return log.isEmpty() ? 0 : log.firstKey();
161 } finally {
162 db.close();
163 }
164 }
165
166 @Override
167 public <T extends Entry> List<T> getEntries(long from, long to) {
168 assertIsOpen();
169 DB db = txMaker.makeTx();
170 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800171 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800172 if (log.isEmpty()) {
173 throw new LogIndexOutOfBoundsException("Log is empty");
174 } else if (from < log.firstKey()) {
175 throw new LogIndexOutOfBoundsException("From index out of bounds.");
176 } else if (to > log.lastKey()) {
177 throw new LogIndexOutOfBoundsException("To index out of bounds.");
178 }
179 List<T> entries = new ArrayList<>((int) (to - from + 1));
180 for (long i = from; i <= to; i++) {
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800181 T entry = verifyNotNull(serializer.decode(log.get(i)), "LogEntry %s was null", i);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800182 entries.add(entry);
183 }
184 return entries;
185 } finally {
186 db.close();
187 }
188 }
189
190 @Override
191 public <T extends Entry> T getEntry(long index) {
192 assertIsOpen();
193 DB db = txMaker.makeTx();
194 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800195 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800196 byte[] entryBytes = log.get(index);
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800197 return entryBytes == null ? null : verifyNotNull(serializer.decode(entryBytes),
198 "LogEntry %s was null", index);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800199 } finally {
200 db.close();
201 }
202 }
203
204 @Override
205 public boolean isEmpty() {
206 assertIsOpen();
207 DB db = txMaker.makeTx();
208 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800209 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800210 return log.isEmpty();
211 } finally {
212 db.close();
213 }
214 }
215
216 @Override
217 public <T extends Entry> T lastEntry() {
218 assertIsOpen();
219 DB db = txMaker.makeTx();
220 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800221 BTreeMap<Long, byte[]> log = getLogMap(db);
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800222 return log.isEmpty() ? null : verifyNotNull(serializer.decode(log.lastEntry().getValue()));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800223 } finally {
224 db.close();
225 }
226 }
227
228 @Override
229 public long lastIndex() {
230 assertIsOpen();
231 DB db = txMaker.makeTx();
232 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800233 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800234 return log.isEmpty() ? 0 : log.lastKey();
235 } finally {
236 db.close();
237 }
238 }
239
240 @Override
241 public void removeAfter(long index) {
242 assertIsOpen();
243 txMaker.execute(new TxBlock() {
244 @Override
245 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800246 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800247 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800248 long removedBytes = 0;
249 ConcurrentNavigableMap<Long, byte[]> tailMap = log.tailMap(index, false);
250 Iterator<Map.Entry<Long, byte[]>> it = tailMap.entrySet().iterator();
251 while (it.hasNext()) {
252 Map.Entry<Long, byte[]> entry = it.next();
253 removedBytes += entry.getValue().length;
254 it.remove();
Madan Jampani778f7ad2014-11-05 22:46:15 -0800255 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800256 size.addAndGet(-removedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800257 }
258 });
259 }
260
261 @Override
262 public long size() {
263 assertIsOpen();
264 DB db = txMaker.makeTx();
265 try {
266 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
267 return size.get();
268 } finally {
269 db.close();
270 }
271 }
272
273 @Override
274 public void sync() throws IOException {
275 assertIsOpen();
276 }
277
278 @Override
279 public void compact(long index, Entry entry) throws IOException {
280
281 assertIsOpen();
282 txMaker.execute(new TxBlock() {
283 @Override
284 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800285 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800286 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
287 ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800288 Iterator<Map.Entry<Long, byte[]>> it = headMap.entrySet().iterator();
289
290 long deletedBytes = 0;
291 while (it.hasNext()) {
292 Map.Entry<Long, byte[]> e = it.next();
293 deletedBytes += e.getValue().length;
294 it.remove();
295 }
296 size.addAndGet(-deletedBytes);
297 byte[] entryBytes = verifyNotNull(serializer.encode(entry));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800298 byte[] existingEntry = log.put(index, entryBytes);
Madan Jampani348a9fe2014-11-09 01:37:51 -0800299 if (existingEntry != null) {
300 size.addAndGet(entryBytes.length - existingEntry.length);
301 } else {
302 size.addAndGet(entryBytes.length);
303 }
Madan Jampani778f7ad2014-11-05 22:46:15 -0800304 db.compact();
305 }
306 });
307 }
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800308
309 private BTreeMap<Long, byte[]> getLogMap(DB db) {
310 return db.createTreeMap(LOG_NAME)
311 .valuesOutsideNodesEnable()
312 .keySerializerWrap(Serializer.LONG)
313 .valueSerializer(Serializer.BYTE_ARRAY)
314 .makeOrGet();
315 }
Madan Jampani2ee20002014-11-06 20:06:12 -0800316}