blob: 624df624ff74875fd245e34838005fe68743dee9 [file] [log] [blame]
Brian O'Connorabafb502014-12-02 22:26:20 -08001package org.onosproject.store.service.impl;
Madan Jampani778f7ad2014-11-05 22:46:15 -08002
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;
Brian O'Connorabafb502014-12-02 22:26:20 -080028import org.onosproject.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)
Madan Jampani13efefa2014-11-10 12:23:09 -080055 .mmapFileEnableIfSupported()
Yuta HIGUCHI413573d2014-11-10 09:45:18 -080056 .cacheSize(cacheSize)
Madan Jampani778f7ad2014-11-05 22:46:15 -080057 .makeTxMaker();
Yuta HIGUCHI4490a732014-11-18 20:20:30 -080058 log.info("Raft log file: {}", dbFile.getCanonicalPath());
Madan Jampani778f7ad2014-11-05 22:46:15 -080059 }
60
61 @Override
62 public void close() throws IOException {
63 assertIsOpen();
64 txMaker.close();
65 txMaker = null;
66 }
67
68 @Override
69 public boolean isOpen() {
70 return txMaker != null;
71 }
72
73 protected void assertIsOpen() {
74 checkState(isOpen(), "The log is not currently open.");
75 }
76
77 @Override
78 public long appendEntry(Entry entry) {
79 checkArgument(entry != null, "expecting non-null entry");
80 return appendEntries(entry).get(0);
81 }
82
83 @Override
84 public List<Long> appendEntries(Entry... entries) {
85 checkArgument(entries != null, "expecting non-null entries");
86 return appendEntries(Arrays.asList(entries));
87 }
88
89 @Override
Madan Jampania88d1f52014-11-14 16:45:24 -080090 public synchronized List<Long> appendEntries(List<Entry> entries) {
Madan Jampani778f7ad2014-11-05 22:46:15 -080091 assertIsOpen();
92 checkArgument(entries != null, "expecting non-null entries");
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080093 final List<Long> indices = new ArrayList<>(entries.size());
Madan Jampani778f7ad2014-11-05 22:46:15 -080094
95 txMaker.execute(new TxBlock() {
96 @Override
97 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -080098 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -080099 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
100 long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1;
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800101 long addedBytes = 0;
Madan Jampani778f7ad2014-11-05 22:46:15 -0800102 for (Entry entry : entries) {
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800103 byte[] entryBytes = verifyNotNull(serializer.encode(entry),
104 "Writing LogEntry %s failed", nextIndex);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800105 log.put(nextIndex, entryBytes);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800106 addedBytes += entryBytes.length;
Madan Jampani778f7ad2014-11-05 22:46:15 -0800107 indices.add(nextIndex);
108 nextIndex++;
109 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800110 size.addAndGet(addedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800111 }
112 });
113
114 return indices;
115 }
116
117 @Override
118 public boolean containsEntry(long index) {
119 assertIsOpen();
120 DB db = txMaker.makeTx();
121 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800122 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800123 return log.containsKey(index);
124 } finally {
125 db.close();
126 }
127 }
128
129 @Override
130 public void delete() throws IOException {
131 assertIsOpen();
132 txMaker.execute(new TxBlock() {
133 @Override
134 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800135 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800136 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
137 log.clear();
138 size.set(0);
139 }
140 });
141 }
142
143 @Override
144 public <T extends Entry> T firstEntry() {
145 assertIsOpen();
146 DB db = txMaker.makeTx();
147 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800148 BTreeMap<Long, byte[]> log = getLogMap(db);
Yuta HIGUCHI1ec41662014-11-19 22:01:54 -0800149 return log.isEmpty() ? null : verifyNotNull(decodeEntry(log.firstEntry().getValue()));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800150 } finally {
151 db.close();
152 }
153 }
154
155 @Override
156 public long firstIndex() {
157 assertIsOpen();
158 DB db = txMaker.makeTx();
159 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800160 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800161 return log.isEmpty() ? 0 : log.firstKey();
162 } finally {
163 db.close();
164 }
165 }
166
Yuta HIGUCHI1ec41662014-11-19 22:01:54 -0800167 private <T extends Entry> T decodeEntry(final byte[] bytes) {
168 if (bytes == null) {
169 return null;
170 }
171 return serializer.decode(bytes.clone());
172 }
173
Madan Jampani778f7ad2014-11-05 22:46:15 -0800174 @Override
175 public <T extends Entry> List<T> getEntries(long from, long to) {
176 assertIsOpen();
177 DB db = txMaker.makeTx();
178 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800179 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800180 if (log.isEmpty()) {
181 throw new LogIndexOutOfBoundsException("Log is empty");
182 } else if (from < log.firstKey()) {
183 throw new LogIndexOutOfBoundsException("From index out of bounds.");
184 } else if (to > log.lastKey()) {
185 throw new LogIndexOutOfBoundsException("To index out of bounds.");
186 }
187 List<T> entries = new ArrayList<>((int) (to - from + 1));
188 for (long i = from; i <= to; i++) {
Yuta HIGUCHI1ec41662014-11-19 22:01:54 -0800189 T entry = verifyNotNull(decodeEntry(log.get(i)), "LogEntry %s was null", i);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800190 entries.add(entry);
191 }
192 return entries;
193 } finally {
194 db.close();
195 }
196 }
197
198 @Override
199 public <T extends Entry> T getEntry(long index) {
200 assertIsOpen();
201 DB db = txMaker.makeTx();
202 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800203 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800204 byte[] entryBytes = log.get(index);
Yuta HIGUCHI1ec41662014-11-19 22:01:54 -0800205 return entryBytes == null ? null : verifyNotNull(decodeEntry(entryBytes),
Yuta HIGUCHIa38d9952014-11-10 09:46:36 -0800206 "LogEntry %s was null", index);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800207 } finally {
208 db.close();
209 }
210 }
211
212 @Override
213 public boolean isEmpty() {
214 assertIsOpen();
215 DB db = txMaker.makeTx();
216 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800217 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800218 return log.isEmpty();
219 } finally {
220 db.close();
221 }
222 }
223
224 @Override
225 public <T extends Entry> T lastEntry() {
226 assertIsOpen();
227 DB db = txMaker.makeTx();
228 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800229 BTreeMap<Long, byte[]> log = getLogMap(db);
Yuta HIGUCHI1ec41662014-11-19 22:01:54 -0800230 return log.isEmpty() ? null : verifyNotNull(decodeEntry(log.lastEntry().getValue()));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800231 } finally {
232 db.close();
233 }
234 }
235
236 @Override
237 public long lastIndex() {
238 assertIsOpen();
239 DB db = txMaker.makeTx();
240 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800241 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800242 return log.isEmpty() ? 0 : log.lastKey();
243 } finally {
244 db.close();
245 }
246 }
247
248 @Override
249 public void removeAfter(long index) {
250 assertIsOpen();
251 txMaker.execute(new TxBlock() {
252 @Override
253 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800254 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800255 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800256 long removedBytes = 0;
257 ConcurrentNavigableMap<Long, byte[]> tailMap = log.tailMap(index, false);
258 Iterator<Map.Entry<Long, byte[]>> it = tailMap.entrySet().iterator();
259 while (it.hasNext()) {
260 Map.Entry<Long, byte[]> entry = it.next();
261 removedBytes += entry.getValue().length;
262 it.remove();
Madan Jampani778f7ad2014-11-05 22:46:15 -0800263 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800264 size.addAndGet(-removedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800265 }
266 });
267 }
268
269 @Override
270 public long size() {
271 assertIsOpen();
272 DB db = txMaker.makeTx();
273 try {
274 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
275 return size.get();
276 } finally {
277 db.close();
278 }
279 }
280
281 @Override
282 public void sync() throws IOException {
283 assertIsOpen();
284 }
285
286 @Override
287 public void compact(long index, Entry entry) throws IOException {
288
289 assertIsOpen();
290 txMaker.execute(new TxBlock() {
291 @Override
292 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800293 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800294 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
295 ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800296 Iterator<Map.Entry<Long, byte[]>> it = headMap.entrySet().iterator();
297
298 long deletedBytes = 0;
299 while (it.hasNext()) {
300 Map.Entry<Long, byte[]> e = it.next();
301 deletedBytes += e.getValue().length;
302 it.remove();
303 }
304 size.addAndGet(-deletedBytes);
305 byte[] entryBytes = verifyNotNull(serializer.encode(entry));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800306 byte[] existingEntry = log.put(index, entryBytes);
Madan Jampani348a9fe2014-11-09 01:37:51 -0800307 if (existingEntry != null) {
308 size.addAndGet(entryBytes.length - existingEntry.length);
309 } else {
310 size.addAndGet(entryBytes.length);
311 }
Madan Jampani778f7ad2014-11-05 22:46:15 -0800312 db.compact();
313 }
314 });
315 }
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800316
317 private BTreeMap<Long, byte[]> getLogMap(DB db) {
318 return db.createTreeMap(LOG_NAME)
319 .valuesOutsideNodesEnable()
320 .keySerializerWrap(Serializer.LONG)
321 .valueSerializer(Serializer.BYTE_ARRAY)
322 .makeOrGet();
323 }
Madan Jampani2ee20002014-11-06 20:06:12 -0800324}