package edu.berkeley.thebes.common.persistence.disk; import edu.berkeley.thebes.common.config.Config; import edu.berkeley.thebes.common.data.DataItem; import edu.berkeley.thebes.common.persistence.IPersistenceEngine; import edu.berkeley.thebes.common.persistence.util.LockManager; import edu.berkeley.thebes.common.thrift.ThriftDataItem; import org.apache.commons.io.FileUtils; import org.apache.thrift.TDeserializer; import org.apache.thrift.TException; import org.apache.thrift.TSerializer; import org.apache.thrift.protocol.TBinaryProtocol; import org.iq80.leveldb.*; import com.yammer.metrics.Metrics; import com.yammer.metrics.core.Gauge; import com.yammer.metrics.core.Histogram; import com.yammer.metrics.core.Meter; import com.yammer.metrics.core.Timer; import com.yammer.metrics.core.TimerContext; import org.slf4j.*; import static org.fusesource.leveldbjni.JniDBFactory.*; import java.io.*; import java.util.concurrent.TimeUnit; public class LevelDBPersistenceEngine implements IPersistenceEngine { DB db; LockManager lockManager; private static org.slf4j.Logger logger = LoggerFactory.getLogger(LevelDBPersistenceEngine.class); private final Meter putCount = Metrics.newMeter(LevelDBPersistenceEngine.class, "put-requests", "requests", TimeUnit.SECONDS); private final Meter getCount = Metrics.newMeter(LevelDBPersistenceEngine.class, "get-requests", "requests", TimeUnit.SECONDS); private final Meter obsoletePutCount = Metrics.newMeter(LevelDBPersistenceEngine.class, "obsolete-put-requests", "requests", TimeUnit.SECONDS); private final Meter nullGetCount = Metrics.newMeter(LevelDBPersistenceEngine.class, "null-get-requests", "requests", TimeUnit.SECONDS); private final Timer putLatencyTimer = Metrics.newTimer(LevelDBPersistenceEngine.class, "leveldb-put-latencies"); private final Timer forcePutLatencyTimer = Metrics.newTimer(LevelDBPersistenceEngine.class, "leveldb-force-put-latencies"); private final Timer getLatencyTimer = Metrics.newTimer(LevelDBPersistenceEngine.class, "leveldb-get-latencies"); private final Histogram putSizeHistogram = Metrics.newHistogram(LevelDBPersistenceEngine.class, "leveldb-put-size-histogram"); private final Histogram getSizeHistogram = Metrics.newHistogram(LevelDBPersistenceEngine.class, "leveldb-get-size-histogram"); ThreadLocal<TSerializer> serializer = new ThreadLocal<TSerializer>() { @Override protected TSerializer initialValue() { return new TSerializer(); } }; ThreadLocal<TDeserializer> deserializer = new ThreadLocal<TDeserializer>() { @Override protected TDeserializer initialValue() { return new TDeserializer(); } }; private final String dbFilename; public LevelDBPersistenceEngine(String dbFilename) { this.dbFilename = dbFilename; } public void open() throws IOException { Options options = new Options(); options.blockSize(1024); if(Config.getDatabaseCacheSize() != -1) options.cacheSize(Config.getDatabaseCacheSize()); options.createIfMissing(true); lockManager = new LockManager(); if(Config.doCleanDatabaseFile()) { try { FileUtils.forceDelete(new File(dbFilename)); } catch(Exception e) { if (!(e instanceof FileNotFoundException)) logger.warn("error: ", e) ; } } db = factory.open(new File(dbFilename), options); Metrics.newGauge(LevelDBPersistenceEngine.class, "locktable-size", new Gauge<Integer>() { @Override public Integer value() { return lockManager.getSize(); } }); } @Override public void force_put(String key, DataItem value) throws TException { TimerContext context = forcePutLatencyTimer.time(); try { doPut(key, value); } finally { context.stop(); } } @Override public void put_if_newer(String key, DataItem value) throws TException { putCount.mark(); TimerContext context = putLatencyTimer.time(); try { if(value == null) { logger.warn("NULL write to key "+key); return; } lockManager.lock(key); try { DataItem curItem = get(key); if (curItem != null && curItem.getVersion().compareTo(value.getVersion()) > 0) { obsoletePutCount.mark(); return; } else { doPut(key, value); return; } } finally { lockManager.unlock(key); } } finally { context.stop(); } } /** Please own the lock! */ public void doPut(String key, DataItem value) throws TException { byte[] putBytes = serializer.get().serialize(value.toThrift()); putSizeHistogram.update(putBytes.length); db.put(key.getBytes(), putBytes); } public DataItem get(String key) throws TException { getCount.mark(); TimerContext context = getLatencyTimer.time(); try { byte[] byteRet = db.get(key.getBytes()); if(byteRet == null) { nullGetCount.mark(); return null; } getSizeHistogram.update(byteRet.length); ThriftDataItem tdrRet = new ThriftDataItem(); deserializer.get().deserialize(tdrRet, byteRet); return new DataItem(tdrRet); } finally { context.stop(); } } public void delete(String key) throws TException { lockManager.lock(key); try { db.delete(key.getBytes()); } finally { lockManager.unlock(key); } } public void close() throws IOException { db.close(); } }