
com.cloudhopper.datastore.leveldb.LevelDbDataStore Maven / Gradle / Ivy
The newest version!
package com.cloudhopper.datastore.leveldb;
/*
* #%L
* ch-datastore
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.cloudhopper.commons.util.HexUtil;
import com.cloudhopper.commons.util.StringUtil;
import com.cloudhopper.datastore.*;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.iq80.leveldb.CompressionType;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBException;
import org.iq80.leveldb.DBFactory;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Options;
import org.iq80.leveldb.ReadOptions;
import org.iq80.leveldb.WriteOptions;
import static org.fusesource.leveldbjni.JniDBFactory.*;
/**
* LevelDb backed implementation of a DataStore.
*
* @author garth
*/
public class LevelDbDataStore extends BaseDataStore implements AscendingIteratorSupport {
private final static Logger logger = LoggerFactory.getLogger(LevelDbDataStore.class);
// path used to generate a full path to the database file
private File path;
// the underlying db
private DB db;
// for performance, the database is not synchronized on every put() and take()
// if -1, sync() is disabled
// if 0, sync() is called on ever put() and take()
// if > 0, the amount of ms between a sync() call is made, set to 5 seconds by default
private long synchronizeInterval;
private Timer synchronizeTimer;
private TimerTask synchronizeTimerTask;
public LevelDbDataStore() {
super("LDB-JNI");
// defaults
synchronizeInterval = 5000;
}
/**
* Returns the underlying database. If not opened, this will return
* null.
* @return Null if not open, otherwise the B-Tree database.
*/
public DB getDatabase() {
return db;
}
/**
* Gets the interval between sync() calls to underlying LevelDb cabinet db.
* For performance, by default, the database is not synchronized on every
* put() and take(). If -1, sync() is disabled. If 0, sync() is called on
* every put() and take(). Throughput will be dramatically decreased, but
* it'll guarantee the data makes it into the file. If > 0, its the length
* of ms between a sync() call is made, set to 5 seconds by default
* @return The synchronization interval in milliseconds
*/
public long getSynchronizeInterval() {
return synchronizeInterval;
}
/**
*
* @throws QueueStoreException Thrown if the data store is already open
* when this method is called.
*/
public void setSynchronizeInterval(long value) {
this.synchronizeInterval = value;
}
private File getDatabaseFile() {
return new File(getDirectory(), getName() + ".ldb");
}
@Override
public void doDelete() throws DataStoreFatalException {
File databaseFile = getDatabaseFile();
try {
factory.destroy(databaseFile, new Options());
databaseFile.delete();
} catch (Exception e) {
throw new DataStoreFatalException("Unable to delete " + databaseFile.getPath(), e);
}
}
@Override
public void doOpen() throws DataStoreFatalException {
path = getDatabaseFile();
Options options = new Options();
options.createIfMissing(true);
options.logger(new org.iq80.leveldb.Logger() {
public void log (String message) {
logger.trace("leveldb: {}", message);
}
});
try {
db = factory.open(path, options);
} catch (IOException e) {
throw new DataStoreFatalException("Unable to open " + toString() + " @ " + path + " {message=" + e.getMessage() + "}", e);
}
if (this.synchronizeInterval < 0) {
logger.debug("Automatic sync disabled {synchronizeInterval < 0} for " + toString());
} else if (this.synchronizeInterval == 0) {
logger.debug("Automatic sync enabled for every transaction {synchronizeInterval == 0} for " + toString());
} else {
// schedule task to print results every 5 seconds
synchronizeTimer = new Timer("LevelDbDataStoreSync-" + getName(), true);
synchronizeTimerTask = new TimerTask() {
@Override
public void run() {
try {
if (db != null) {
logger.trace("Synchronized data store [name=" + getName() + "]");
// Implement this in LevelDB by just writing a special key
byte[] syncKey = new byte[] { 0x00 };
byte[] syncValue = new byte[8];
ByteBuffer.wrap(syncValue).putLong(System.currentTimeMillis());
db.put(syncKey, syncValue, new WriteOptions().sync(true));
} else {
// cancel this task!
logger.warn("Cancelling synchronizeTimerTask since db was null, this may be normal if data store shutting down");
this.cancel();
}
} catch (Exception e) {
logger.error("", e);
}
}
};
synchronizeTimer.schedule(synchronizeTimerTask, synchronizeInterval, synchronizeInterval);
logger.debug("Automatic sync enabled every " + synchronizeInterval + " ms for " + toString());
}
}
@Override
public void doClose() throws DataStoreFatalException {
try {
// make sure sync thread is cancelled
if (synchronizeTimerTask != null) {
try {
synchronizeTimerTask.cancel();
} catch (Exception e) {
// ignore this
}
synchronizeTimerTask = null;
}
if (synchronizeTimer != null) {
try {
synchronizeTimer.cancel();
} catch (Exception e) {
// ignore this
}
synchronizeTimer = null;
}
if (db != null) {
try {
db.close();
} catch (Exception e) {
throw new DataStoreFatalException("Unable to properly close DataStore {message=" + e.getMessage() + "}", e);
}
}
} finally {
db = null;
}
}
@Override
public void doSetRecord(byte[] key, byte[] value) throws DataStoreFatalException {
try {
if (this.synchronizeInterval == 0) {
db.put(key, value, new WriteOptions().sync(true));
} else {
db.put(key, value, new WriteOptions().sync(false));
}
} catch (DBException e) {
throw new DataStoreFatalException("Unable to set value and key [0x" + HexUtil.toHexString(key) + "] [message=" + e.getMessage() + "]", e);
}
}
@Override
public byte[] doGetRecord(byte[] key) throws RecordNotFoundException, DataStoreFatalException {
byte[] value = null;
try {
value = db.get(key);
} catch (DBException e) {
throw new DataStoreFatalException("Unable to get value at key [0x" + HexUtil.toHexString(key) + "] [message=" + e.getMessage() + "]", e);
}
if (value == null) {
throw new RecordNotFoundException("The record for key [0x" + HexUtil.toHexString(key) + "] was not found");
}
return value;
}
@Override
public void doDeleteRecord(byte[] key) throws RecordNotFoundException, DataStoreFatalException {
try {
// TODO: need to throw a RecordNotFoundException if the key isn't there
doGetRecord(key);
if (this.synchronizeInterval == 0) {
db.delete(key, new WriteOptions().sync(true));
} else {
db.delete(key, new WriteOptions().sync(false));
}
} catch (DBException e) {
throw new DataStoreFatalException("Unable to delete record with key [0x" + HexUtil.toHexString(key) + "] [message=" + e.getMessage() + "]", e);
}
}
public DataStoreIterator doGetAscendingIterator() throws DataStoreFatalException {
return new LevelDbDataStoreIterator(getDatabase());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy