blob: 637a643f744ee1a1516788f95aa4c496fa55af13 [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
Madan Jampani2ee20002014-11-06 20:06:12 -080044 public MapDBLog(String dbFileName, StoreSerializer serializer) {
45 this.dbFile = new File(dbFileName);
Madan Jampani778f7ad2014-11-05 22:46:15 -080046 this.serializer = serializer;
47 }
48
49 @Override
50 public void open() throws IOException {
51 txMaker = DBMaker
52 .newFileDB(dbFile)
53 .makeTxMaker();
54 }
55
56 @Override
57 public void close() throws IOException {
58 assertIsOpen();
59 txMaker.close();
60 txMaker = null;
61 }
62
63 @Override
64 public boolean isOpen() {
65 return txMaker != null;
66 }
67
68 protected void assertIsOpen() {
69 checkState(isOpen(), "The log is not currently open.");
70 }
71
72 @Override
73 public long appendEntry(Entry entry) {
74 checkArgument(entry != null, "expecting non-null entry");
75 return appendEntries(entry).get(0);
76 }
77
78 @Override
79 public List<Long> appendEntries(Entry... entries) {
80 checkArgument(entries != null, "expecting non-null entries");
81 return appendEntries(Arrays.asList(entries));
82 }
83
84 @Override
85 public List<Long> appendEntries(List<Entry> entries) {
86 assertIsOpen();
87 checkArgument(entries != null, "expecting non-null entries");
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080088 final List<Long> indices = new ArrayList<>(entries.size());
Madan Jampani778f7ad2014-11-05 22:46:15 -080089
90 txMaker.execute(new TxBlock() {
91 @Override
92 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -080093 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -080094 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
95 long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1;
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -080096 long addedBytes = 0;
Madan Jampani778f7ad2014-11-05 22:46:15 -080097 for (Entry entry : entries) {
98 byte[] entryBytes = serializer.encode(entry);
99 log.put(nextIndex, entryBytes);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800100 addedBytes += entryBytes.length;
Madan Jampani778f7ad2014-11-05 22:46:15 -0800101 indices.add(nextIndex);
102 nextIndex++;
103 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800104 size.addAndGet(addedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800105 }
106 });
107
108 return indices;
109 }
110
111 @Override
112 public boolean containsEntry(long index) {
113 assertIsOpen();
114 DB db = txMaker.makeTx();
115 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800116 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800117 return log.containsKey(index);
118 } finally {
119 db.close();
120 }
121 }
122
123 @Override
124 public void delete() throws IOException {
125 assertIsOpen();
126 txMaker.execute(new TxBlock() {
127 @Override
128 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800129 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800130 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
131 log.clear();
132 size.set(0);
133 }
134 });
135 }
136
137 @Override
138 public <T extends Entry> T firstEntry() {
139 assertIsOpen();
140 DB db = txMaker.makeTx();
141 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800142 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800143 return log.isEmpty() ? null : serializer.decode(log.firstEntry().getValue());
144 } finally {
145 db.close();
146 }
147 }
148
149 @Override
150 public long firstIndex() {
151 assertIsOpen();
152 DB db = txMaker.makeTx();
153 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800154 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800155 return log.isEmpty() ? 0 : log.firstKey();
156 } finally {
157 db.close();
158 }
159 }
160
161 @Override
162 public <T extends Entry> List<T> getEntries(long from, long to) {
163 assertIsOpen();
164 DB db = txMaker.makeTx();
165 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800166 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800167 if (log.isEmpty()) {
168 throw new LogIndexOutOfBoundsException("Log is empty");
169 } else if (from < log.firstKey()) {
170 throw new LogIndexOutOfBoundsException("From index out of bounds.");
171 } else if (to > log.lastKey()) {
172 throw new LogIndexOutOfBoundsException("To index out of bounds.");
173 }
174 List<T> entries = new ArrayList<>((int) (to - from + 1));
175 for (long i = from; i <= to; i++) {
176 T entry = serializer.decode(log.get(i));
177 entries.add(entry);
178 }
179 return entries;
180 } finally {
181 db.close();
182 }
183 }
184
185 @Override
186 public <T extends Entry> T getEntry(long index) {
187 assertIsOpen();
188 DB db = txMaker.makeTx();
189 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800190 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800191 byte[] entryBytes = log.get(index);
192 return entryBytes == null ? null : serializer.decode(entryBytes);
193 } finally {
194 db.close();
195 }
196 }
197
198 @Override
199 public boolean isEmpty() {
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 return log.isEmpty();
205 } finally {
206 db.close();
207 }
208 }
209
210 @Override
211 public <T extends Entry> T lastEntry() {
212 assertIsOpen();
213 DB db = txMaker.makeTx();
214 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800215 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800216 return log.isEmpty() ? null : serializer.decode(log.lastEntry().getValue());
217 } finally {
218 db.close();
219 }
220 }
221
222 @Override
223 public long lastIndex() {
224 assertIsOpen();
225 DB db = txMaker.makeTx();
226 try {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800227 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800228 return log.isEmpty() ? 0 : log.lastKey();
229 } finally {
230 db.close();
231 }
232 }
233
234 @Override
235 public void removeAfter(long index) {
236 assertIsOpen();
237 txMaker.execute(new TxBlock() {
238 @Override
239 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800240 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800241 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800242 long removedBytes = 0;
243 ConcurrentNavigableMap<Long, byte[]> tailMap = log.tailMap(index, false);
244 Iterator<Map.Entry<Long, byte[]>> it = tailMap.entrySet().iterator();
245 while (it.hasNext()) {
246 Map.Entry<Long, byte[]> entry = it.next();
247 removedBytes += entry.getValue().length;
248 it.remove();
Madan Jampani778f7ad2014-11-05 22:46:15 -0800249 }
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800250 size.addAndGet(-removedBytes);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800251 }
252 });
253 }
254
255 @Override
256 public long size() {
257 assertIsOpen();
258 DB db = txMaker.makeTx();
259 try {
260 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
261 return size.get();
262 } finally {
263 db.close();
264 }
265 }
266
267 @Override
268 public void sync() throws IOException {
269 assertIsOpen();
270 }
271
272 @Override
273 public void compact(long index, Entry entry) throws IOException {
274
275 assertIsOpen();
276 txMaker.execute(new TxBlock() {
277 @Override
278 public void tx(DB db) {
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800279 BTreeMap<Long, byte[]> log = getLogMap(db);
Madan Jampani778f7ad2014-11-05 22:46:15 -0800280 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
281 ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index);
Yuta HIGUCHIebaa1572014-11-09 19:14:31 -0800282 Iterator<Map.Entry<Long, byte[]>> it = headMap.entrySet().iterator();
283
284 long deletedBytes = 0;
285 while (it.hasNext()) {
286 Map.Entry<Long, byte[]> e = it.next();
287 deletedBytes += e.getValue().length;
288 it.remove();
289 }
290 size.addAndGet(-deletedBytes);
291 byte[] entryBytes = verifyNotNull(serializer.encode(entry));
Madan Jampani778f7ad2014-11-05 22:46:15 -0800292 byte[] existingEntry = log.put(index, entryBytes);
Madan Jampani348a9fe2014-11-09 01:37:51 -0800293 if (existingEntry != null) {
294 size.addAndGet(entryBytes.length - existingEntry.length);
295 } else {
296 size.addAndGet(entryBytes.length);
297 }
Madan Jampani778f7ad2014-11-05 22:46:15 -0800298 db.compact();
299 }
300 });
301 }
Yuta HIGUCHI94ecb892014-11-06 23:31:44 -0800302
303 private BTreeMap<Long, byte[]> getLogMap(DB db) {
304 return db.createTreeMap(LOG_NAME)
305 .valuesOutsideNodesEnable()
306 .keySerializerWrap(Serializer.LONG)
307 .valueSerializer(Serializer.BYTE_ARRAY)
308 .makeOrGet();
309 }
Madan Jampani2ee20002014-11-06 20:06:12 -0800310}