better pruning. data store improvements.

This commit is contained in:
Christopher Jeffrey 2016-03-12 04:59:56 -08:00
parent f79d2cfa89
commit bb306c3547
2 changed files with 113 additions and 72 deletions

View File

@ -1216,8 +1216,15 @@ ChainDB.prototype.hasBlock = function hasBlock(hash, callback) {
var self = this; var self = this;
var id = 'b/b/' + hash; var id = 'b/b/' + hash;
if (typeof hash === 'number') if (typeof hash === 'number') {
id = 'b/h/' + pad32(hash); return this.getHash(hash, function(err, hash) {
if (err)
return callback(err);
if (!hash)
return callback();
return self.hasBlock(hash, callback);
});
}
this.db.get(id, function(err, data) { this.db.get(id, function(err, data) {
if (err && err.type !== 'NotFoundError') if (err && err.type !== 'NotFoundError')
@ -1330,6 +1337,7 @@ ChainDB.prototype._pruneBlock = function _pruneBlock(block, callback) {
// For much more aggressive pruning, we could delete // For much more aggressive pruning, we could delete
// the block headers 288 blocks before this one here as well. // the block headers 288 blocks before this one here as well.
batch.put('b/q/' + pad32(block.height + self.keepBlocks), block.hash());
return utils.forEachSerial(block.txs, function(tx, next) { return utils.forEachSerial(block.txs, function(tx, next) {
self._pruneTX(tx, batch, next); self._pruneTX(tx, batch, next);
@ -1383,6 +1391,26 @@ ChainDB.prototype._pruneTX = function _pruneTX(tx, batch, callback) {
}, callback); }, callback);
}; };
ChainDB.prototype._pruneTX = function _pruneTX(tx, batch, callback) {
var self = this;
batch.put(
't/q/' + pad32(tx.height + self.keepBlocks) + '/' + tx.hash('hex'),
DUMMY);
return callback();
if (tx.isCoinbase())
return callback();
tx.inputs.forEach(function(input) {
batch.put(
't/q/' + pad32(tx.height + self.keepBlocks) + '/' + input.prevout.hash,
DUMMY);
});
callback();
};
ChainDB.prototype._removeTX = function _removeTX(hash, batch, callback) { ChainDB.prototype._removeTX = function _removeTX(hash, batch, callback) {
var self = this; var self = this;
var uniq = {}; var uniq = {};
@ -1468,13 +1496,23 @@ ChainDB.prototype._pruneQueue = function _pruneQueue(block, batch, callback) {
if (err) if (err)
return callback(err); return callback(err);
if (hashes.length) // if (hashes.length)
utils.debug('Retroactively pruning txs at height %d.', block.height); // utils.debug('Retroactively pruning txs at height %d.', block.height);
utils.forEachSerial(hashes, function(hash, next) { self.db.get('b/q/' + pad32(block.height), function(err, hash) {
batch.del('t/q/' + pad32(block.height) + '/' + hash); if (err && err.type !== 'NotFoundError')
self._removeTX(hash, batch, next); return callback(err);
}, callback);
if (hash) {
batch.del('b/q/' + pad32(block.height));
batch.del('b/b/' + utils.toHex(hash));
}
utils.forEachSerial(hashes, function(hash, next) {
batch.del('t/q/' + pad32(block.height) + '/' + hash);
self._removeTX(hash, batch, next);
}, callback);
});
} }
}; };

View File

@ -14,8 +14,10 @@ var assert = utils.assert;
var fs = bcoin.fs; var fs = bcoin.fs;
var pad32 = utils.pad32; var pad32 = utils.pad32;
var MAX_FILE_SIZE = 512 * 1024 * 1024;
var MAX_FILE_SIZE = 1 * 1024 * 1024; var MAX_FILE_SIZE = 1 * 1024 * 1024;
var nullBuffer = new Buffer([0xff, 0xff, 0xff, 0xff]); var MAX_FILE_SIZE = 100 * 1024;
var NULL_CHUNK = new Buffer([0xff, 0xff, 0xff, 0xff]);
/** /**
* DataStore * DataStore
@ -38,18 +40,21 @@ function DataStore(db, options) {
if (!this.dir) if (!this.dir)
this.dir = bcoin.prefix + '/store-' + network.type + '.db'; this.dir = bcoin.prefix + '/store-' + network.type + '.db';
// Keep a pool of FDs open for disk cache benefits
this._db = db; this._db = db;
this.db = this; this.db = this;
this.busy = false;
this.jobs = [];
this.fileIndex = -1;
// Keep a pool of FDs open for disk cache benefits
this.pool = new bcoin.lru(50, 1, function(key, value) { this.pool = new bcoin.lru(50, 1, function(key, value) {
fs.close(value.fd, function(err) { fs.close(value.fd, function(err) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
}); });
}); });
this.busy = false;
this.jobs = [];
this.fileIndex = -1;
this._init(); this._init();
} }
@ -206,7 +211,11 @@ DataStore.prototype.getData = function get(off, callback) {
this.openFile(off.fileIndex, function(err, fd, fsize) { this.openFile(off.fileIndex, function(err, fd, fsize) {
if (err) if (err)
return callback(err); return callback(err);
return self.read(fd, off.offset, off.size, callback); return self.read(fd, off.offset, off.size, function(err, data) {
if (err)
return callback(err);
return callback(null, data);
});
}); });
}; };
@ -215,28 +224,35 @@ DataStore.prototype.get = function get(key, callback) {
return this._db.get(key, function(err, offset) { return this._db.get(key, function(err, offset) {
if (err) if (err)
return callback(err); return callback(err);
if (isDirect(offset)) if (isDirect(key, offset))
return callback(null, offset); return callback(null, offset);
return self.getData(offset, callback); return self.getData(offset, callback);
}); });
}; };
DataStore.prototype.batch = function batch(ops, options, callback) { DataStore.prototype.batch = function batch(ops, options, callback) {
var batch = new Batch(this); var batch;
if (!callback) {
callback = options;
options = null;
}
if (!options)
options = {};
batch = new Batch(this, options);
if (ops) { if (ops) {
if (!callback) {
callback = options;
options = null;
}
if (!options)
options = {};
batch.ops = ops; batch.ops = ops;
return batch.write(callback); return batch.write(callback);
} }
return batch; return batch;
}; };
function Batch(store) { function Batch(store, options) {
this.options = options;
this.ops = []; this.ops = [];
this.store = store; this.store = store;
this._db = store._db; this._db = store._db;
@ -257,11 +273,16 @@ Batch.prototype.write = function write(callback) {
if (!this._db) if (!this._db)
return callback(new Error('Already written.')); return callback(new Error('Already written.'));
batch = this._db.batch(); batch = this.options.sync
? utils.syncBatch(this._db)
: this._db.batch();
if (this.options.sync)
this._db.fsync = true;
utils.forEachSerial(this.ops, function(op, next) { utils.forEachSerial(this.ops, function(op, next) {
if (op.type === 'put') { if (op.type === 'put') {
if (isDirect(op.value)) { if (isDirect(op.key, op.value)) {
batch.put(op.key, op.value); batch.put(op.key, op.value);
return next(); return next();
} }
@ -280,7 +301,7 @@ Batch.prototype.write = function write(callback) {
if (!offset) if (!offset)
return next(); return next();
batch.del(op.key); batch.del(op.key);
if (isDirect(offset)) if (isDirect(op.key, offset))
return next(); return next();
self.store.delData(offset, next); self.store.delData(offset, next);
}); });
@ -318,8 +339,9 @@ Iterator.prototype.seek = function seek(key) {
// Store coins, chain entries, dummies, lookup // Store coins, chain entries, dummies, lookup
// hashes directly in the db (unless they're // hashes directly in the db (unless they're
// the same length as offset). // the same length as offset).
function isDirect(data) { function isDirect(key, data) {
return data.length !== 12 && data.length <= 116; return !/^(b\/b\/|t\/t\/)/.test(key);
return data.length !== 12 && (data.length <= 87 || data.length === 116);
} }
Iterator.prototype.next = function next(callback) { Iterator.prototype.next = function next(callback) {
@ -328,7 +350,7 @@ Iterator.prototype.next = function next(callback) {
return callback(err); return callback(err);
if (value) { if (value) {
if (isDirect(value)) if (isDirect(key, value))
return callback(null, key, value); return callback(null, key, value);
return self.getData(value, function(err, data) { return self.getData(value, function(err, data) {
if (err) if (err)
@ -359,7 +381,7 @@ utils.wrap = function wrap(callback, unlock) {
DataStore.prototype.putData = function putData(data, callback, force) { DataStore.prototype.putData = function putData(data, callback, force) {
var self = this; var self = this;
var offset, undo; var offset;
var unlock = this._lock(putData, [data, callback], force); var unlock = this._lock(putData, [data, callback], force);
if (!unlock) if (!unlock)
@ -412,7 +434,7 @@ DataStore.prototype.put = function put(key, value, callback) {
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
if (isDirect(value)) if (isDirect(key, value))
return self._db.put(key, value, callback); return self._db.put(key, value, callback);
return this.putData(value, function(err, offset) { return this.putData(value, function(err, offset) {
@ -425,7 +447,6 @@ DataStore.prototype.put = function put(key, value, callback) {
DataStore.prototype.readUndo = function readUndo(index, offset, callback) { DataStore.prototype.readUndo = function readUndo(index, offset, callback) {
var self = this; var self = this;
var undo;
return this.openFile(index, function(err, fd, fsize) { return this.openFile(index, function(err, fd, fsize) {
if (err) if (err)
@ -440,26 +461,9 @@ DataStore.prototype.readUndo = function readUndo(index, offset, callback) {
}); });
}; };
DataStore.prototype.isNull = function isNull(index, offset, size, callback) {
var self = this;
var undo;
return this.openFile(index, function(err, fd, fsize) {
if (err)
return callback(err);
return self.read(fd, offset, 4, function(err, data) {
if (err)
return callback(err);
return callback(null, utils.readU32(data, 0) === 0xffffffff);
});
});
};
DataStore.prototype.delData = function delData(off, callback, force) { DataStore.prototype.delData = function delData(off, callback, force) {
var self = this; var self = this;
var undo, index, offset, size; var index, offset, size;
var unlock = this._lock(delData, [off, callback], force); var unlock = this._lock(delData, [off, callback], force);
if (!unlock) if (!unlock)
@ -476,45 +480,44 @@ DataStore.prototype.delData = function delData(off, callback, force) {
if (err) if (err)
return callback(err); return callback(err);
return self.write(fd, offset, nullBuffer, function(err, data) { // Overwrite the "fileIndex" in the undo chunk
return self.write(fd, offset + size, NULL_CHUNK, function(err) {
if (err) if (err)
return callback(err); return callback(err);
if (offset + size !== fsize) if (offset + size + 12 !== fsize)
return callback(); return callback();
utils.debug('Pruning block files...');
utils.debug({ offset: offset, size: size, total: offset + size, fsize: fsize });
// If we're deleting the last record, traverse // If we're deleting the last record, traverse
// through the reverse linked list of undo offsets // through the reverse linked list of undo offsets
// until we hit a record that isn't deleted. // until we hit a record that isn't deleted.
// Truncate to the last deleted record's offset. // Truncate to the last deleted record's offset.
(function next() { (function next() {
if (offset === 0)
return done();
self.readUndo(index, offset, function(err, undo) { self.readUndo(index, offset, function(err, undo) {
if (err) if (err)
return callback(err); return callback(err);
self.isNull(index, undo.offset, function(err, res) { if (undo.fileIndex !== 0xffffffff)
if (!res) return done();
return done(); offset = undo.offset;
offset = undo.offset; if (offset === 0)
if (offset === 0) return done();
return done(); return next();
return next();
});
}); });
})(); })();
function done() { function done() {
// Delete the file if nothing is in it. // Delete the file if nothing is in it.
utils.debug('Truncating to %d', offset);
if (offset === 0) { if (offset === 0) {
return fs.close(fd, function(err) { self.pool.remove(index);
if (err) return fs.unlink(self.dir + '/f' + pad32(index), callback);
return callback(err);
self.pool.remove(index);
fs.unlink(self.dir + '/f' + pad32(index), callback);
});
} }
if (self.pool.has(index)) self.truncate(index, offset + 12, callback);
self.pool.get(index).size = offset;
self.truncate(index, offset, callback);
} }
}); });
}); });
@ -533,8 +536,8 @@ DataStore.prototype.del = function del(key, callback, force) {
if (err && err.type !== 'NotFoundError') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
if (!off) if (!off)
return next(); return callback();
if (isDirect(off)) if (isDirect(key, off))
return self._db.del(key, callback); return self._db.del(key, callback);
self.delData(off, function(err) { self.delData(off, function(err) {
if (err) if (err)
@ -565,7 +568,7 @@ DataStore.prototype.truncate = function truncate(index, size, callback) {
callback = utils.ensure(callback); callback = utils.ensure(callback);
self.openFile(index, function(err, fd, fsize) { this.openFile(index, function(err, fd, fsize) {
if (err) if (err)
return callback(err); return callback(err);